[Java] 11. 상속

Java / / 2019. 3. 21. 21:24

1. 상속이란?

 

상속이란 말 그대로 자식이 부모로 부터 무언가를 물려 받는 것을 말합니다.

 

1
2
3
4
5
6
7
8
9
10
11
package c.inheritance;
 
public class Parent {
    public Parent() {
        System.out.println("Parent Constructor");
    }
    
    public void printName() {
        System.out.println("printName() - Parent");
    }
}

 

 

1
2
3
4
5
6
7
8
package c.inheritance;
 
public class Child extends Parent{
    public Child() {
        System.out.println("Child Constructor");
    }
}
 

 

 

Child 클래스의 뒤에 있는 extends라는 것은 자바의 예약어입니다. 그 다음에 클래스 이름을 지정하면 그 클래스를 상속받습니다. 이렇게 상속을 하면 부모 클래스에 선언되어 있는 public 및 protected로 선언되어 있는 모든 변수와 메소드를 내가 갖고 있는 클래스에서 사용할 수 있습니다. 즉, 접근 제어자가 없거나 private로 선언된 것들을 자식 클래스에서 사용할 수 없습니다. 위의 예제에서는 Child 클래스에서 printName()이라는 public 한 메소드를 마음껏 사용할 수 있습니다.  

 

이를 클래스의 관계를 나타내는 UML 클래스 다이어그램으로 그려보면 그림과 같습니다.

 

 

 

이와 같이 클래스 다이어그램에서 속이 빈 삼각형 화살표로 자식 -> 부모 클래스를 가리키면 상속 관계를 나타냅니다. 상속관계가 아닌 참조 관계는 보통은 그냥 직선으로 관계를 나타냅니다. 

 

1
2
3
4
5
6
7
8
9
10
package c.inheritance;
 
public class Inheritance {
 
    public static void main(String[] args) {
        Child child = new Child();
        child.printName();
    }
    
}

 

 

다음 Inheritance 클래스를 컴파일하고 실행해보면 결과는 다음과 같습니다.

 

 

Parent 클래스의 메소드를 호출하지 않았는데, 상속을 한 클래스가 생성자를 호출하면, 자동으로 부모 클래스의 기본 생성자가 호출됩니다. 그래서 "Parent Constructor"라는 문장이 출력됩니다. 그 다음 자식 클래스의 생성자에 있는 내용 들이 수행됩니다. 또한 부모 클래스에만 printName() 이라는 메소드가 있기 대문에 부모 클래스에 있는 printName() 메소드가 실행된 것을 볼 수 있습니다. 정리를 해보겠습니다.

 

부모 클래스에서는 기본 생성자를 만들어 놓는 것 이외에는 상속을 위해서 별도로 작업을 할 필요는 없다.

 

● 자식 클래스는 클래스 선언시 extends 다음에 부모 클래스 이름을 적어준다.

 

● 자식 클래스의 생성자가 호출되면, 자동으로 부모 클래스의 매개 변수 없는 생성자가 실행된다.

 

● 자식 클래스에서는 부모 클래스에 있는 public, protected로 선언된 모든 인스턴스 및 클래스 변수와 메소드를 사용할 수 있다.

 

이렇게 상속을 만든 이유는 부모 클래스가 갖고 있는 변수와 메소드를 상속받음으로써 개발을 할 때 2중, 3중의 일을 안해도 되는 장점이 있습니다. 맥북으로 예를 들었을 때 신모델이 나오면 달랑 한 가지만 있는게 아니라 13인치, 15인치 모델이 있고 각각의 모델도 두개 정도의 모델로 나뉩니다. 이렇게 같은 시점에 나오는 맥북 4가지 모델을 각각 처음부터 따로 디자인한다면 회사 입장에서는 엄청난 시간과 인건비 낭비입니다. 하지만, 하나를 제대로 만들어 놓고, 그것에서 파생되는 것들을 조금씩 바꾸어 판매하면 사용자는 기호에 따라 여러 모델을 살 수 있습니다. 

 

자바에서의 상속도 바로 이와 비슷한 개념입니다. 하나의 클래스를 잘 만들어 놓으면 그 클래스를 상속받아 추가적인 기능을 넣을 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
package c.inheritance;
 
public class Child extends Parent{
 
    public Child() {
        System.out.println("Child Constructor");
    }
    
    public void printAge() {
        System.out.println("printAge() - 18 month");
    }
}

 

 

이렇게 상속받은 Child 클래스는 추가적인 메소드를 만들어도 전혀 문제가 없습니다. 만약 상속 이라는 개념이 없을 때, printName() 메소드에 문제가 있어서 고쳐야 하는 상황이 발생한다면 모든 클래스에 있는 printName() 메소드를 일일이 고치는 상황이 발생합니다. 

 

여기서 주의할점으로는 자바는 다중 상속이 되지 않는다는 것입니다. extends 뒤에 클래스를 하나만 써야 합니다. 두개 이상 클래스를 나열하면 컴파일이 되지 않습니다.

 

2. 상속과 생성자

만약 부모 클래스에 기본 생성자가 없다면 어떻게 될까요? 기본 생성자를 주석 처리하고 Parent 클래스를 다시 컴파일한 후 Inheritance 클래스를 실행한다면 다음과 같은 결과를 얻을 수 있습니다.

 

 

