Java

객체(Object) - 다형성(polymorphism)

제주니어 2022. 9. 23. 22:46

다형성(polymorphism)

다형성이란?

  • 조상 클래스 타입의 참조변수자손클래스의 인스턴스를 참조할 수 있도록 한다.
  • 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
class Tv {
		boolean power;
		int channel;

		void power () { /* 내용생략*/  }
		void channelUp () { /* 내용생략*/ }
		void channelDown () { /* 내용생략*/ }
}

class CaptionTv extends Tv {
		String text;
		void caption() { /* 내용생략*/ }
}
  • 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다. → [자바의 정석 p355]
  • Tv t = new CaptionTv ();
  • CaptionTv c = new CaptionTv ();

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

  • 참조변수 = 리모콘, 인스턴스 = 기능 → 리모콘이 가진 기능이 더 크면 작동이 안된다고 생각하기 !

참조변수의 형변환

자손타입 → 조상타입 (UpCasting) : 형변환 생략 가능

  • 부모 타입으로 업 캐스팅된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
  • 단, 예외가 있는데 부모 타입의 메소드가 오버라이딩되었다면 오버라이딩된 메소드가 대신 호출된다.

자손타입 ← 조상타입(DownCasting) : 형변환 생략 불가

  • 부모 클래스 타입 참조 변수가 실제로 참조하는 객체를 확인하지 않고 강제 형 변환을 시도하면 ClassCastException 예외가 발생할 수 있다.
  • 객체가 어떤 클래스의 인스턴스인지 instanceof 연산자를 사용해서 확인할 수 있다.

 

  • 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 영향 X
  • 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것이다.
// 1. 부모 타입의 참조 변수로 부모 객체를 다루는 경우
		System.out.println("1. 부모 타입의 참조 변수로 부모 객체를 다루는 경우");
		Product product = new Product(); // OK
		System.out.println();
		
		// product 참조 변수로 Product의 멤버만 접근 가능.
		System.out.println("<product 참조 변수로 Product의 멤버만 접근 가능>");
		System.out.println(product);
		System.out.println();
	
		// 2. 자식 타입의 참조 변수로 자식 객체를 다루는 경우
		Desktop desktop = new Desktop();
		System.out.println();
		
		// desktop 참조 변수로 Product(부모), Desktop(자식)의 멤버에 접근 가능
		System.out.println("<desktop 참조 변수로 Product(부모), Desktop(자식)의 멤버에 접근 가능>");
		System.out.println(desktop);
		System.out.println(desktop.isAllInone()); // Desktop extends Product (상속)
		System.out.println();
		
		// 3. 부모 타입의 참조 변수로 자식 객체를 다루는 경우(다형성 적용)
		System.out.println("3. 부모 타입의 참조 변수로 자식 객체를 다루는 경우(다형성 적용)");
		product = /* (Product) */ new Desktop(); // 자동 형 변환 // product는 위에서 변수 선언했으므로 그대로 사용.
		
		// product 참조 변수로 Product의 멤버만 접근 가능
		// 하지만 Desktop의 멤버에 접근하고 싶을 때는 형 변환을 해야 한다. 
		System.out.println(product); // 부모 타입의 참조 변수이기 때문에 자식 객체의 메서드는 사용할 수 없고 부모 타입의 메서드만 사용할 수 있다.
		System.out.println(product.toString()); 
		//└오버라이딩 -> Product, DeskTop 둘다 toStrig이 있지만 Desktop에 있는 toString이 실행됨, 동적 바인딩 때문에 가능.
		System.out.println(((Desktop)product).isAllInone()); // 강제 형 변환 -> Desktop에 있는 메서드 사용 가능
		System.out.println();

바인딩

  • 실제 실행할 메소드 코드와 호출하는 코드를 연결시키는 것을 바인딩이라 한다.
  • 프로그램이 실행되기 전에(컴파일 시점) 바인딩이 일어날 경우 정적 바인딩이라 한다.
  • 정적 바인딩오버라이딩이 불가능한 메소드에서 발생한다. (static, private, final 메소드)
  • 프로그램이 실행되면서 객체 타입을 기준으로 바인딩 되는 것을 동적 바인딩이라 한다.
  • 동적 바인딩은 다형성을 가능하게 만들어주는 핵심적인 개념으로 오버라이딩된 메소드에서 발생한다.

