Chapter07. 객체지향 프로그래밍 II - part2

Hyeonjun·2022년 10월 14일
0

자바의 정석

목록 보기
6/7
post-thumbnail

5. 다형성 (Polymorphism)

5.1 다형성이란?

객체지향개념에서 다형성이란 여러가지 형태를 가질 수 있는 능력을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.

  • 조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.
  • 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, 두 클래스가 서로 상속관계에 있는 경우, 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것이 가능하다.

자손 클래스의 인스턴스를 참조하는 이유

Tv        t = new CaptionTv();
CaptionTv c = new CaptionTv();
  • 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
    • 참조변수 t로는 CaptionTv 인스턴스의 모든 멤버를 사용할 수 없다.
    • Tv 타입의 참조변수로는 CaptionTv인스턴스 중 Tv 클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다.
    • 따라서 생성된 CaptionTv인스턴스의 멤버 중에서 Tv 클래스에 정의되지 않은 멤버는 t로 사용이 불가능하다.
  • 또한 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다.
    CaptionTv c = new Tv(); // ClassCastException
    • 자손 타입의 참조변수가 사용할 수 있는 멤버의 개수가 더 많이 때문에 이를 허용하지 않는다.
    • 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
      • 클래스는 상속을 통해서 확장될 수는 있어도 축소될 수는 없어서, 조상 인스턴스의 멤버 개수는 자손 인스턴스의 멤버 개수보다 항상 적거나 같다.

