데이터융합 JAVA응용 SW개발자 기업 채용연계 연수과정 6일차 강의 정리

misung·2022년 3월 31일
0

08 상속과 다형성

08-1 상속이란?

물려받는 개념, B클래스가 A클래스를 상속받는 경우 A클래스의 멤버 변수와 메서드를 사용할 수 있음.

클래스의 상속

상속 문법

class B extends A {
	...
}

좀 더 이해하기 편한 예시로는 Mammal, 즉 포유류를 상위 개념의 클래스로 놓고 사람을 하위 클래스로 놓는다면..

class Mammal {
	...
}

class Human extends Mammal {
	...
}

상속을 사용하여 고객 관리 프로그램 구현하기

Customer 클래스는 고객 아이디, 고객 이름, 고객 등급, 보너스 포인트, 보너스 포인트 적립 비율을 멤버 변수로 갖는다.

Customer.java


public class Customer {
	protected int customerID;
	protected String customerName;
	protected String customerGrade;
	int bonusPoint;
	double bonusRatio;
	
	public Customer() {
		customerGrade = "SILVER";
		bonusRatio = 0.01;
	}
	
	public int getCustomerID() {
		return customerID;
	}
	
	public void setCustomerID(int customerID) {
		this.customerID = customerID;
	}
	
	public String getCustomerName() {
		return customerName;
	}
	
	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}

	public String getCustomerGrade() {
		return customerGrade;
	}
	
	public void setCustomerGrade(String customerGrade) {
		this.customerGrade = customerGrade;
	}
	
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price;
	}
	
	public String showCustomerInfo() {
		return customerName + "님의 등급은 " + customerGrade + 
				"이며, 보너스 포인트는 " + bonusPoint + "입니다.";		
	}
}

VIPCustomer.java

public class VIPCustomer extends Customer {
	private int agentID;
	double saleRatio;
	
	public VIPCustomer() {
		customerGrade = "VIP";
		bonusRatio = 0.05;
		saleRatio = 0.1;
	}
	
	public int getAgentID() {
		return agentID;
	}
}

CustomerTest.java

public class CustomerTest {

	public static void main(String[] args) {
		Customer customerKim = new Customer();
		customerKim.setCustomerID(100);
		customerKim.setCustomerName("김선생");
		customerKim.bonusPoint = 1000;
		System.out.println(customerKim.showCustomerInfo());
		
		VIPCustomer customerLee = new VIPCustomer();
		customerLee.setCustomerID(101);;
		customerLee.setCustomerName("이순신");
		customerLee.bonusPoint = 50000;
		System.out.println(customerLee.showCustomerInfo());
	}

}

Customer 클래스에서 하위 클래스가 몇 가지 속성을 상속받을 수 있도록 protected로 선언하고, 해당 멤버 변수를 접근할 수 있게 메서드를 작성하였다.

VIP 손님의 경우 VIPCustomer 클래스를 이용하여 생성하고, 적립률을 생성자에서 더 높게 설정했고, 기본 손님과는 다른 할인율과 상담원 ID가 추가되어 있다.

08-2 상속에서 클래스 생성과 형 변환

하위 클래스 생성 시에는 상위 클래스의 생성자가 먼저 호출됨.

하위 클래스가 생성되는 과정

VIPCustomer에는 Customer에 있는 멤버 변수를 직접 가지고 있지 않지만 참조할 수 있다. 즉, Customer의 멤버 변수는 메모리 공간 상의 어딘가에 존재하게 된다는 뜻이다.

VIPCustomer 인스턴스를 생성한다고 치면, Customer 클래스의 생성자가 먼저 호출 된 다음, VIPCustomer의 생성자가 호출되게 된다.

힙 메모리에 Customer 클래스의 멤버 변수가 Customer 클래스의 생성자 호출 후에 생성되고, 그 다음에 VIPCustomer의 생성자가 호출되면서 VIPCustomer 클래스의 멤버 변수가 등록된다.

부모를 부르는 예약어, super

this가 자기 자신의 참조 값을 알고 있는 것 처럼, super는 상위 클래스의 참조 값을 알고 있다.

하위 클래스에서 상위 클래스의 생성자가 자동으로 호출될 때 super();가 실행된다. 이는 사용자가 직접 추가하지 않아도 컴파일러가 자동으로 추가해준다.

super 예약어로 매개변수가 있는 생성자 호출

Customer() 클래스의 기본 생성자를 없애고, 새 생성자를 아래와 같이 추가한다.

public Customer(int customerID, String customerName) {
	this.customerID = customerID;
    this.customerName = customerName;
    customerGrade = "SILVER";
    bonusRatio = 0.01;
}

이렇게 한 경우 VIPCustomer 클래스의 생성자에서 오류가 난다. 왜냐하면 묵시적으로 호출될 상위 클래스의 기본 생성자가 없어졌기 때문이다.

