article thumbnail image
Published 2020. 3. 3. 11:13

상속과 생성자

편리함을 위해서 어떠한 기능을 수용하면 그 기능이 기존의 체계와 관계하면서 다양한 문제를 발생시킨다.

그 문제를 한마디로 줄여서 말하면 복잡도의 증가라고 할 수 있다.

이번 시간에는 생성자가 상속을 만나면서 발생한 복잡성을 보여줄 생각이다.

그 맥락에서 super이라는 키워드의 의미도 중요하게 다뤄질 내용이다.

이번 수업을 이해하기 위해서는 기본 생성자의 성질에 대한 이해가 선행되야 한다. 아래의 예제를 보자.

package org.opentutorials.javatutorials.Inheritance.example4;
public class ConstructorDemo {
    public static void main(String[] args) {
        ConstructorDemo  c = new ConstructorDemo();
    }
}

위의 예제는 에러를 발생시키지 않는다.

ConstructorDemo 객체를 생성할 때 자동으로 생성자를 만들어주기 때문이다. 하지만 아래의 예제는 에러가 발생한다.

package org.opentutorials.javatutorials.Inheritance.example4;
public class ConstructorDemo {
    public ConstructorDemo(int param1) {}
    public static void main(String[] args) {
        ConstructorDemo  c = new ConstructorDemo();
    }
}

여러분이 이 ConstructorDemo 라는 클래스에 생성자(constructor)를 선언했는데

그 생성자가 매개변수가 있다라는 것은 이 생성자가 기본 생성자가 아니라는 뜻이다

그리고 이상태에서 클래스를 인스턴스화 시키면 에러가 발생한다.

 

첫 코드의 ConstructorDemo는 어떠한 생성자도 존재하지 않는 상태이다.

그러면 자바는 자동으로 기본 생성자를 만들어준다.

기본 생성자는 이 클래스의 이름과 같으면서 매개변수가 없는 메쏘드기본 생성자이다.

그리고 그 기본생성자는 내용이 비어있겠죠.

 

그렇기 때문에 우리가 new ConstructorDemo(); 라고 했을 때 생성자가 만들어질 수 있는 것은

바로 이 ConstructorDemo라고 하는 메쏘드에 해당되는 생성자를 자바가 자동으로 만들기 때문에 가능하다.

 

그런데 두 번째 코드의 ConstructorDemo같은 경우에는

기본 생성자가 아닌 생성자, 즉 매개변수가 있는 생성자를 정의하고 있다.

자, 이렇게 어떠한 생성자가 개발자에 의해서 명시적으로 만들어지게 되면

자바는 기본 생성자를 자동으로 만들어 주지 않는다.

그 상태에서 new ConstructorDemo();를 했기 때문에 (이 생성자가 호출이 돼야 하는데 이 생성자는 인자가 없다)

그런데 ConstructorDemo는 인자가 없는 생성자를 가지고 있지 않고

자바는 그것을 자동으로 만들어주지 않았기 때문에 에러가 발생하게 된다.

 

그렇기 때문에 인자가 없는 생성자를 명시적으로 선언해 주면

public ConstructorDemo(){}

에러가 나지 않는다.


 

매개변수가 있는 생성자가 있을 때는 자동으로 기본 생성자를 만들어주지 않는다.

따라서 위의 예제는 존재하지 않는 생성자를 호출하고 있다.

이 문제를 해결하기 위해서는 아래와 같이 기본 생성자를 추가해줘야 한다.

package org.opentutorials.javatutorials.Inheritance.example4;
public class ConstructorDemo {
    public ConstructorDemo(){}            // 2. 따라서 기본생성자를 설정해준다
    public ConstructorDemo(int param1) {} // 1. 매개변수가 있는 생성자는 자동으로 기본생성자 안 만든다
    public static void main(String[] args) {
        ConstructorDemo  c = new ConstructorDemo();
    }
}

이제 본론으로 들어가보자. 상속 토픽의 첫 번째 예제를 조금 수정해서 생성자를 통해서 left, right의 값을 설정한다.


package org.opentutorials.javatutorials.Inheritance.example2;
 
class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo4 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

이해를 돕기 위해서 아래와 같이 차이점만을 부각한 이미지를 첨부하였다. 붉은색으로 표시된 부분이 달라진 부분이다.

실행결과는 아래와 같다.

 

30

15

-10

 

