본문 바로가기
JAVA

Java - 다형성, instanceof

by winteringg 2022. 10. 29.

1. 다형성 (Polymorphism)
1) 부모 타입의 참조 변수로 자식 타입의 여러 객체를 다룰 수 있는 기능. 다형성은 상속을 전제 조건으로 함.
2) 상속한 클래스의 객체는 슈퍼 클래스로도 서브 클래스로도 다룰 수 있음. 
3) 같은 코드에서 여러 다른 실행 결과가 나옴.
4) 하위클래스 객체를 상위클래스에 대입하여 사용할 수 있음.
5) 캡슐화(정보 은닉), 상속과 더불어 객체 지향 프로그래밍의 가장 큰 특징 중 하나임.
6) 다형성을 잘 활용하면 유연하고 확장성 있고, 유지보수가 편리한 프로그램을 만들 수 있음.

기본적으로는 아래 코드처럼 객체를 생성할 때 참조 타입과 인스턴스의 타입을 동일하게 작성해왔음.

//이제까지 타입은 아래처럼 동일해야 했음.
Tv t = new Tv();
SmartTv st = new SmartTv();

하지만 다형성은 아래 코드처럼 타입 불일치도 허용함.

TV -> 조상타입, SmartTv -> 자손타입일 경우,
조상타입 (Tv) 의 참조변수로 자손타입 SmartTv 인스턴스를 다룰 수 있음.

//타입 불일치 허용하는 다형성
Tv t = new SmartTv();

하지만 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수는 없음.

TV -> 조상타입, SmartTv -> 자손타입일 경우,

//조상 타입의 참조변수로 자손 타입의 객체를 가리키는 것은 가능.
Tv t = new SmartTv();
//자손 타입의 참조변수로 조상 타입의 객체를 가리키는 것은 불가능.
SmartTv st = new Tv();


2. 다형성을 사용하는 이유
1) 상속과 메서드 재정의를 활용하여 확장성 있는 프로그램을 만들 수 있음.
2) 다형성 적용이 안된 경우에는 if-else if 문이 구현되고 코드의 유지보수가 어려움. (CPU는 모든 if문들을 하나 하나씩 읽어내기 때문에 if문을 많이 사용하게 되면 비효율적인 코드가 되고, 가독성이 떨어짐.) 
3) 상위 클래스에서는 공통적인 부분을 제공하고 하위 클래스에서는 각 클래스에 맞는 기능만 구현.
4) 여러 클래스를 하나의 타입(상위 클래스)으로 핸들링 할 수 있음.

3. 자동 타입 형변환
1) 프로그램 실행 도중에 자동적으로 타입 변환이 일어남. 사용할 수 있는 멤버의 개수를 조절하는 것.
2) 자식 타입이 부모 타입으로 자동 형변환.
3) 상속 계층에서 어떤 상위 타입이라도 자동 타입 변환이 일어날 수 있음.
4) 자동 타입 변환된 이후의 효과
  - 부모 클래스에 선언된 필드와 메서드만 접근 가능함.
  - 메서드가 재정의 되었다면, 아무리 참조 변수가 부모 타입이고 부모 타입에 그 메서드가 있더라도, 자식 클래스의 재정의된 메서드가 호출 됨. 

class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }

Cat cat = new Cat();

Animal animal = cat;    //OK. 조상인 Animal 타입으로 형변환 가능.
Cat cat = (Cat)animal;  //OK. 자손인 Cat 타입으로 형변환 가능.
Dog dog = (Dog)cat      //에러. 상속관계가 아닌 클래스간의 형변환 불가능.

cat == animal   //두 참조 변수가 동일한 객체를 참조 하고 있는지? true가 나옴.
                //자식 객체가 부모 타입으로 자동 형변환이 되었을 경우에도, 
                //부모 타입 변수가 참조하는 것은 자식 객체.


4. 강제 형변환 (Casting)
1) 형변환의 전제조건 -- 상속, 구현관계에 있는 것만 객체타입 변환이 가능.
2) 부모 타입을 자식 타입으로 강제 변환하는 것.
   People (부모 클래스), Man (자식 클래스) 일 경우,
  ex) 자식클래스타입 변수 = (자식클래스타입) 부모클래스변수; Man m = (Man)people;
  ex) 자식 클래스에서 단독으로 만든 메서드를 호출할 경우
       => ((자식클래스)부모클래스타입.자식클래스메서드(); ((Man)people).run();
3) 강제 형변환 조건
  - 자식 타입 객체가 부모 타입으로 자동 변환 된 이후, 다시 자식 타입으로 변환할 때만 유효함. 나머지는 다 불가능.
4) 강제 타입 변환이 필요한 이유
  - 자식 타입이 부모 타입으로 자동 변환되면, 부모 타입에 선언된 필드와 메서드만 사용 가능함.
  - 그렇기 때문에 자식 타입에 선언된 필드와 메서드를 다시 사용해야 한다면 강제 타입 변환이 필요.
