본문 바로가기
프로그래밍 ( Programming )/JAVA

[JAVA] 상속에서 클래스 생성과 형 변환

by Jayce_choi 2021. 6. 26.
반응형

하위 클래스가 생성될 때는 항상 상위 클래스의 생성자가 먼저 호출되게 됩니다. 상속 관계에서 클래스의 생성 과정을 살펴보면 하위 클래스가 상위 클래스의 변수와 메서드를 사용할 수 있는 이유와 하위 클래스가 상위 클래스의 자료형으로 형 변환을 할 수 있는 이유를 이해할 수 있습니다.

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

상속을 받은 하위 클래스는 상위 클래스의 변수와 메서드를 사용 가능합니다. 변수를 사용할수있다는 것은 그 변수를 저장하고 있는 메모리가 존재한다는 뜻입니다. 그러나 이전에 적었던 VIPCustomer 클래스의 코드를 보면 해당 변수가 존재하지 않습니다. Customer 클래스를 상속받았을 뿐입니다. 여기서 우리는 상속된 하위 클래스가 생성되는 과정을 다시 생각해볼 필요가 있습니다.

테스트를 위해서 이전에 사용했던 코드에 출력문만 추가해보겠습니다.

package inheritance;

public class Customer {
	protected int customerID;
	protected String customerName;
	protected String customerGrade;
	int bonusPoint;
	double bonusRatio;
	
	public Customer() {
		customerGrade = "SILVER";
		bonusRatio = 0.01;
	  System.out.println("Customer() 생성자 호출 "); // 추가 코드
	} // 디폴트 생성자
	
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price;
	} // 보너스 포인트 적립, 지불 가격 계산 메서드 
	
	public String getCustomerInfo() {
		return customerName = "님의 등급은 " + customerGrade + "이며, 보너스 포인트는" + bonusPoint + "입니다.";
	} // 고객 정보를 반환하는 메서드

	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;
	}
	
}
package inheritance;

public class VIPCustomer extends Customer {
	private int agentID;
	double saleRatio;
	
	public VIPCustomer() {
		customerGrade = "VIP";
		bonusRatio = 0.05;
		saleRatio = 0.1;
		System.out.println("VIPCustomer() 생성자 호출"); // 추가 코드
	}
	public int getAgentID() {
		return agentID;
	}
}
package inheritance;

public class CustomerTest2 {

	public static void main(String[] args) {
		VIPCustomer customerKim = new VIPCustomer();
		customerKim.setCustomerID(10020);
		customerKim.setCustomerName("김유신");
		customerKim.bonusPoint = 10000;
		System.out.println(customerKim.getCustomerInfo());
	}
}

해당 코드를 실행하면 다음과 같이 출력되게 됩니다.

결과를 해석하였을때 Customer라는 생성자가 먼저 호출되고 그다음에 VIPCustomer()가 호출되는 것을 알 수가 있습니다. 정리하면 상위 클래스를 상속받은 하위 클래스가 생성될 시 반드시 상위 클래스의 생성자가 먼저 호출되게 됩니다. 그리고 상위 클래스 생성자가 호출될 때는 상위 클래스의 멤버 변수가 메모리에 생성되는 것이지요. 하위 클래스 VIPCustomer가 생성될 때 메모리 구조는 다음과 같이 표현됩니다.

* 여기서 주의할점은 private로 상위 클래스에서 변수를 선언했던 경우 하위 클래스에서 해당 변수를 사용할 수 없었던 것은 상위 클래스의 변수가 생성되지 않았기 때문이 아니라 단지 하위 클래스에서 접근이 불가능한 것입니다.

 

Super 예약어 ( 부모를 부르는 예약어 )

Super 예약어는 하위 클래스에서 상위 클래스로 이동할 때 사용합니다. 하위 클래스는 상위 클래스의 주소, 즉 참조 값을 알고 있습니다. 해당 참조값을 가지고 있는 예약어가 바로 super입니다.

