Theme. 상속이란?

 

상속의 개념을 간단하게 말하자면,

어떤 클래스가 다른 클래스의 모든 멤버들을 포함하고, 그 외에 추가적으로 다른 멤버들을 가지는 것을 말한다.

이때, 데이터 멤버와 메서드를 모두 포함한다.

 

예를 통해 상속에 대해 이해해보자.

Computer class와 Notebook class가 있다.

 

Computer라는 클래스는 다음과 같은 멤버들을 가진다고 하자.

 

 

Notebook이라는 클래스의 경우에는 컴퓨터가 가지는 데이터 멤버나 메서드에 추가적으로,

screen size, weight의 데이터 멤버를 가진다. 

 

즉, 아래와 같은 관계를 가진다.

 

 

Notebook class는 Computer class가 가지고 있는 멤버들에 더하여 추가적으로 다른 멤버들을 가지고 있다.

이는 Computer class를 "확장"했다고 볼 수 있을 것이다.

따라서, Notebook class가 Computer class를 상속받았다는 것을 표현할 때, 

"public class Notebook extends Computer" ... 와 같이 "extends"를 적어준다.

 

만약, Notebook class의 객체를 아래와 같이 생성한다면,

Notebook nb = new Notebook();

그 객체 안에는 Computer class에 존재하는 멤버들이 함께 들어있다.

따라서,

nb.manufacturer = "LG";

nb.computePower();

와 같이 사용할 수 있다.

 

여기서 상속을 의미하는 용어들에 대해 정리할 필요가 있다.

먼저, "is-a" 관계란 말 그대로 "~는 ~의 일종이다."와 같이 이해하면 된다.

예시에서 A Notebook is a Computer. 라고 표현하면 노트북은 컴퓨터의 일종이다, 컴퓨터들 중 하나이다. 

따라서, 노트북은 컴퓨터를 상속받는다. 노트북과 컴퓨터는 "is-a" 관계이다.

 

또한, 컴퓨터가 superclass이면, 노트북은 subclass

컴퓨터가 baseclass이면, 노트북은 extended class

컴퓨터가 parent class이면, 노트북은 child class이다.

 

Theme. 상속과 생성자

상속에서 생성자와 관련하여 반드시 지켜야 하는 규칙이 있다. 

 

어떤 클래스가 있을 때, 작성된 코드에 생성자가 없을 경우 자동으로 no-parameter 생성자가 만들어진다. 하지만, parameter를 받는 생성자가 존재하는 등 생성자가 하나라도 있을 경우 생성자가 자동으로 만들어지지 않는다.

즉, Computer class에

public Computer(String man, String proc, int ram, int disk, double procSpeed) {
manufacturer = man;
processor = proc;
ramSize = ram;
diskSize = disk;
processorSpeed = procSpeed;
}

와 같이 생성자가 존재하는 경우, 자동으로 public Computer() { } 와 같은 no-parameter 생성자는 만들어지지 않는다.

 

모든 서브 클래스의 생성자는 먼저 수퍼 클래스의 생성자를 호출한다.

이때, super(..) 를 통해 명시적으로 호출해 주거나, 

그렇지 않을 경우에는 자동으로 no-parameter 생성자가 호출된다. 이 규칙에서 흔히 오류가 발생할 수 있는데,

이는 아래 코드를 통해 살펴보도록 하자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package section1;
 
public class Computer {
 
    public String manufacturer; 
    public String processor;
    public int ramSize;
    public int diskSize;
    public double processorSpeed;
    
    public Computer(String man, String proc, int ram, int disk, double procSpeed) {
        manufacturer = man;
        processor = proc;
        ramSize = ram;
        diskSize = disk;
        processorSpeed = procSpeed;
    }
    
    public double computePower() {
        return ramSize * processorSpeed;
    }
    
    public double getRamSize() {
        return ramSize;
    }
    
    public double getProcessorSpeed() {
        return processorSpeed;
    }
    
    public int getDiskSize() {
        return diskSize;
    }
    
