TIL/Java

[Java] 객체지향 - 1. 다형성, 동적바인딩, instanceof 연산자, 클래스 형변환

yndev 2022. 1. 13. 23:17

다형성이란?

하나의 인스턴스가 여러가지 타입을 가질 수 있는 것을 의미한다.

다형성은 상속을 기반으로 한 기술이다.

하나의 타입으로 여러 타입의 인스턴스를 처리할 수 있고,

하나의 메소드 호출로 객체별로 각기 다른 방법으로 동작하게할 수 있다.

 

1. Parent p = new Parent(); (O)

2. Child c = new Child(); (O)

3. Parent p2 = new Child(); (O)   ->  다형성 기술이 적용되어서 가능함

4. Child c2 = new Parent(); (X)   ->   Child클래스가 Parent클래스를 다룰 수 없음 

Parent[] arr = new parent[3];

arr[0] = new Child1();

arr[1] = new Child2();

arr[2] = new Child3();

 

다형성의 특징

- 여러 타입의 객체를 하나의 타입으로 관리할 수 있으므로 유지보수성생산성이 증가됨

- 상속 관계에 있는 모든 객체는 동일한 메소드(메시지)를 수신할 수 있음 (역할)

- 확장성이 좋은 코드 작성 가능

- 결합도를 낮춰 유지보수성을 증가시킬 수 있음

 

동적바인딩

- 컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가 런타임 당시 실제 객체가 가진 오버라이딩된 메소드로 바인딩이 바뀌어 동작하는 것을 의미

- 동적바인딩 성립 조건 : 상속 관계로 이루어져 다형성이 적용된 경우. 메소드 오버라이딩이 되어 있어야 함

실제적으로 Child의 methodA를 출력함

Instanceof 연산자

현재 레퍼런스 변수가 어떤 클래스 타입의 객체 주소를 참조하고 있는지를 확인할 때 사용

클래스 타입이 맞으면 true, 맞지 않으면 false를 반환

If(레퍼런스) instanceof 클래스타입) {
   //true일때 처리할 내용. 해당 클래스 타입으로 down casting
}

 

클래스 형변환

- 업캐스팅(up-casting) : 상위(부모) 타입으로 형변환

 ex) Car c = (Car)New Sonata();

- 다운캐스팅(down-casting) : 하위(자식) 타입으로 형변환

 ex) ((Sonata)c).moveSonata();

- 묵시적 형변환 : up-casting의 경우 묵시적 형변환이 적용됨 (자동 형변환)

 ex) Car c = new Sonata();


위의 개념들을 코드로 적용

Animal Class

public class Animal {

	/* 동물은 기본적으로 먹는 행동과 뛰는 행동, 우는 행동을 할 수 있다. */
	
	public void eat() {
		System.out.println("동물이 먹이를 먹습니다.");
	}
	
	public void run() {
		System.out.println("동물이 달려갑니다.");
	}
	
	public void cry() {
		System.out.println("동물이 울음 소리를 냅니다.");
	}

Animal 클래스에 상속된 Rabbit 클래스

public class Rabbit extends Animal{

	/* 동물이 하는 행동을 조금 구체화하여 행동할 수 있도록
	 * Rabbit 클래스에 Animal 클래스의 메소드를 오버라이딩 한다.
	 * */
	
	@Override
	public void eat() {
		System.out.println("토끼가 풀을 뜯어먹고 있습니다.");
	}
	
	@Override
	public void run() {
		System.out.println("토끼가 달려갑니다. 깡총 깡총~");
	}
	
	@Override
	public void cry() {
		System.out.println("토끼가 울음소리를 냅니다. 끼익 끼익~");
	}
	
	/* 추가적으로 토끼는 점프를 뛸 수 있다. */
	public void jump() {
		System.out.println("토끼가 점프합니다. 점프!!");
	}

Animal 클래스에 상속된 Tiger 클래스

public class Tiger extends Animal{

	@Override
	public void eat() {
		System.out.println("호랑이가 고기를 뜯어먹고 있습니다.");
	}
	
	@Override
	public void run() {
		System.out.println("호랑이는 웬만해서 달리지 않습니다. 어슬렁 어슬렁~ 걸어갑니다.");
	}
	