5.2 참조변수의 형변환

  • 기본형 변수(primitive type)와 같이 참조변수 또한 형변환이 가능하다.
    • 단, 서로 상속관계에 잇는 클래스 사이에서만 가능하다.
  • 자손타입 → 조상타입 (Up-casting) : 형변환 생략 가능
  • 조상타입 → 자손타입 (Down-casting: 형변환 생략 불가
  • 형변환을 수행하기 전에 instanceof 연산자를 사용해서 참조변수가 참조하고 있는 실제 인스턴스의 타입을 확인하는 것이 안전하다.
  • 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
  • 단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것 뿐이다.
  • 서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
  • 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

5.3 instanceof 연산자

어떤 타입에 대한 instanceof 연산의 결과가 true라면, 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

  • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다.
  • 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를, 왼쪽에는 타입(클래스명)이 피연산자로 위치한다.
  • 그리고 연산의 결과로 boolean값인 true와 false중의 하나를 반환한다.

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

  • 조상클래스에 선언된 멤버변수와 같은 이름의 인스턴스 변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우는 서로 다른 결과를 얻는다.
  • 메서드의 경우 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에도 참조 변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.
  • 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상 타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.
  • 하지만 중복 정의되지 않은 경우, 조상타입의 참조변수를 사용했을 때와 자손 타입의 참조변수를 사용했을 때의 차이는 없다.
    • 중복된 경우는 참조변수의 타입에 따라 달라지지만, 중복되지않은 경우 하나뿐이므로 선택의 여지가 없기 때문이다.

5.5 매개변수의 다형성

  • 매개변수의 경우 해당 클래스의 자손타입의 참조변수라면 매개변수로 받아들일 수 있다.

5.6 여러 종류의 객체를 배열로 다루기

  • 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.
  • 또는 묶어서 다루고싶은 객체들의 상속관계를 따져서 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.

Vector 클래스

  • Object 타입의 배열을 동적으로 사용할 수 있다.

6. 추상클래스 (Abstract class)

6.1 추상클래스란?

추상 클래스는 미완성 메서드를 포함한다. 미완성 설계도에 비유할 수 있다.

  • 추상클래스 인스턴스는 생성할 수 없다.
    • 추상클래스는 상속을 통해서 자손 클래스에 의해서만 완성될 수 있다.
  • 추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어 바탕이 되는 조상 클래스로서 중요한 의미를 갖는다.
abstract class 클래스 이름 {...}
  • 키워드 abstract를 통해 선언할 수 있다.
  • 추상 메서드를 포함하지 않더라도 abstract를 통해 추상 클래스로 지정할 수 있다.

6.2 추상메서드(abstract method)

선언부만 작성하고 구현부는 작성하지 않은 채 남겨둔 것.

  • 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워둔다.
    • 그래서 추상클래스를 상속받는 자손 클래스는 조상의 추상 메서드를 적절하게 구현해야 한다.
  • 추상 클래스로부터 상속받는 자손 클래스는 오버라이딩을 통해 조상인 추상클래스의 추상 메서드를 모두 구현해주어야 한다.
    • 만일 조상으로부터 상속받은 추상 메서드 중 하나라도 구현하지 않는다면, 자손 클래스 역시 추상 클래스로 지정해주어야 한다.

6.3 추상 클래스의 작성

  • 여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상 클래스로 만들어 상속하도록 하는 경우도 있다.
  • 상속자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면,
  • 추상화기존의 클래스의 공통 부분을 뽑아내어 조상 클래스를 만드는 것이다.

추상화와 구체화

  • 추상화
    • 클래스간의 공통점을 찾아내어 공통의 조상을 만드는 작업
  • 구체화
    • 상속을 통해 클래스를 구현, 확장하는 작업

abstract를 붙이는 이유

  • 조상 클래스에 메서드가 선언되어 있다면 abstract를 붙이지 않아도 override를 통해 하위 메서드에서 확장할 수 있다.
  • 그럼에도 abstract를 붙이는 이유는
    • 추상 메서드로 정의해 자손 클래스를 작성할 때 추상 메서드를 반드시 구현하도록 강제할 수 있기 때문이다.
  • 자손 클래스들은 해당 메서드를 각자에 맞춰 구체화 할 수 있다.

7. 인터페이스 (Interface)

7.1 인터페이스란?

인터페이스는 일종의 추상 클래스이다.

  • 추상 클래스처럼 추상 메서드를 갖지만, 추상 클래스보다 추상화 정도가 높아서 추상 클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.
  • 오직 추상 메서드와 상수만을 멤버로 가질 수 있다.

7.2 인터페이스의 작성

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

인터페이스 멤버의 제약사항

  • 모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract이어야 하며, 이를 생략할 수 있다.
    • 단, JDK 1.8부터 static 메서드와 디폴트 메서드는 예외
      • JDK 1.8부터 인터페이스에 static 메서드와 디폴트 메서드의 추가를 허용하는 방향으로 변경되었다.

7.3 인터페이스의 상속

  • 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속이 가능하다.
  • 클래스와 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.

7.4 인터페이스의 구현

  • 인터페이스도 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없다.
class 클래스이름 implements 인터페이스 이름 {...}
  • 만일 구현하는 인터페이스 메서드의 일부만 구현한다면, abstract를 붙여 추상클래스로 선언해야 한다.
  • 상속과 구현을 동시에 할 수도 있다.
  • 인터페이스의 메서드를 구현하는 경우 반드시 public 접근 제어자를 사용해야 한다.
    • 인터페이스에서 추상 메서드들은 public abstract가 생략되어있기 때문.

7.5 인터페이스를 이용한 다중상속

  • 자바에서 인터페이스로 다중상속을 구현하는 경우는 거의 없다.
  • 인터페이스는 static 상수만 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 거의 없다.
    • 충돌하더라도 클래스 이름을 붙여 구분 가능하다.
  • 추상메서드는 구현 내용이 없어 조상 클래스의 메서드와 선언부가 일치하는 경우 조상 클래스의 메서드를 상속받으면 된다.
  • 이렇게 상속받는 멤버들의 충돌은 피할 수 있지만, 다중상속의 장점을 잃게된다.
  • 두 개의 클래스로부터 상속을 받아야 할 상황이라면
    1. 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한 쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나,
    2. 어느 한 쪽의 필요한 부분을 뽑아와서 인터페이스로 만든 다음 구현하도록 한다.

7.6 인터페이스를 이용한 다형성

  • 인터페이스도 구현체의 인스턴스를 참조할 수 있다.
  • 인스턴스 타입으로의 형변환도 가능하다.
  • 인터페이스는 메서드의 매개변수 타입으로 사용될 수 있다.
  • 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

7.7 인터페이스의 장점

  1. 개발 시간을 단축시킬 수 있다.
  2. 표준화가 가능하다.
  3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
  4. 독립적인 프로그래밍이 가능하다.

7.8 인터페이스의 이해

  • 클래스를 사용하는 쪽(Client)와 클래스를 제공하는 쪽(Server)가 있다.
  • 메서드를 클라이언트에서는 서버의 선언부만 알면 내용은 몰라도 된다.
  • 클라이언트가 서버를 직접 호출하지 않고 인터페이스를 매개체로 해서 클라이언트 가 인터페이스를 통해 서버와 같은 기능의 다른 서버로 대체 되어도 클라이언트는 전혀 영향을 받지 않을 수 있다.

7.9 디폴트 매서드와 static 메서드

  • JDK 1.8부터 디폴트 메서드와 staic 메서드도 추가할 수 있게 되었다.
  • static 메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 예전부터 할 수는 있었다.
    • 그러나 자바를 더 쉽게 배울 수 있도록 규칙을 단순화하려고 인터페이스의 모든 메서드는 추상 메서드로 만들도록 했다.
  • Collection 인터페이스는 인터페이스에는 항상 추상 메서드만 선언할 수 있도록 하기 위해 Collections라는 클래스에 들어가 있다.

디폴트 메서드

  • 인터페이스를 상속하는 여러 메소드가 있을 때, 불가피하게 메서드를 추가하는 경우 구현하는 클래스 모두 해당 메서드를 추가적으로 구현해야 한다.
  • 이런 번거로움을 해결하기 위해 디폴트 메서드를 사용한다.
  • 추상 메서드의 기본적인 구현을 제공하는 메서드로 추상 메서드가 아니기 때문에 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
  • 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우,
    1. 여러 인터페이스의 디폴트 메서드 간의 충돌
      • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
    2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
      • 조상 클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.

8. 내부 클래스 (Inner class)

8.1 내부 클래스란?

내부 클래스는 클래스 내에 선언된 클래스이다.

  • 두 클래스가 서로 긴밀한 관계에 있기 때문에 클래스 안에 다른 클래스를 선언한다.
  • 내부 클래스를 통해
    • 두 클래스의 멤버들 간에 쉽게 전근할 수 있고,
    • 코드의 복잡성을 줄일 수 있다. (캡슐화)

8.2 내부 클래스의 종류와 특징

  • 내부 클래스의 종류는 변수의 선언 위치에 따른 종류와 같다.

8.3 내부 클래스의 선언

class Outer {
	class InstanceInner { } // 인스턴스 클래스
	static class StaticInner { } // 스태틱 클래스
	
	void myMethod() {
		class LocalInner { } // 지역 클래스
	}
}

8.4 내부 클래스의 제어자와 접근성

  • 내부 클래스가 외부 클래스의 멤버와 같이 간주되고, 인스턴스 멤버와 staic 멤버 간의 규칙이 내부 클래스에도 똑같이 적용된다.
  • 내부클래스도 abstract, final, 접근제어자 모두 사용 가능하다.
  • 내부 클래스를 컴파일 하면 아래와 같은 클래스 파일(.class)이 생성된다.
    Outer$LocalInner.class

8.5 익명 클래스 (Anonymous class)

  • 다른 내부 클래스들과 달리 이름이 없다.
    • 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고, 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
new 조상클래스이름() }
	// 멤버 선언
}

또는

new 구현인터페이스이름() {
	// 멤버 선언
}
  • 이름이 없기 때문에 생성자도 가질 수 없으며, 조상 클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다.
  • 오로지 한 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.

Comparator

profile
더 나은 성취

0개의 댓글