본문 바로가기
Java/Java

다형성) 다형성의 활용(매개변수 선언부, 리턴 타입), 동적 바인딩, 정적 바인딩

by 박채니 2022. 3. 24.
SMALL

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


☞ 다형성의 활용 1) 매개변수 선언부

 

Animal 부모 클래스

public class Animal {
	public void say() {
		System.out.println("안녕하세요, 동물 입니다.");
	}
}

Lion 자식 클래스

public class Lion extends Animal {
	public void punch() {
		System.out.println("라이언 펀치!");
	}
}

Tiger 자식 클래스

public class Tiger extends Animal {
	public void kick() {
		System.out.println("타이거 킥!");
	}
}

각 punch()메소드와 kick()메소드를 action() 메소드를 통해 호출하고자 했을 때 아래와 같이 처리할 수 있습니다.

 

메인 메소드 (다형성 활용 전)

public void test3() {
	Lion lion = new Lion();
	Tiger tiger = new Tiger();
		
	action(lion);
	action(tiger);
}
	
public void action(Lion lion) {
	lion.punch();
}
public void action(Tiger tiger) {
	tiger.kick();
}

@콘솔출력값
라이언 펀치!
타이거 킥!

만일 다형성을 활용하지 않으면 메소드 오버로딩을 이용하여 각 타입에 맞는 메소드를 호출 했을 것입니다.

공통되는 메소드가 여러개 있다면, 추후 코드가 방대하게 많아졌을 때 헷갈리기 딱 좋습니다..

 

이러한 문제점을 상속 되어 있는 클래스들이라면 다형성을 활용해서 해결 할 수 있겠죠.

 

다형성 활용 후

public void test3() {
	Lion lion = new Lion();
	Tiger tiger = new Tiger();
		
	action(lion);
	action(tiger);
}

public void action(Animal animal) {
	if(animal instanceof Lion) 
		((Lion)animal).punch();
	else if(animal instanceof Tiger)
		((Tiger)animal).kick();
}

@콘솔출력값
라이언 펀치!
타이거 킥!

action()메소드의 매개변수를 Animal 타입으로 받아 처리할 수 있습니다.

 

매개 변수부에 넘어온 매개인자가 담기는 것,

따라서 Animal animal = lion; 와 Animal animal = tiger; 이 눈에 보이진 않지만 실행 되는 것입니다.

업 캐스팅이 자동 수행 되므로 문제 없는 코드죠! 

 

그 후 내부적으로 instanceof 키워드를 이용하여 Lion으로 캐스팅이 가능하다면 animal을 다운 캐스팅하여 punch()호출,

Tiger로 캐스팅이 가능하다면 마찬가지로 animal을 다운 캐스팅하여 kick()을 호출해주는 것입니다.

 

그렇다면 만약 매개변수부가 Lion 타입인 오버로딩된 action메소드가 있다면 어떻게 될까요?

public void test3() {
	Lion lion = new Lion();
	Tiger tiger = new Tiger();
		
	action(lion);
	action(tiger);
}
	
public void action(Lion lion) {
	lion.punch();
}

public void action(Animal animal) {
	if(animal instanceof Lion) 
		((Lion)animal).punch();
	else if(animal instanceof Tiger)
		((Tiger)animal).kick();
}

@콘솔출력값
라이언 펀치!
타이거 킥!

action(lion); 은 Lion 타입의 action()메소드도 Animal 타입의 action()메소드도 실행할 수 있습니다.

이럴 땐 매개 변수 타입이 정확히 일치하거나, 가까운 타입이 우선 호출됩니다.

 

따라서 action(lion)은 고민할 것도 없이 Lion 타입의 action()메소드를 실행하게 됩니다.

 

☞ 다형성의 활용 2) 리턴 타입

public void test4() {
	Animal animal = animalGenerator();
	System.out.println(animal);
}
	
public Animal animalGenerator() {
	Random rnd = new Random();
	if(rnd.nextBoolean()) 
		return new Lion();
	else 
		return new Tiger();
}

@콘솔출력값
com.ce.java.polymorphism.Lion@5d22bbb7
...
com.ce.java.polymorphism.Tiger@5d22bbb7