따라서 명시적으로 아래와 같이 따로 호출을 해 줄 필요가 있다.

public VIPCustomer(int customerID, String customerName, int agentID) {
	super(customerID, customerName);
    ...
}

이렇게 해서 상위 클래스의 생성자를 호출한 다음 하위 클래스를 생성하게 할 수 있다.

상위 클래스로 묵시적 클래스 형 변환

Customer는 VIPCustomer보다 일반적인 개념이며 VIPCustomer는 Customer의 기능을 모두 가지고 있기도 하고, VIPCustomer는 VIPCustomer형이면서 동시에 Customer형이기도 하다.

Customer vc = new VIPCustomer();

이렇게 선언하는 경우에는 VIPCustomer()의 생성자가 호출되어 VIPCustomer의 멤버 변수까지 힙에 생성되지만, vc는 Customer형이므로 Customer클래스 내의 메서드나 멤버변수만 사용할 수 있다.

08-3 메서드 오버라이딩

상위 클래스 메서드 재정의하기

VIPCustomer 클래스에서는 할인율을 만들어 두었는데, 정작 사용은 않고 있었으며 상위 클래스인 Customer 클래스에는 calcPrice 메서드가 이미 만들어져 있다.

이를 VIPCustomer 클래스에서 다시 정의하고, 할인율이 적용되도록 만들어 보자.

public class VIPCustomer extends Customer {
	...
    @Override
    public int calcPrice(int price) {
    	bonusPoint += price * bonusRatio;
        return price - (int)(price * saleRatio);
    }
}

@Override는 컴파일러에게 재정의된 메서드라는 정보를 제공하는 것이다.

이렇게 메서드를 재정의 한 경우, 같은 가격의 물건을 Customer, VIPCustomer에서 각각 계산해보면 가격의 차이가 나게 계산되는 것을 확인할 수 있다.

묵시적 클래스 형 변환과 메서드 재정의

다음과 같은 Customer 형을 갖는 VIPCustomer 인스턴스를 생성한다고 하자.

Customer vc = new VIPCustomer("10000", "김선생", 2000);
vc.calcPrice(10000);

우리는 앞서 Customer와 VIPCustomer 모두 calcPrice() 함수를 만들어 두었다. 과연 calcPrice는 어느 것이 호출되는 것일까?

출력 :
... 9000

상속 관계에서 상위 클래스와 하위 클래스에 같은 이름의 메서드가 존재하는 경우, 호출되는 메서드는 인스턴스에 따라 결정된다.

그리고 이렇게 인스턴스의 메서드가 호출되는 기술을 가상 메서드라고 한다.

가상 메서드

변수는 인스턴스 생성 시 각각의 인스턴스에 맞게 새로이 생성되지만, 메서드의 경우에는 인스턴스가 달라도 실행 로직은 같으므로 여러 번 생성되지 않는다.

...
public class TestClass {
	int num;
	void func() {
		System.out.println("func");
	}

	public static void main(String[] args) {
		TestClass tc1 = new TestClass();
        TestClass tc2 = new TestClass();
        
        tc1.func();
        tc2.func();
	}
}

출력 :
func
func

각 인스턴스의 num의 경우엔 힙 메모리에 각각 따로 존재하게 되지만, 메서드 영역에는 func가 한 개만 존재하게 된다.

08-4 다형성

다형성이란?

다형성이란 하나의 코드가 여러 자료형으로 구현되어 실행됨을 의미한다.

다형성 테스트를 위한 예제로..

AnimalTest.java

package polymorphism;

class Animal {
	public void move() {
		System.out.println("동물이 움직인다.");
	}
}

class Human extends Animal {
	public void move() {
		System.out.println("사람이 두 발로 걷는다.");
	}
}

class Dog extends Animal {
	public void move() {
		System.out.println("개가 네 발로 달린다.");
	}
}

class Bird extends Animal {
	public void move() {
		System.out.println("새가 하늘을 난다.");
	}
}

public class AnimalTest {

	public static void main(String[] args) {
		AnimalTest animal = new AnimalTest();
		
		animal.moveAnimal(new Human());
		animal.moveAnimal(new Dog());
		animal.moveAnimal(new Bird());
	}
	
	public void moveAnimal(Animal animal) {
		animal.move();
	}

}

출력 :
사람이 두 발로 걷는다.
개가 네 발로 달린다.
새가 하늘을 난다.

animal.move() 의 코드는 변함없으나, 매개변수로 전달되는 인스턴스가 어떤 것이냐에 따라 출력문이 달라진다.

다형성의 장점

상위 클래스에서 공통 부분의 메서드를 제공하고, 하위 클래스에서 추가 요소를 덧붙이는 식으로 구현하면 코드 양이 줄어 유지보수가 편리해진다.

08-5 다형성 활용하기

일반 고객과 VIP 고객의 중간 등급 만들기