this가 자기 자기 자신의 참조 값을 가지고 있는 것과 같다고 생각하면 됩니다. 또한 super는 상위 클래스의 생성자를 호출하는데도 사용됩니다.

위에서 사용했던 VIPCustomer의 코드에는 사실 컴파일을 할때 다음과 같이 자동으로 추가되는 코드 한 줄이 있습니다.

	public VIPCustomer() {
		super(); // 컴파일러가 자동으로 추가하는 코드. 상위 클래스의 Customer()가 호출됨. 

		customerGrade = "VIP";
		bonusRatio = 0.05;
		saleRatio = 0.1;
		System.out.println("VIPCustomer() 생성자 호출"); // 추가 코드
	}

super()라는 코드가 자동으로 생성되기 때문에 상위 클래스를 먼저 생성하게 됩니다.

 

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

이런 경우에 대해 생각해보겠습니다. Customer 클래스를 생성 시 고객 ID와 이름을 반드시 지정해야 한다고 가정합니다. 이런 경우 set () 메서드로 값을 지정하는 게 아닌 새로운 생성자를 만드는데 매개변수로 값을 전달받아야 합니다. 즉 디폴트 생성자가 아닌 매개변수를 받는 생성자를 직접 구현을 해야 합니다.

이전에 사용했던 Customer 클래스에 새로운 생성자를 추가하고 기존의 디폴트 생성자는 주석 처리를 해보겠습니다.

package inheritance;

public class Customer {
	protected int customerID;
	protected String customerName;
	protected String customerGrade;
	int bonusPoint;
	double bonusRatio;
	
	/*
	public Customer() {
		customerGrade = "SILVER";
		bonusRatio = 0.01;
		System.out.println("Customer() 생성자 호출");
	} // 디폴트 생성자
	
	*/
	public Customer( int customerID, String customerName) {
		this.customerID = customerID;
		this.customerName = customerName;
		customerGrade = "SILVER";
		bonusRatio = 0.01;
		System.out.println("Customer(int,String) 생성자 호출");
	}
	
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price;
	} // 보너스 포인트 적립, 지불 가격 계산 메서드 
	
	public String getCustomerInfo() {
		return customerName = "님의 등급은 " + customerGrade + "이며, 보너스 포인트는" + bonusPoint + "입니다.";
	} // 고객 정보를 반환하는 메서드
	
	
	///////------- 추가 코드 ------////////////
	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;
	}
	
}

그러나 해당 코드로 실행을 해보면 VIPCustomer 클래스에서 오류가 발생합니다. 오류가 발생한 디폴트 생성자에 마우스를 올려 보면 다음과 같은 오류 메시지가 보입니다.

해당 메시지는 묵시적으로 호출될 디폴트 생성자 - Customer( )가 제대로 정의되지 않았기 때문입니다. 때문에 반드시 명시적으로 다른 생성자를 호출해야 합니다.

Customer 클래스를 새로 생성 시 고객 ID와 고객 이름을 필요로 하였으니 VIPCustomer 클래스도 생성시 해당 값이 필요합니다. 또한 추가적으로 상담원 ID까지 함께 지정해보겠습니다.

( 기존의 VIPCustomer 클래스의 디폴트 생성자를 지우거나 주석 처리를 한 후 필요한 매개변수를 포함하는 생성자를 새로 작성해봅시다. )

package inheritance;

public class VIPCustomer extends Customer {
	private int agentID;
	double saleRatio;
	
	/*
	public VIPCustomer() {
		customerGrade = "VIP";
		bonusRatio = 0.05;
		saleRatio = 0.1;
		System.out.println("VIPCustomer() 생성자 호출");
	}*/
	
	// 새로 작성된 VIPCustomer 생성자.
	public VIPCustomer(int customerID, String customerName, int agentID) {
		super(customerID, customerName);
		customerGrade ="VIP";
		bonusRatio = 0.05;
		saleRatio = 0.1; 
		this.agentID = agentID;
		System.out.println("VIPCustomer(int,String) 생성자 호출");
	}
	