랜덤으로 true, false를 리턴하여 true면 Lion 객체를, false면 Tiger 객체를 리턴하여 Animal 타입의 animal 참조 변수에 담았습니다.

그에 대한 Lion 혹은 Tiger 객체 정보가 출력되는 것을 확인할 수 있습니다.

 

☞ 정적바인딩, 동적바인딩

 

정적 바인딩

- 컴파일 타임에서 메소드를 실행할 객체 타입 기준으로 바인딩

public void test5() {
	Animal[] animals = new Animal[2];
	animals[0] = animalGenerator();
	animals[1] = animalGenerator();
		
	for(Animal animal : animals) {
		animal.say();
	}
}

	
public Animal animalGenerator() {
	Random rnd = new Random();
	if(rnd.nextBoolean()) 
		return new Lion();
	else 
		return new Tiger();
}

@콘솔출력값
안녕하세요, 동물 입니다.
안녕하세요, 동물 입니다.

실제 객체 타입 기준 (Animal)의 say()가 출력되어 "안녕하세요, 동물 입니다."가 출력 되었습니다.

 

동적 바인딩

- 다형성이 적용된 상태에서 오버라이드된 메소드를 호출하는 경우, 자식 타입의 메소드가 바인딩

//Lion 클래스
public class Lion extends Animal {
	@Override
	public void say() {
		System.out.println("안녕하세요, 라이언입니다.");
	}
	public void punch() {
		System.out.println("라이언 펀치!");
	}
}

//Tiger 클래스
public class Tiger extends Animal {
	@Override
	public void say() {
		System.out.println("안녕하세요, 타이거입니다.");
	}
	
	public void kick() {
		System.out.println("타이거 킥!");
	}
}

자식 클래스에 say()메소드를 오버라이딩하였습니다.

public void test5() {
	Animal[] animals = new Animal[2];
	animals[0] = animalGenerator();
	animals[1] = animalGenerator();
		
	for(Animal animal : animals) {
		animal.say();
	}
}

	
public Animal animalGenerator() {
	Random rnd = new Random();
	if(rnd.nextBoolean()) 
		return new Lion();
	else 
		return new Tiger();
}

@콘솔출력값
안녕하세요, 라이언입니다.
안녕하세요, 타이거입니다.

그 후 동일한 코드를 실행해보니, 각 객체의 say()를 출력할 때 자식 클래스들의 say()가 출력 되는 것을 확인할 수 있었습니다.

오버라이딩에 대한 메모리 구조를 알고 있다면 사실 당연한 이야기긴 합니다.

 

부모 객체가 먼저 생성된 후 자식 객체가 만들어지기 때문에, 메소드 영역에 부모의 say()메소드가 먼저 생성 됩니다.

그 후 자식 객체가 생성되는데, 자식 객체가 생성되면서 오버라이딩된 say()메소드를 메소드 영역에 생성하려고 가니 이미 부모의 say()메소드가 생성 되어 있어 부모 메소드를 '덮어쓰게' 되어 자식의 say()메소드가 '오버라이딩' 되는 것이죠.

따라서 animal.say()를 하니 자식 클래스의 say()로 오버라이딩 된 메소드가 호출 되는 것입니다.

 

동적 바인딩 활용 예

Computer 클래스

public class SmartPhone extends Computer {
	private String carrier;
	
	public SmartPhone() {}
	public SmartPhone(String brand, String productCode, String productName, int price, String os, String carrier) {
		super(brand, productCode, productName, price, os);
		this.carrier = carrier;
	}
	
	//get,set생략
	
	@Override
	public String toString() {
		return super.toString() + carrier;
	}
	
	public String getSmartPhoneInfo() {
		return "SmartPhone [" + super.getBrand() + ", " + super.getProductCode() + ", " + super.getProductName() + ", " + super.getPrice() + ", "
				+ super.getOs() + ", " + carrier + "]";	
	}
}

Desktop 클래스

public class Desktop extends Computer {
	private String monitor;
	private String keyboard;
	private String mouse;
	
	public Desktop() {}
	public Desktop(String brand, String productCode, String productName, int price, String os, String monitor, String keyboard, String mouse) {
		super(brand, productCode, productName, price, os);
		this.monitor = monitor;
		this.keyboard = keyboard;
		this.mouse = mouse;
	}
	