	@Override
	public void cry() {
		System.out.println("호랑이가 울부짖습니다. 어흐으응~!!");
	}
	
	/* 호랑이는 추가적으로 물어뜯기를 할 수 있다. */
	public void bite() {
		System.out.println("호랑이가 물어 뜯습니다. 앙~");
	}

Application(출력) 클래스

public class Application {

	public static void main(String[] args) {

		System.out.println("Animal 생성 ------");
		
		Animal animal = new Animal();
		animal.eat();
		animal.run();
		animal.cry();
		
		System.out.println("Rabbit 생성 ------");
		
		Rabbit rabbit = new Rabbit();
		rabbit.eat();
		rabbit.run();
		rabbit.cry();
		rabbit.jump();
		
		System.out.println("Tiger 생성 ------");
		
		Tiger tiger = new Tiger();
		tiger.eat();
		tiger.run();
		tiger.cry();
		tiger.bite();
	
		/* Rabbit은 Rabbit 타입이기도 하면서 Animal 타입이기도 함 
		 * Tiger는 Tiger 타입이기도 하면서 Animal 타입이기도 함
		 * */
		
		Animal animal1 = new Rabbit();
		Animal animal2 = new Tiger();
		
		/* 하지만 반대로 Animal은 Animal일뿐 Rabbit, Tiger가 될 수 없다. 
		 * 그래서 반대로 작성할 시 에러 발생한다. 
		 * */
		//Rabbit r = new Animal();
		//Tiger t = new Animal();

출력 클래스에서 동적 바인딩 확인

		System.out.println("동적바인딩 확인 ------");
		/* 컴파일 당시에는 해당 타입의 메소드와 연결되어 있다가, 
		 * 런타임 당시 실제 객체가 가진 오버라이딩 된 메소드로 바인딩이 바뀌어 동작한다.
		 * */
		animal1.cry();
		
		/* 하나의 메소드 호출로 각기 다른 객체의 다른 메소드를 동작시키게 한다. */
		
		//animal1.jump();
		//animal2.bite();

 

위의 코드에서 현재 레퍼런스 변수 타입은 Animal 이기 때문에

Rabbit과 Tiger이 가지고 있는 고유한 기능(jump, bite)고유한 기능을 동작시키지 못한다. 

이러한 문제점을 해결하기 위해 클래스타입 형변환을 확인 해보자.

		System.out.println("클래스타입 형변환 확인 ------");
		
		((Rabbit)animal1).jump();
		((Tiger)animal2).bite();
        
        	/* 타입 형변환을 잘못 하는 경우
		 * 컴파일 시에는 문제가 되진 않지만 런타임시 Exception이 발생한다. 
		 * */
		//((Tiger)animal1).bite();	//토끼 인스턴스는 호랑이가 될 수 없다.

 

객체별로 고유한 기능을 동작시키기 위해서는 레퍼런스 변수를 형변환하여 Rabbit과 Tiger로 변경해야 메소드 호출이 가능하다.
타입 형변환 시 실제 인스턴스와 타입이 일치하지 않는 경우에는 ClassCastException이 발생할 수 있다.


레퍼런스 변수가 참조하는 실제 인스턴스가 원하는 타입과 맞는지 비교하는 연산자 instanceof

System.out.println("ionstanceof 확인 ------");
System.out.println("animal1이 Tiger 타입인지 확인 : " + (animal1 instanceof Tiger));		//false
System.out.println("animal1이 Rabbit  타입인지 확인 : " + (animal1 instanceof Rabbit));		//true
System.out.println("animal2이 Tiger 타입인지 확인 : " + (animal2 instanceof Tiger));		//false
System.out.println("animal2이 Rabbit  타입인지 확인 : " + (animal2 instanceof Rabbit));		//true

/* 상속 받은 타입도 함께 가지고 있다(다형성) */
System.out.println("animal1이 Animal 타입인지 확인 : " + (animal1 instanceof Animal));		//true
System.out.println("animal2이 Animal 타입인지 확인 : " + (animal2 instanceof Animal));		//true

/* 모든 클래스는 Object의 후손이다. */
System.out.println("animal1이 Object 타입인지 확인 : " + (animal1 instanceof Object));		//true
System.out.println("animal2이 Object 타입인지 확인 : " + (animal2 instanceof Object));		//true