5) Up-casting (자동 형변환) : 자손타입에서 조상타입으로 형변환, 형변환 생략 가능. 묵시적임.
  => 조작 멤버변수가 줄어듦.
6) Down-casting (강제 형변환) : 업캐스팅된 클래스를 다시 원래의 타입으로 형변환.
  - 하위 클래스로의 형변환은 명시적으로 강제 형변환을 해야 함. 부모 타입으로 한번 형 변환이 된 자식 객체만 강제 타입 변환을 사용할 수 있음.

5. 참조변수의 형변환을 하는 이유는?
- 참조변수(쉽게 말하면 리모콘)을 변경함으로써 사용할 수 있는 멤버의 개수를 조절하기 위해서.

6. 다형성의 장점
1) 다형적 매개변수
  - 보통 메서드를 호출할 때는 메서드 선언부에서 지정한 데이터 타입과 일치하는 매개값을 전달하여 호출하지만, 매개 변수에 다형성을 적용하면 자식 객체를 대입하는 것도 허용됨. (메서드 하나로 상속에 포함되는 모든 클래스들 사용가능)
  - 참조타입 매개변수는 메서드 호출시, 자신과 같은 타입이거나 또는 자손타입의 주소의 인스턴스를 넘겨줌.

만약 다형성이 없었다면 클래스별 메서드를 객체별로 하나 하나 만들었어야 함. 하지만 다형성으로 인해 조상타입 하나로 자식 객체들까지 다 접근 가능.
  => 조상 타입의 참조변수 p에 Tc, Computer, Audio 등 자식 객체들이 들어올 수 있어서,
  => 메서드 여러개 만들 필요 없이 메서드 하나로 모든 물건 기능 제어 가능함.

Product -> 조상 클래스
Tv, Computer, Audio -> 자식 클래스

// 다형성이 없었다면 클래스별 메서드 하나 하나 만들었어야 함.
void buy(Tv t) {
    money -= t.price;
    bonusPoint += t.bonusPoint;
}
void buy(Computer c) {
    money -= c.price;
    bonusPoint += c.bonusPoint;
}
....등등

// 하지만 다형성으로 인해 조상타입 하나로 자식 클래스들까지 다 접근 가능.
// 조상 타입의 참조변수 p에 Tc, Computer, Audio 등 자식 객체들이 들어올 수 있어서,
// 메서드 여러개 만들 필요 없이 메서드 하나로 모든 물건 기능 제어 가능함.
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();

void buy(Product p) {
    money -= p.price;
    bonusPoint += p.bonusPoint;
}


2) 하나의 배열로 여러종류의 객체 다루기
  - 조상 타입의 배열에 자손들의 객체를 담을 수 있음.

// 여러 종류의 객체를 배열로 다루기.
// 조상 타입의 배열에 자손들의 객체를 담을 수 있음.
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();

        ||
        
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

 

7. 객체 타입 확인하는 instanceof 연산자
1) 부모 타입이면 모두 자식 타입으로 강제 타입 변환할 수 있는 것이 아님. (ClassCastException 예외 발생 가능성)
2) 먼저 자식 타입인지 확인 후 강제 타입을 해야 함.
3) instanceof 연산자는 참조변수가 참조하는 인스턴스의 실제 타입을 체크하는데 사용함. (형변환 가능여부 확인)
  - boolean result = 좌항(객체) instanceof 우항(타입)
  - 좌항의 객체가 우항의 타입으로 만들어졌는지, 원래 인스턴스의 형이 맞는지 여부를 체크해서 참이면 true, 거짓이면 false 반환.

=> 형변환을 하는 이유는 인스턴스의 원래 기능을 모두 사용하기 위해서. Car 타입의 리모콘인 c 로는 Engine 클래스의 메서드인 run()을 호출할 수 없기 때문. 그래서 리모콘을 Engine 타입으로 바꿔서 run()을 호출함.

public void work(Car c) {
	if(c instanceof Engine) {  // 1. 형변환이 가능한지 확인 후,
          Engine e = (Engine)c;    // 2. 형 변환
          e.run();

 

 

 

참고 : [한빛미디어] 이것이 자바다 (신용권의 Java 프로그래밍 정복) Chapter 7.상속
참고 : [도우출판] JAVA의 정석(3ND EDITION)-자바의 정석 최신 Java 8.0 포함 Chapter 7.상속

'JAVA' 카테고리의 다른 글

JAVA - Stream (스트림)  (0) 2022.10.29
Java - Arrays 클래스  (0) 2022.10.29
Java - 제네릭스 (Generics)  (0) 2022.10.29
예외 처리 (Exception)  (1) 2022.09.10
오버로딩(Overloading), 오버라이딩(Overriding)  (0) 2022.09.09

댓글