	//get,set 생략
	
	@Override
	public String toString() {
		return super.toString() + monitor + ", " + keyboard + ", " + mouse;
	}
	
	public String getDesktopInfo() {
		return "Desktop [" + super.getBrand() + ", " + super.getProductCode() + ", " + super.getProductName() + ", " + super.getPrice() + ", "
				+ super.getOs() + ", " + monitor + ", " + keyboard + ", " + mouse + "]";	
	}
}

Tv 클래스

public class Tv extends Product {
	private String resolution;
	private int size;
	
	public Tv() {}
	public Tv(String brand, String productCode, String productName, int price, String resolution, int size) {
		super(brand, productCode, productName, price);
		this.resolution = resolution;
		this.size = size;
	}
	
    //get,set생략
	
	@Override
	public String toString() {
		return super.toString() + ", " + resolution + ", " + size;
	}
	
	public String getTvInfo() {
		return "Tv [" + super.getBrand() + ", " + super.getProductCode() + ", " + super.getProductName() + ", " + super.getPrice() + ", "
				+ resolution + ", " + size + "]";	
	}
}

부모 클래스 Product

public class Product {
	private String brand;
	private String productCode;
	private String productName;
	private int price;
	
	public Product() {}
	public Product(String brand, String productCode, String productName, int price) {
		this.brand = brand;
		this.productCode = productCode;
		this.productName = productName;
		this.price = price;
	}
	
	//get,set생략
	
	@Override
	public String toString() {
		return brand + ", " + productCode + ", " + productName + ", " + price;
	}
}

부모 클래스 Computer

public class Computer extends Product {
	private String os;
	
	public Computer() {}
	public Computer(String brand, String productCode, String productName, int price, String os) {
		super(brand, productCode, productName, price);
		this.os = os;
	}
	
	//get,set생략
	
	@Override
	public String toString() {
		return super.toString() + ", " + os + ", ";
	}
}

메인 메소드

public class ProductMain_ {
	Product[] products = new Product[10];
	
	public static void main(String[] args) {
		ProductMain_ main = new ProductMain_();
		main.test1();
		main.test2();
		main.test3();
	}
	
	public void test1() {
		Desktop desktop = new Desktop("삼성", "samsung-1234", "삼성점보데스크탑", 1_000_000, "Windows11", "좋은모니터", "좋은키보드", "좋은마우스");
		SmartPhone smartPhone = new SmartPhone("아이폰", "iphone-11", "아이폰11프로", 1_500_000, "애플", "SKT");
		Tv tv = new Tv("LG", "lg-9898", "울트라와이드샤프TV", 5_000_000, "UHD", 80);
		
		products[0] = desktop;
		products[1] = smartPhone;
		products[2] = tv;
	}
	
	//요소별 info 메소드 출력
	public void test2() {
		for(int i = 0; i < products.length; i++) {
			if(products[i] instanceof Desktop)
				System.out.println(((Desktop)products[i]).getDesktopInfo());
			else if(products[i] instanceof SmartPhone)
				System.out.println(((SmartPhone)products[i]).getSmartPhoneInfo());
			else if (products[i] instanceof Tv)
				System.out.println(((Tv)products[i]).getTvInfo());
		}
	}
    
	//toString이 오버라이드 되어 있을 떄, products의 각 요소 별 toString메소드를 호출하세요.
	//동적바인딩
	public void test3() {
		for(Product product : products) {
			if(product != null)
				System.out.println(product.toString());
		}
	}
}

@콘솔출력값
Desktop [삼성, samsung-1234, 삼성점보데스크탑, 1000000, Windows11, 좋은모니터, 좋은키보드, 좋은마우스]
SmartPhone [아이폰, iphone-11, 아이폰11프로, 1500000, 애플, SKT]
Tv [LG, lg-9898, 울트라와이드샤프TV, 5000000, UHD, 80]
삼성, samsung-1234, 삼성점보데스크탑, 1000000, Windows11, 좋은모니터, 좋은키보드, 좋은마우스
아이폰, iphone-11, 아이폰11프로, 1500000, 애플, SKT
LG, lg-9898, 울트라와이드샤프TV, 5000000, UHD, 80
LIST