SubstractionableCalculator의 생성자로 left와 right의 값을 받아서 초기화시키고 있다.

만약 클래스 Calculator가 메소드 setOprands가 아니라 생성자를 통해서 left, right 값을 설정하도록 하고 싶다면 아래와 같이 코드를 변경해야 할 것이다.

package org.opentutorials.javatutorials.Inheritance.example3;
 
class Calculator {
    int left, right;
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo5 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

달라진 부분은 아래와 같다.

 

위의 코드를 실행하면 오류가 발생한다.


기본 생성자를 명시 안한 상태이기 때문이다.

아래처럼 Calculator라는 클래스는 기본생성자가 아닌 생성자를 이미 명시적으로 정의를 해논 상태이다

 

그 얘기는 뭐냐면 기본 생성자가 있어야 하는데 자바가 자동으로 기본 생성자를 만들어주지 않았다는 뜻이다.

하위 클래스가 상속을 받을 때 상위 클래스에 있는 Calculator의 기본 클래스, 기본 생성자를 호출해야 하는데,

그 생성자가 존재하지 않기 때문에 에러가 발생하는 겁니다.

컴파일러는 여러분에게 기본생성자를 명시적으로 선언하라고 불평을 하고 있는 것이다.

    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }

                     ↓

    public Calculator(){
         
    }
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }

즉 상위 클래스인 Calculator의 생성자가 존재하지 않는다는 의미다. 하위 클래스가 호출될 때 자동으로 상위 클래스의 기본 생성자를 호출하게 된다. 그런데 상위 클래스에 매개변수가 있는 생성자가 있다면 자바는 자동으로 상위 클래스의 기본 생성자를 만들어주지 않는다. 따라서 존재하지 않는 생성자를 호출하게 되기 때문에 에러가 발생했다. 이 문제를 해결하기 위해서는 아래와 같이 상위 클래스에 기본 생성자를 추가하면 된다.

package org.opentutorials.javatutorials.Inheritance.example3;
 
class Calculator {
    int left, right;
     
    public Calculator(){
         
    }
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo5 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

차이점은 아래와 같다.

그런데 상위 클래스인 Calculator에는 left와 right 값을 초기화할 수 있는 좋은 생성자가 이미 존재한다. 이것을 사용한다면 하위 클래스에서 left와 right의 값을 직접 설정하는 불필요한 절차를 생략할 수 있을 것이다. 어떻게 하면 상위 클래스의 생성자를 호출할 수 있을까?


super

super는 상위 클래스를 가리키는 키워드다.

하위 클래스가 상위 클래스를 참조할 수 있는 방법, 키워드이다.

예제를 통해서 super의 사용법을 알아보자.

package org.opentutorials.javatutorials.Inheritance.example3;
 
class Calculator {
    int left, right;
     
    public Calculator(){}
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        super(left, right);
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo5 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

차이점은 아래와 같다.

class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        this.left = left;
        this.right = right;
    }

                     ↓

class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        super(left,right);
    }

super라고 해주고 괄호가 붙어있으면

부모클래스의 생성자라는 뜻이다.

그 생성자에게 left값과 right값을 주게 되면 

super 키워드는 부모 클래스를 의미한다. 여기에 ()붙이면 부모 클래스의 생성자를 의미하게 된다. 이렇게 하면 부모 클래스의 기본 생성자가 없어져도 오류가 발생하지 않는다.

하위 클래스의 생성자에서 super를 사용할 때 주의할 점은 super가 가장 먼저 나타나야 한다는 점이다.

즉 부모가 초기화되기 전에 자식이 초기화되는 일을 방지하기 위한 정책이라고 생각하자.

 

다시 말하지만 초기화 코드를 super 보다 먼저 등장시키면 안된다.

항상 하위클래스의 초기화 코드는 super클래스를 호출한 다음에 나타나야 한다. 안 그러면 오류가 나타난다.

하위클래스가 만들어진다는 것은 상위클래스가 이미 인스턴스화 되었다는 것이니까,

상위클래스가 미리 초기화가 다 끝난 상태에서만 하위 클래스의 초기화를 진행할 수 있다는 것이다.

'📌 java > java' 카테고리의 다른 글

java - overloading  (0) 2020.03.10
java - overriding  (0) 2020.03.09
java - 상속  (0) 2020.03.02
java - 생성자(constructor)  (0) 2020.02.28
java - 유효 범위  (0) 2020.02.26
복사했습니다!