"Parent Constructor"라는 메시지는 출력되지 않고 정상적으로 실행됩니다. 별 문제가 없다고 생각할 수 있습니다. 하지만, Parent 클래스에 매개 변수를 받는 생성자가 있을 경우에는 이야기가 달라집니다. 다음과 같이 Parent 클래스에 String을 매개 변수로 받는 생성자를 하나 추가 후 실행해 봅시다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package c.inheritance;
    
public class Parent {
    /*
    public Parent() {
        System.out.println("Parent Constructor");
    }
    */
        
    public Parent(String name) {
        System.out.println("Parent(String) Constructor");
    }
        
    public void printName() {
        System.out.println("printName() - Parent");
    }
}
 

 

 

Parent 클래스를 변경해 놓고, Child 클래스를 다시 컴파일 하면 다음과 같은 에러를 출력합니다.

 

 

Child 클래스의 생성자가 실행할 때 Parent 클래스의 매개 변수가 없는 기본 생성자가 없다는 에러가 발생합니다. 이러한 문제가 발생하는 이유는 Parent 클래스의 상속을 받은 Child 클래스의 모든 생성자가 실행될 때 Parent()의 기본 생성자를 찾는데, Parent 클래스에 정의한 생성자는 String을 매개 변수로 받는 생성자밖에 없기 때문입니다. 이러한 경우에 2가지 해결 방법이 있습니다.

 

1. 부모 클래스인 Parent 클래스에 기본 생성자를 만든다.

2. 자식 클래스에서 부모 클래스의 생성자를 명시적으로 지정하는 super()를 사용한다.

 

여기서 super() 라는 것은 메소드처럼 super()로 사용하면 부모 클래스의 생성자를 호출한다는 것을 의미합니다. 메소드처럼 사용하지 않고 super.printName()으로 사용하면 부모 클래스에 있는 printName()이라는 메소드를 호출한다는 의미입니다. 자식 클래스를 컴파일할 때 자동으로 super()라는 문장이 들어가기 때문에 실행 오류가 발생하는 것입니다. 정상적으로 수행되게 하려면 다음과 같이 Child 클래스의 생성자를 고치면 됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
package c.inheritance;
 
public class Child extends Parent{
 
    public Child() {
        super(null);
        System.out.println("Child Constructor");
    }
    
    public void printAge() {
        System.out.println("printAge() - 18 month");
    }
}

 

 

여기서 super(null) 이라고 지정하면, Parent 클래스의 생성자 중 null을 매개 변수로 받을 수 있는 생성자를 찾습니다. String을 매개 변수로 하는 생성자가 있기 때문에, 이 생성자가 호출됩니다. 참조 자료형을 매개 변수로 받는 생성자가 하나 더 있다면 어떻게 될까요? 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package c.inheritance;
    
public class Parent {
        
    public Parent(String name) {
        System.out.println("Parent(String) Constructor");
    }
        
    public Parent(Inheritance obj) {
        System.out.println("Parent(Inheritance) Constructor"); 
    }
        
    public void printName() {
        System.out.println("printName() - Parent");
    }
        
}
 

 

 

이렇게 Parent 클래스의 코드를 수정하고 실행을 해봅시다. Child 클래스의 생성자에서 null을 넘겨주면 어떤 객체의 null인지알 수 없습니다. 

 

 

Parent 클래스의 Inheritance를 매개 변수로 받는 생성자를 지우고, 다음과 같이 Child 클래스에 String을 매개 변수로 받는 생성자를 추가합니다. 일반적으로 부모 클래스에 같은 타입을 매개 변수로 받는 생성자가 있다면 다음과 같이 처리합니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
package c.inheritance;
    
public class Parent {
        
    public Parent(String name) {
        System.out.println("Parent(String) Constructor");
    }
        
    public void printName() {
        System.out.println("printName() - Parent");
    }    
}

 

 

 

 

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package c.inheritance;
 
public class Child extends Parent{
    
    public Child() {
        super(null);
        System.out.println("Child Constructor");
    }
    
    public Child(String name) {
        super(name);
        System.out.println("Child(String) Constructor");
    }
    
    public void printAge() {
        System.out.println("printAge() - 18 month");
    }
}

 

매개 변수로 넘겨받은 값을 부모 클래스의 생성자로 바로 넘겨주는 것을 볼 수 있습니다. 또한 super()를 사용할 때 가장 중요한 것중 하나는 "반드시 생성자의 첫줄"에 있어야만 한다는 것입니다. 정리를 해보면 다음과 같습니다.

 

● 생성자에서는 super()를 명시적으로 지정하지 않으면, 자동으로 super()가 추가된다.

 

● 만약, 부모클래스에 매개 변수가 없는 생성자가 정의되어 있지 않고, 매개 변수가 있는 생성자만 정의되어 있을 경우에는 명시적으로 super()에서 매개 변수가 있는 생성자를 호출하도록 해야만 한다.

 

 

'Java' 카테고리의 다른 글

[Java] 13. 참조 자료형의 형 변환  (0) 2019.03.23
[Java] 12. 메소드 오버라이딩(Overriding)  (0) 2019.03.23
[Java] 10. 접근 제어자  (0) 2019.03.19
[Java] 9.패키지  (0) 2019.03.10
[Java] 8. Pass by Value, Pass by reference  (0) 2019.03.03
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기