    public String toString() {
        String result = "Manufacturer: " + manufacturer +
        "\nCPU: " + processor +
        "\nRAM: " + ramSize + " megabytes" +
        "\nDisk: " + diskSize + " gigabytes" +
        "\nProcessor speed: " + processorSpeed +
        " gigahertz";
        return result;
        }
    
    
    
    
    
}
 
cs

 

이와 같이 Computer class에는 매개변수를 받는 생성자가 존재한다. 따라서 no-parameter 생성자는 만들어지지 않을 것이다. 

이때 아래와 같이 Notebook class를 작성하면 에러가 발생한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
    
        screenSize = screen;
        this.weight = weight;
    }
}
 
cs

 

왜 에러가 발생할까?

 

현재 서브 클래스인 Notebook 클래스의 생성자에서 수퍼 클래스인 Computer 클래스의 생성자를 호출하고 있지 않다.

따라서, 자동으로 no-parameter 생성자가 호출될 것이고, "super()" 라고 호출되면서 Computer 클래스의 no-parameter 생성자를 호출하고자 한다. 

하지만! 수퍼 클래스인 Computer 클래스에는 no-parameter 생성자가 존재하지도 않고, 자동으로 만들어지지도 않는 상태이다. 왜냐하면 이미 parameter를 받는 생성자가 존재하기 때문이다.

있지도 않은 생성자를 호출하려고 하니 에러가 발생하는 것이다.

 

만약 Computer 클래스에 아무런 생성자가 작성되어 있지 않은 상황이었다면 에러가 발생하지 않았을 것이다.

왜냐하면 Computer 클래스에서 자동으로 no-parameter 생성자가 호출되고, Notebook 클래스에서는 자동으로 수퍼 클래스의 no-parameter 생성자를 호출할 것이기 때문이다.

 

에러를 해결하기 위해서는 다음과 같이 작성해야 한다.

Computer 클래스의 생성자가 매개변수로 

String man, String proc, int ram, int disk, double procSpeed

를 받고 있으니, Notebook 클래스의 생성자에서도 super(......) 안에 수퍼 클래스에 맞춰 매개변수를 적어줬다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
        super(man, proc, ram, disk, procSpeed); // 에러 해결!
        screenSize = screen;
        this.weight = weight;
    }
}
 
cs

 

Theme. Method Overriding(오버라이딩)

 

자식 클래스는 부모 클래스의 데이터 멤버나 메서드를 모두 상속받게 된다. 이때, 부모로부터 상속받은 메서드를 자식클래스에서 보다 목적에 맞게 사용하고 싶다면, 그 메서드 일부를 수정(덮어쓴다)할 수 있다. 이것이 오버라이딩이다. 이때, 메서드의 이름은 똑같아야 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package section1;
 
public class Computer {
 
    public String manufacturer; 
    public String processor;
    public int ramSize;
    public int diskSize;
    public double processorSpeed;
    
    public Computer(String man, String proc, int ram, int disk, double procSpeed) {
        manufacturer = man;
        processor = proc;
        ramSize = ram;
        diskSize = disk;
        processorSpeed = procSpeed;
    }
    
    public double computePower() {
        return ramSize * processorSpeed;
    }
    
    public double getRamSize() {
        return ramSize;
    }
    
    public double getProcessorSpeed() {
        return processorSpeed;
    }
    
    public int getDiskSize() {
        return diskSize;
    }
    
    public String toString() {
        String result = "Manufacturer: " + manufacturer +
        "\nCPU: " + processor +
        "\nRAM: " + ramSize + " megabytes" +
        "\nDisk: " + diskSize + " gigabytes" +
        "\nProcessor speed: " + processorSpeed +
        " gigahertz";
        return result;
        }
}
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
        super(man, proc, ram, disk, procSpeed); // 에러 해결!
        screenSize = screen;
        this.weight = weight;
    }
    // Overriding
    public String toString() {
        String result = "Manufacturer: " + manufacturer +
        "\nCPU: " + processor +
        "\nRAM: " + ramSize + " megabytes" +
        "\nDisk: " + diskSize + " gigabytes" +
        "\nProcessor speed: " + processorSpeed + " gigahertz" + 
        "\nScreen Size: " + screenSize + " inches" + // 이 부분이 추가됨
        "\nWeight: " + weight + " kg";                 // 이 부분이 추가됨
        return result;
        }
    
    
}
 