	public int getAgentID() {
		return agentID;
	}
}

해당 코드의 새로운 생성자는 고객 ID, 고객 이름, 상담원 ID를 매개변수로 받습니다. super 예약어는 상위 클래스 생성자를 호출하는 역할을 수행하며 3행의 super(customerID, customerName) 문장으로 상위 클래스 생성자를 호출합니다.

* 코드 실행을 위해서 디폴트 생성자로 표기했던 main 문에서 매개변수를 삽입하여 main문 수정을 하면 되겠습니다. 

Customer customerLee = new Customer(10010, "이순신");

VIPCustomer customerKim = new VIPCustomer(10020, "김유신",121);

상위 클래스의 멤버 변수나 메서드를 참조할 때도 마찬가지로 super를 활용하면 됩니다. 만약 VIPCustomer 클래스에서 showVIPINfo() 메서드에서 상위 클래스의 showCustomerInfo() 메서드를 참조해 담당 상담원 아이디를 추가로 출력하려고 할 때 다음과 같이 구현 가능합니다.

public String showVIPInfo() {
		return super.getCustomerInfo() + "담당 상담원 아이디는" + agentID + "입니다.";
	}

super 예약어를 통해서 상위 클래스의 참조 값을 가지고 있기에 해당 코드처럼 사용하면 고객 정보를 출력하는 getCustomerInfo() 메서드를 새로 구현하지 않고도 상위 클래스의 구현 내용을 활용 가능합니다.

 

상속 클래스로 묵시적 클래스 형 변환.

상속을 공부하면서 우리가 이해해야 하는 중요한 관계가 클래스 간의 형 변환입니다. 이전에 생성했던 Customer와 VIPCustomer 클래스의 관계를 먼저 살펴보겠습니다. 개념 측에서는 상위 클래스인 Customer가 VIPCustomer보다 일반적인 개념이며 기능 면에서는 VIPCustomer 가 Customer보다 기능이 더 많습니다. 왜냐하면 상속받은 클래스는 상위 클래스 기능을 모두 사용 가능하며 추가로 많은 기능이 더해졌기 때문입니다.

때문에 VIPCustomer는 VIPCustomer이면서 동시에 Customer 클래스이기도 합니다. 즉 VIPCustomer로 클래스 인스턴스를 생성 시 인스턴스의 자료형을 Customer 형으로 클래스 형 변환을 하여 선언이 가능합니다.

이러한 클래스 형 변환을 업 캐스팅(upcasting) 이라고도 합니다.

ex ) Customer vc = new VIPCustomer(); → 가능.

그러나 주의할 점은 위의 경우는 가능하지만 Customer로 인스턴스를 생성 시 VIPCustomer 형으로는 선언이 불가능합니다. 즉 Customer 클래스가 VIPCustomer의 기능을 모두 가지고 있지 않기 때문입니다.

ex ) VIPCustomer vc = new Customer(); → 불가능

 

추가적인 부분 - 클래스의 상속 계층 구조가 여러 단계일 때도 묵시적으로 형 변환이 가능한가?

클래스의 상속 계층이 여러 단계일 경우도 상위 클래스로의 형 변환은 묵시적으로 이뤄집니다. 그림의 계층 구조에서 보면 포유류를 상속받은 호랑이와 영장류가 있고 영장류를 상속받은 인간이 있습니다. 때문에 인간은 Human형이면서 Primate, Mammal 형이 되기에 하단의 두줄과 같이 코딩이 가능합니다.

Primate pHuman = new Human();

Mammal mHuman = new Human();

 

출처: Do it, Introduction to Java Programming

반응형

'프로그래밍 ( Programming ) > JAVA' 카테고리의 다른 글

[JAVA] 상속이란?  (0) 2021.06.26
[JAVA] ArrayList 란  (0) 2021.06.10
[JAVA] 배열  (0) 2021.06.01
[JAVA] static 응용 - 싱글톤 패턴  (0) 2021.05.28

댓글