JAVA/Java의 정석

Java의 객체지향 프로그래밍 [4] : 다형성, 추상클래스, 인터페이스, 내부 클래스

JWonK 2023. 6. 26. 19:29
728x90
반응형

▶ 다형성이란?


상속과 함께 객체지향개념에서 중요한 특징 중 하나이다.

  • 객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미한다.
  • 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
  • 이를 좀 더 구체적으로 말하자면, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 한 것이다.

 

 

class Tv{
    boolean power;
    int channel;
    
    void power() { power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}

class CaptionTv extends Tv{
    String text;
    void caption() { /* 내용생략 */ }
}

위 두 클래스의 인스턴스를 생성할 때는

Tv t = new Tv();
CaptionTv c = new CaptionTv();

이처럼 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, Tv와 CaptionTv 클래스가 서로 상속관계에 있을 경우, 다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

 

Tv t = new CaptionTv();

 

인스턴스를 같은 타입의 참조변수로 참조하는 것과 조상타입의 참조변수로 참조하는 것은 어떤 차이가 있는지에 대해 알아본다.

 

CaptionTv c = new CaptionTv();
Tv        t = new CpationTv();
  • Tv타입의 참조변수로는 CaptionTv인스턴스 중에서 Tv클래스의 멤버들(상속받은 멤버포함)만 사용할 수 있다.
  • 따라서, 생성된 CaptionTv인스턴스의 멤버 중에서 Tv클래스에 정의 되지 않은 멤버, text와 caption()은 참조변수 t로 사용이 불가능하다.
  • 즉, t.text 또는 t.caption()와 같이 할 수 없다는 것이다.

→ 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

 

 

만약 아래와 같이 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 가능할까?

CaptionTv c = new Tv();

그렇지 않다. 위의 코드를 컴파일하면 에러가 발생한다. 그 이유는 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. 그래서 이를 허용하지 않는다.

 

→ 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.

 

→ 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.

→ 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

 

 

 

 

 

 

 

 

 참조변수의 형변환


기본형 변수와 같이 참조변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능하다.

 

  • 자손타입 → 조상타입 (Up-casting) : 형변환 생략가능
  • 조상타입 → 자손타입 (Down-casting) : 형변환 생략불가

 

→ 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다. 단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐이다.

 

서로 상속관계에 있는 타입 간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다. 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

 

 

 

 

 

 

참조변수와 인스턴스의 연결


조상 타입의 참조변수와 자손 타입의 참조변수의 차이점이 사용할 수 있는 멤버의 개수에 있다고 배웠다. 여기서 한 가지 더 알아두어야 할 내용이 있다.

  • 조상 클래스에 선언된 멤버 변수와 같은 이름의 인스턴스 변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조 변수로 인스턴스를 참조하는 경우는 서로 다른 결과를 얻는다.
  • 결론부터 말하자면, 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

 

class BindingTest{
    public static void main(String[] args){
        Parent p = new Child();
        Child c = new Child();
        
        System.out.println("p.x = " + p.x);
        p.method();
        
        System.out.println("c.x = " + c.x);
        c.method();
    }
}

class Parent{
    int x = 100;
    
    void method(){
        System.out.println("Parent Method");
    }
}

class Child extends Parent{
    int x = 200;
    
     void method(){
        System.out.println("Child Method");
    }
}



#############실행 결과##############
p.x = 100
Child Method
c.x = 200
Child Method

 

 

 

 

 

추상 클래스란(abstract class)?


  • 클래스를 설계도에 비유한다면, 추상 클래스는 미완성 설계도에 비유할 수 있다.
  • 미완성 설계도란, 단어의 뜻 그대로 완성되지 못한 채로 남겨진 설계도를 말한다. 클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아니라, 단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.
  • 미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스는 생성할 수 없다. 추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.
  • 추상클래스는 키워드 'abstract'를 붙이기만 하면 된다.

 

abstract class 클래스이름 {
    ...
}

 

 

 

 

 

 

 

  인터페이스(interface)란?


  • 인터페이스는 일종의 추상클래스이다.
  • 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버 변수를 구성원으로 가질 수 없다.
  • 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

 

interface 인터페잇이름 {
    public static final 타입 상수이름 = 값;
    public abstract 메서드이름 (매개변수목록);
}

 

일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약 사항이 있다.

  • 모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract이어야 하며, 이를 생략할 수 있다. (단, static 메서드와 디폴트 메서드는 예외 [jdk 1.8부터])

 

 

 

 

  인터페이스의 상속


인터페이스는 인터페이스로부터만 상속받을 수 있으며 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. 

  • 구현은 implements를 사용하여 구현한다

 

class 클래스이름 implements 인터페이스이름 {
    // 인터페이스에 정의된 추상메서드를 구현해야 한다.
}

class Fighter implements Fightable {
    public void move(int x, int y) { /* 생략 */ }
    public void attack(Unit u) { /* 생략 */ }
}

 

 

 

 

 

 

인터페이스를 이용한 다형성과 인터페이스 장점


→ 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인터턴스를 반환한다는 것을 의미한다.

 

인터페이스를 사용하는 이유와 장점이다.

 

1. 개발 시간을 단축시킬 수 있다.

: 일단 인터페이스가 작성도면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 매서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문

 

2. 표준화가 가능하다.

: 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

 

3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.

: 서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.

 

4. 독립적인 프로그래밍이 가능하다.

: 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다. 

 

 

 

 

 

 

인터페이스의 이해


인터페이스를 바르게 이해하기 위해서는 다음의 두 가지 사항을 기억해야한다.

 

  1. 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
  2. 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.

 

클래스 A를 작성하는데 있어서 클래스 B가 사용되지 않았다는 점에 주의해야한다. 

클래스 A는 클래스 B의 메서드를 호출하지만, 클래스 A는 인터페이스 I하고만 직접적인 관계에 있기 때문에 클래스 B의 변경에 영향을 받지 않는다.

 

클래스 A는 인터페이스를 통해 실제로 사용하는 클래스의 이름을 몰라도 되고 심지어는 실제로 구현된 클래스가 존재하지 않아도 문제되지 않는다. 클래스 A는 오직 직접적인 관계에 있는 I의 영향만 받는다.

 

 

 

 

 

 

 

 

내부 클래스(inner class)


내부 클래스는 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스를 선언하는 이유는 간단하다. 

두 클래스가 서로 긴밀한 관계에 있기 때문이다.

 

내부 클래스의 장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다. (캡슐화)

 

 

 

내부 클래스의 종류와 특징


내부 클래스 특 징
인스턴스 클래스
(instance class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다루어진다. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 클래스
(static class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static 멤버처럼 다루어진다. 주로 외부 클래스의 static 멤버, 특히 static 메서드에서 사용될 목적으로 선언된다.
지역 클래스
(local class)
외부 클래스의 메서드나 초기화 블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.

익명 클래스
(annoymous class)
클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스 (일회용)

 

 

 

 

 

 

▶ 익명 클래스 (anonymous class)


익명 클래스는 특이하게도 다른 내부 클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.

 

new 조상클래스이름() {
    // 멤버 선언
}

또는

new 구현인터페이스이름 {
    // 멤버 선언
}

 

이름이 없기 때문에 생성자도 가질 수 없으며, 조상클래스의 이름이나 구현받고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 여러 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.

 

 

 

class InnerEx8{
     public static void main(String[] args){
         Button b = new Button("Start");
         b.addActionListener(new ActionListener() {
         	public void actionPerformed(ActionEvent e){
              	  	System.out.println("ActionEvent occured!!");
           	}
            }
     	 };
     }
}
728x90
반응형