cs

 

이때, 주의할 점이 하나 있다. superclass인 Computer class에서 일반적을 데이터 멤버들은 private로 하고, get/set 메서드를 통해 다른 클래스에서 접근할 수 있도록 하는데, 이 경우에 subclass인 Notebook class에서는 상속받은 그 데이터 멤버들을 클래스 객체를 생성하지 않고도 사용할 수 있을까?

private이면 다른 클래스에서 접근하지 못하도록 하는 것인데... 상속을 받은 것은 다른 클래스이긴 하지만 부모 자식 관계이기도 하고...?

정답은 "일반적인 다른 클래스와 동일하게 접근할 수 없다" 이다. 그래서 오버라이딩을 할 때에 부모클래스에서 private처리된 데이터 멤버들은 자식 클래스에서 자신의 데이터 멤버처럼 똑같이 사용할 수 없음에 주의하자. 이게 규칙이다.

 

이때 등장하는 것이 protected 이다. 

어떤 변수가 private이면 다른 어떤 클래스에서도 그 변수에 원칙적으로 접근할 수 없다. 만약, 상속의 관계라고 하더라도 마찬가지이다.

하지만, 어떤 변수가 protected이면 상속 받은 클래스에서만큼은 그 변수에 접근할 수 있도록 허용해준다. 

다른 클래스라고 하기에는 애매한 느낌이 들던 자식 클래스는 부모 클래스에서 protected 처리된 데이터 멤버들에는 자신의 데이터 멤버처럼 접근이 가능한 것이다.

 

protected를 사용하지 않더라도 오버라이딩을 할 때, superclass의 데이터 멤버가 private인 경우에도 subclass에서 에러없이 사용할 수 있는 방법이 있다.

아래 코드를 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
        super(man, proc, ram, disk, procSpeed); // 에러 해결!
        screenSize = screen;
        this.weight = weight;
    }
    // Overriding
//    public String toString() {
//        String result = "Manufacturer: " + manufacturer +
//        "\nCPU: " + processor +
//        "\nRAM: " + ramSize + " megabytes" +
//        "\nDisk: " + diskSize + " gigabytes" +
//        "\nProcessor speed: " + processorSpeed + " gigahertz" + 
//        "\nScreen Size: " + screenSize + " inches" + // 이 부분이 추가됨
//        "\nWeight: " + weight + " kg";        &nbs

 

Theme. 다형성(Polymorphism)

 

다형성이란, 수퍼클래스 타입의 변수(참조변수)가 서브 클래스 타입의 객체를 참조할 수 있다는 것이다.

즉, 위 예제에서 Computer class는 superclass이고, Notebook class는 subclass이므로,

Computer theComputer = new Notebook("Bravo", "Intel", 4, 240, 2/4, 15.07, 5);와 같이 

Computer 타입의 변수 theComputer가 Notebook 타입의 객체를 참조할 수 있다.

하지만, 역은 성립하지 않는다.(subclass 타입의 변수가 superclass 타입의 객체를 참조할 수 없다.)

 

이때, 만약 아래와 같이 superclass와 subclass가 각각 가지고 있는 toString() 메서드를 실행한다면, 둘 중 어떤 toString()메서드가 실행될까?

System.out.println(theComputer.toString());

이에 대해

1. theComputer는 Computer(superclass) 타입의 변수이므로 Computer class의 toString() 메서드가 실행된다고 하는 것은 static binding(정적 바인딩)이라고 한다.

2. theComputer가 Notebook(subclass) 타입의 변수이므로 Notebook class의 객체 내에 존재하는 toString() 메서드가 실행된다고 하는 것은 dynamic binding(동적 바인딩)이라고 한다.

Java에서는 동적 바인딩이 일어난다.

 

+ Recent posts