instaceof연산자

  • 참조변수의 형변환 가능여부 확인에 사용하며 가능하면 true 반환.
  • 참조변수 instanceof 타입(클래스명)
System.out.println("<instanceof 연산자>");
		for(Product p : arr3) {
			/*
			 * instanceof 연산자
			 *   - 참조 변수가 실제로 어떤 클래스 타입의 객체의 주소를 참조하는지 확인할 때 사용.
			 */
//			System.out.println(((Desktop)p).isAllInone()); // error
			if(p instanceof Desktop) {
				System.out.println(((Desktop)p).isAllInone());
			} else {
				System.out.println(((SmartPhone)p).getMobileAgency());
			}
			// 오버라이딩의 개념을 확용해서 실제로 참조하고 있는 객체의 메소드를 찾아서 실행한다. 
//			System.out.println(p.toString());
			System.out.println();
		}

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

  • 메서드 → 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우 참조 변수의 타입에 상관없이 오버라이딩 된 메서드가 호출되지만 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

  • static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. → 영향을 받지 않는 건 인스턴스메서드뿐이다.
  • static메서드는 참조변수가 아닌 클래스이름.메서드()로 호출해야 한다.
  • 자손 클래스에서 조상 클래스의 멤버를 중복으로 정의하지 않았을 때는 참조변수의 타입에 따른 변화는 없다.

매개변수의 다형성

  • 매개 변수를 조상 타입의 참조변수로 설정하면 메서드의 매개변수로 조상 클래스의 자손타입의 참조변수 어느 것이나 매개 변수로 받아들일 수 있다.

  • 위 예제는 고객(Buyer)이 buy(Product p)메서드를 이용해서 Tv와 Computer를 구입하고, 고객의 잔고와 보너스 점수를 출력하는 예제이다.
  • 매개변수가 Product 타입의 참조변수라는 것은 메서드의 매개변수로 Product클래스의 자손 타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻이다.
  • Product클래스의 price와 bonusPoint가 선언되어 있기 때문에 참조변수 p로 인스턴스의 price와 bonusPoint를 사용할 수 있다.
// 매개 변수의 다형성
		print(new Desktop());
		print(new SmartPhone());
	}

	// 비효율적
//	public static void print(Desktop desktop) { // static 메소드니까 호출하려면 static이여야 한다.-> 인스턴스 접근 불가
//		System.out.println(desktop);
//	}
//
//	public static void print(SmartPhone smartPhone) {
//		System.out.println(smartPhone);
//	} 

	public static void print(Product product) { // 하나의 메서드로 두 개의 타입을 받아서 처리 가능.
		System.out.println("<매개 변수의 다형성>");
		System.out.println(product);
	}

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

  • 조상 타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.

// 다형성을 사용하기 전에는 Desktop, SmartPhone 배열을 만들어야 한다.
		Desktop[] arr1 = new Desktop[2];
		
		arr1[0] = new Desktop();
		arr1[1] = new Desktop();
		
		SmartPhone[] arr2 = new SmartPhone[2];
		
		arr2[0] = new SmartPhone();
		arr2[1] = new SmartPhone();
		
		// 다형성을 적용하면 부모 클래스의 참조 변수로 자식 객체들을 가리킬 수 있다.
		Product[] arr3 = new Product[4];
		arr3[0] = new Desktop("a1111", "아이맥 24인치", "애플", 2000000, true);
		arr3[1] = new Desktop("d-01", "매직스테이션", "삼성", 1500000, false);
		arr3[2] = new SmartPhone("a2222", "아이폰 12 미니", "애플", 960000, "KT");
		arr3[3] = new SmartPhone("s-01", "갤럭시 22", "삼성",600000, "SKT" );