조건 :
제품을 살 때 항상 10% 할인
보너스 포인트를 2% 적립
담당 전문 상담원 없음

코드가 길어져서 gist로 첨부 : https://gist.github.com/crisine/7a0af060a0b83291af080d20b7c888f0

중간 등급인 Gold 회원 클래스를 추가하고 각 하위 클래스별로 메서드를 재정의한 다음, 메인 메서드가 포함된 클래스에서 ArrayList와 향상된 for문을 사용하여 고객에 대한 정보를 출력해 보았다.

상속은 언제 사용할까?

만약 상속 없이 Customer 클래스 한개로 관리한다고 치면 이런 코드가 나올 수 있다.

if(customerGrade == "VIP") {
	...
} else if (customerGrade == "Gold") {
	...
} else if (customerGrade == "SILVER) {
	...
}
...

이런 경우에 고객 등급이 추가 혹은 삭제되는 경우 유지보수가 매우 복잡해지므로 앞서 다형성으로 구현한 방식이 확장성 있고 유지보수하기 좋은 편이다.

항상 사용하는 것이 좋은가?

IS-A 관계일때만 하는 것이 좋다. HAS-A관계인 경우 상속을 하면 안 되는데, 학생과목을 포함하는 그런 경우가 특히 그렇다.

그러한 HAS-A 관계는 Student 클래스 내의 멤버변수로써 Subject를 포함하는 것이 옳다.

08-6 다운 캐스팅과 instanceof

하위 클래스로 형 변환, 다운 캐스팅

앞서 상위 클래스로 형 변환 되는 과정을 보았다면, 여기서는 하위 클래스로 형 변환이 되는 과정을 보겠다.

앞선 상속 관계에서 Animal ani = new Human(); 이라는 코드가 작성 가능한데, 이런 경우 ani 인스턴스는 Human의 메서드와 멤버 변수를 이용할 수 없다.

따라서 원래의 인스턴스 자료형으로 형 변환하기 위해 다운 캐스팅이 필요해진다.

instanceof

Animal ani = new Human();
if (ani instanceof Human) {
	Human human = (Human)ani;
}

만약 ani 인스턴스 자료형이 Human인 경우, 인스턴스 ani를 Human형으로 다운캐스팅 하는 과정을 거친다.

상기한 코드는 ani 인스턴스가 Human형으로 작성되었는지 확인하는 과정을 거치는 것이고, 그렇다고(true) 판단되면 Human human = (Human)ani;와 같이 형 변환 시 명시적으로 자료형을 써 주어야 한다.

package polymorphism;
import java.util.ArrayList;

class Animal {
	public void move() {
		System.out.println("동물이 움직인다.");
	}
}

class Human extends Animal {
	public void move() {
		System.out.println("사람이 두 발로 걷는다.");
	}
	
	public void coding() {
		System.out.println("사람이 코드를 짠다.");
	}
}

class Dog extends Animal {
	public void move () {
		System.out.println("개가 네 발로 달린다.");
	}
	
	public void bark() {
		System.out.println("개가 짖는다.");
	}
}

class Bird extends Animal {
	public void move () {
		System.out.println("새가 하늘을 난다.");
	}
	
	public void sing() {
		System.out.println("새가 지저귄다.");
	}
}

public class AnimalTest {
	ArrayList<Animal> aniList = new ArrayList<Animal>();
	public static void main(String[] args) {
		AnimalTest aTest = new AnimalTest();
		aTest.addAnimal();
		System.out.println("원래 형으로 다운캐스팅");
		aTest.testCasting();
	}

	public void addAnimal() {
		aniList.add(new Human());
		aniList.add(new Dog());
		aniList.add(new Bird());
		
		for (Animal ani : aniList) {
			ani.move();
		}
	}
	
	public void testCasting() {
		for (int i = 0; i < aniList.size(); i++) {
			Animal ani = aniList.get(i);
			if (ani instanceof Human) {
				Human h = (Human)ani;
				h.coding();
			} else if (ani instanceof Dog) {
				Dog d = (Dog)ani;
				d.bark();
			} else if (ani instanceof Bird) {
				Bird b = (Bird)ani;
				b.sing();
			} else {
				System.out.println("지원되지 않는 형입니다.");
			}
		}
	}
}

출력 :
사람이 두 발로 걷는다.
개가 네 발로 달린다.
새가 하늘을 난다.
원래 형으로 다운캐스팅
사람이 코드를 짠다.
개가 짖는다.
새가 지저귄다.

향상된 for문에서 각 인스턴스의 move를 호출할 땐, 재정의된 메서드가 호출된다. 그러나 ArrayList의 타입이 Animal이므로, 각 클래스에서 따로 제공하는coding(), bark(), sing() 등을 호출할 수가 없다.

따라서 각 메서드를 호출하기 위해서는 다시 원래 자료형으로 다운캐스팅을 하여야 한다.

0개의 댓글