[그린컴퓨터] Server/JAVA(객체 지향 프로그래밍)

상속에서 클래스 생성과 형 변환 { 하위 클래스가 생성되는 과정, 부모를 부르는 예약어 super, 상위 클래스로 묵시적 클래스 형 변환 }

Ben의 프로그램 2023. 5. 29. 16:38
728x90

하위 클래스가 생성되는 과정을 알아야 하는 이유를 알아야 하는 이유

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

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

  • 상속을 받은 하위 클래스는 상위 클래스의 변수와 메서드를 사용할 수 있다고 했습니다. 
  • 즉 이전에 만든 CustomerTest 클래스 예제를 살펴보면, VIPCustomer 클래스로 선언한 customerKim 인스턴스는 상속받은 상위 클래스의 변수를 자기 것처럼 사용할 수 있습니다. 
  • 변수를 사용할 수 있다는 것은 그 변수를 저장하고 있는 메모리가 존재한다는 뜻입니다. 
  • 그런데 VIPCustomer 클래스의 코드를 보면 해당 변수가 존재하지 않습니다. Customer 클래스를 상속받았을 뿐입니다. 
  • 여기에서 우리는 상속된 하위 클래스가 생성되는 과정을 다시 생각해 볼 필요가 있습니다. 
  • 테스트를 하기 위해 Customer 와 VIP Customer 클래스 생성자에 출력문을 추가하겠습니다. 
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 class VIPCustomerExtends extends Customer{
	private int agentID;
	double saleRatio;
	
	public VIPCustomerExtends() {
		customerGrade = "VIP";   // 상위 클래스에서 private 변수이므로 오류 발생
		bonusRatio = 0.05;
		saleRatio = 0.1;
		System.out.println("VIPCustomerExtends( ) 생성자 호출 ");
	}
...
  • 위와 같이 Customer( ) 생성자와 VIPCustomer( ) 생성자에 출력문을 추가했습니다. 
  • 이제 CustomerTest2 클래스를 실행하여 출력 결과를 확인해 보겠습니다. 
public class CustomerTest2 extends Customer {

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

}

  • 출력 화면을 보면 상위 클래스의 Customer( ) 생성자가 먼저 호출되고 그다음에 VIPCustoemr( ) 가 호출되는 것을 알 수 있습니다. 
  • 정리하면 상위 클래스를 상속받은 하위 클래스가 생성될 때는 반드시 상위 클래스의 생성자가 먼저 호출됩니다. 
  • 그리고 상위 클래스 생성자가 호출될 때 상위 클래스의 멤버 변수가 메모리에 생성되는 것이지요. 

  • 그림으로 보면 위와 같은데, 상위 클래스의 변수가 메모리에 먼저 생성되기 때문에 하위 클래스에서도 이 값들을 모두 사용할 수 있습니다. 
  • 앞서서 private 으로 선언했을 경우 발생했던 오류에 대해서 다시 생각해보겠습니다. 이 경우 변수는 상위 클래스 생성자를 통해 생성되었지만 private 접근제어자 때문에 하위 클래스에서 접근 불가능한 것이었습니다. 
  • 지금까지 하위 클래스가 생성될 때 상위 클래스가 먼저 만들어진다는 것을 배웠습니다. 
  • 이제 어떤 과정으로 상위 클래스가 생성되는지 살펴봅시다. 

부모를 부르는 예약어, super

  • super 예약어는 하위 클래스에서 상위 클래스로 접근할 때 사용합니다. 
  • 하위 클래스는 상위 클래스의 주소, 즉 참조 값을 알고 있습니다. 
  • 이 참조 값을 가지고 있는 예약어가 바로 super 입니다. (하위 클래스가 생성될 때는 상위 클래스 생성자가 먼저 호출되기 때문에 하위 클래스에는 상위 클래스의 참조 값을 가지고 있고, 이 참조 값을 가리키는 예약어가 super 이다)
  • this 가 자기 자신의 참조 값을 가지고 있는 것과 같습니다. 
  • super 는 또한 상위 클래스의 생성자를 호출하는 데도 사용합니다. (상위 클래스의 참조 값을 가지고 있으니 당연한 결과다)

상위 클래스 생성자 호출하기

  • CustomerTest2.java 예제를 보면 VIPCustomer 만 생성하였는데 Customer 상위 클래스의 생성자가 호출되며 상위 클래스도 생성된 것을 알 수 있었습니다.
  • 하위 클래스 생성자만 호출했는데 상위 클래스 생성자가 호출되는 이유는 하위 클래스 생성자에서 super( ) 를 자동으로 호출하기 때문입니다. 
  • super( ) 를 호출하면 상위 클래스의 디폴트 생성자가 호출됩니다. 
public class VIPCustomerExtends extends Customer{
	private int agentID;
	double saleRatio;
	
	public VIPCustomerExtends() {
		super();
		customerGrade = "VIP";   // 상위 클래스에서 private 변수이므로 오류 발생
		bonusRatio = 0.05;
		saleRatio = 0.1;
		System.out.println("VIPCustomerExtends( ) 생성자 호출 ");
	}
  • 하위 클래스의 디폴트 생성자는 바이트 코드로 변환된기 전에 위와 같이 코드가 자동으로 변경됩니다. 

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

  • 이런 경우를 한번 생각해보겠습니다. 
  • Customer 클래스를 생성할 때 고객 ID 와 이름을 반드시 지정해야 한다고 가정합시다. 
  • 이런 경우에 디폴트 생성자가 아니라 새로운 생성자를 만들어서 매개변수가 있는 생성자를 직접 구현해야 합니다. 
...
public Customer(int customerID, String customerName) {
		this.customerID = customerID;
		this.customerName = customerName;
		customerGrade = "SILVER";
		bonusRatio = 0.01;
		System.out.println("Customer( ) 생성자 호출 ");
	}
...
  • 그런데 이렇게 Customer 클래스의 디폴트 생성자를 없애고 새로운 생성자를 작성하면, Customer 클래스를 상속받은 VIPCustomer 클래스에서 오류가 발생합니다. 
  • "Implicit super constructor Customer( ) is undefined. Must explicit invoke another constructor.
  • 이 오류 메시지는 묵시적으로 호출될 디폴트 생성자 Customer( ) 가 정의되지 않았기 때문에, 반드시 명시적으로 다른 생성자를 호출해야 한다는 뜻입니다. 
  • Customer 클래스를 새로 생성할 때 고객 ID와 고객 이름을 반드시 지정하여 생성하기로 했으니 VIPCustomer 클래스를 생성할 때도 이 값이 필요합니다.
  •  VIP 고객만을 위한 상담원 ID까지 함께 지정할 수 있도록 VIPCustomer 의 생성자를 수정해보도록 하겠습니다. 
public class VIPCustomerExtends extends Customer{
	private int agentID;
	double saleRatio;
	
	public VIPCustomerExtends(int customerID, String customerName, int agentID) {
		super(customerID, customerName);
		this.agentID = agentID;
		customerGrade = "VIP";   // 상위 클래스에서 private 변수이므로 오류 발생
		bonusRatio = 0.05;
		saleRatio = 0.1;
		System.out.println("VIPCustomerExtends(int customerID, String customerName, int agentID) 생성자 호출 ");
	}
  • 새로운 생성자는 고객 ID, 고객 이름, 상담원 ID를 매개변수로 받습니다.
  • super 예약어는 상위 클래스 생성자를 호출하는 역할을 하며, 이번에 사용한 super( ) 예약어는 매개변수 customerID, customerName 을 사용하여 상위 클래스의 디폴트 생성자를 호출하는 것이 아니라 내가 원하는 상위 클래스의 생성자를 호출하는 것을 볼 수 있습니다. 
  • 작동하는 과정을 다시 한번 생각해보면 다음과 같습니다.
  • super( ) 를 통해 Customer(int customerID, String customerName) 라는 상위 클래스 생성자를 호출하고 코드 순서대로 상위 클래스 멤버 변수가 초기화됩니다. 그 이후 상위 클래스 생성자 호출이 끝나면 VIPCustomer 하위 클래스 생성자의 내부 코드 수행이 마무리됩니다. 
public class CustomerTest2 {

	public static void main(String[] args) {
		VIPCustomerExtends customerKim = new VIPCustomerExtends(10020, "김유신", 2020);
		customerKim.bonusPoint = 10000;
		System.out.println(customerKim.showCustomerInfo());
	}

}
  • 실제로 코드를 수정한 후 Test 클래스도 위와 같이 수정한 후 실행해보면 다음과 같이 결과 값이 출력되는 것을 볼 수 있습니다. 

  • VIP 등급인 김유신 고객을 생성할 때는 상위 클래스 생성자를 먼저 호출한 후 하위 클래스 생성자의 코드 수행이 정상적으로 마무리되는 것을 확인할 수 있습니다. 

상위 클래스의 멤버 변수나 메서드를 참조하는 super

  • 상위 클래스에 선언한 멤버 변수나 메서드를 하위 클래스에서 참조할 때도 super 를 사용합니다. 
  • 예를 들어 VIPCustomer 클래스의 showVIPInfo( ) 메서드에서 상위 크래스의 showCustomerInfo( ) 메서드를 참조해 담당 상담원 아이디를 추가로 출력하려고 할 때 다음과 같이 구현할 수 있습니다.
public String showVIPInfo() {
		return super.showCustomerInfo() + "담당 상담원 아이디는" + agentID + "입니다.";
}
  • super 예약어는 상위 클래스의 참조 값을 가지고 있으므로 위 코드처럼 사용하면 고객 정보를 출력하는 showCustomerInfo( ) 메서드를 새로 구현하지 않고 상위 클래스의 구현 내용을 활용할 수 있습니다. (물론 여기서 super 예약어를 사용하지 않고 상위 클래스의 메서드가 잘 호출됩니다. 그 이유는 이후 오버라이딩 파트에서 다룹니다. 아무튼, 하위 클래스가 상위 클래스와 동일한 이름의 메서드를 구현하는 경우 상위 클래스의 메서드를 가리켜야 할 때는 super 예약어를 사용해야 합니다.)

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

  • 상속을 공부하면서 우리가 이해해야 하는 중요한 관계가 클래스 간의 형 변환입니다. 
  • Customer 와 VIPCustomer 의 관계를 생각해 봅시다. 
  • 새념 면에서 보면 상위 클래스인 Customer 가 VIPCustomer 보다 일반적은 개념이고, 기능 면에서 보면 VIPCustomer 가 Customer 보다 기능이 더 많습니다. 왜냐하면 상속받은 클래스는 상위 클래스 기능을 모두 사용할 수 있고 추가로 더 많은 기능을 구현하기 때문입니다. 
  • 따라서 VIPCustomer 는 VIPCustomer 형이면서 동시에 Customer 형이기도 합니다. 
  • 즉 VIPCustomer 클래스로 인스턴스를 생성할 때 이 인스턴스의 자료형을 Customer 형으로 클래스 형 변환하여 선언할 수 있습니다. 

  • 상위 클래스가 일반적인 개념이지만, 실질적인 기능은 자식 클래스가 더 많이 가지고 있으므로 자식 클래스는 부모 클래스로 자동 형변환이 가능합니다. 
  • 하지만 반대로 Customer (부모)로 인스턴스를 생성할 때 VIPCustomer (자식)형으로 선언할 수는 없습니다. 상위 클래스인 Customer 가 VIPCustomer 클래스의 기능을 다 가지고 있는 것은 아니기 때문입니다. 

형 변환된 vc 가 가리키는 것

  • Customer vd = new VIPCustomer( ); 에서 형 변환된 vc 가 가리키는 것은 무엇일까요? 

  • Customer vc = new VIPCustomer( ); 문장이 실행되면 VIPCustomer 생성자가 호출되므로 클래스 변수가 위와 같이 우선 만들어집니다. 
  • 그런데 클래스의 자료형이 Customer 로 한정되었습니다. 
  • 클래스가 형 변환이 되었을 때는 선언한 클래스형에 기반하여 멤버 변수와 메서드에 접근할 수 있습니다. 
  • 즉 이 vc 참조 변수가 가리킬 수 있는 변수와 메서드는 Customer 클래스의 멤버뿐입니다. 
  • 이렇게 클래스 형 변환을 사용하는 이유는 이후 나오는 오버라이딩과 다형성에서 자세히 다룰 것입니다.