Java - 상속 (Extends)

상속은 부모 클래스를 상속받은 자식 클래스가 기능을 물려받는 걸 의미한다. 아래 예제 코드를 보자.

Bus 클래스는 아무것도 선언되지 않았지만, Car 클래스를 상속받고 있으며, passenger 클래스에서 Bus 클래스의 인스턴스를 생성해서 Car 클래스의 메서드 run()을 실행하는 코드이다.

Car.java

1
2
3
4
5
6
7
package devandy.java.inheritance;

public class Car {
void run() {
System.out.println(" 탑승하다.");
}
}

Bus.java

1
2
3
4
5
package devandy.java.inheritance;

public class Bus extends Car {

}

passenger.java

1
2
3
4
5
6
7
8
package devandy.java.inheritance;

public class Passenger {
public static void main(String[] args){
Bus b = new Bus();
b.run();
}
}

실행결과는 다음과 같다.

1
탑승하다.

아무것도 선언하지 않은 빈껍데기 일뿐인 Bus 클래스의 인스턴스를 생성했는데, 인스턴스에서 메서드를 호출할 수 있었다.

Bus 클래스가 Car 클래스를 상속받으면서 Car 클래스에 선언된 기능(메서드)들도 사용할 수 있게된 것이다.

상속을 활용하면 중복코드를 줄일 수 있는 장점이 있다. 또 이런 특징때문에 객체지향 프로그래밍의 핵심적인 개념으로 사용되기도 한다.

추가로 서브 클래스에서 수퍼 클래스의 기능을 직접 호출하고 싶을땐 super 키워드를 통해 사용할 수 있다. 위의 코드에서 passenger 클래스에서 run()을 호출하는 것이 아니라 Bus 클래스에서 run()을 호출하고 싶다면, 아래처럼 작성하면 된다.

1
2
3
4
5
package devandy.java.inheritance;

public class Bus extends Car {
super.run();
}

위의 예제코드를 활용하여 생성자를 추가하는 것으로 조금 변형을 해보았다.
Bus 클래스에서 생성자를 만들어보았다. 이 생성자는 숫자를 파라미터로 받아서 number+" 번 버스"라는 문장을 출력한다. passenger 클래스에서 숫자를 인자값으로 넣는 인스턴스를 생성후, run()을 실행했다.

Car.java

1
2
3
4
5
6
7
package devandy.java.inheritance;

public class Car {
void run() {
System.out.println(" 탑승하다.");
}
}

Bus.java

1
2
3
4
5
6
7
8
9
10
package devandy.java.inheritance;

public class Bus extends Car {
int number;

Bus(int number){
this.number = number;
System.out.print(number+"번 버스");
}
}

passenger.java

1
2
3
4
5
6
7
8
package devandy.java.inheritance;

public class Passenger {
public static void main(String[] args){
Bus b = new Bus(1415);
b.run();
}
}

실행결과이다.

1
1415번 버스 탑승하다.

위의 프로그램이 JVM에서 작동하는 순서는 다음과 같다.

1
Bus b = new Bus(1415);

우선 passenger 클래스의 main()이 실행되고, main()에서 호출한 Bus 클래스의 생성자가 실행된다.

1
b.run();

그리고 인스턴스의 메서드를 실행하기 위해 인스턴스의 클래스 타입인 Bus 클래스가 호출되고, 찾는 메서드가 Bus 클래스에 없으므로 수퍼 클래스인 Car 클래스에서 찾아서 호출된다.

예제코드로 확인해보자.

Car.java

1
2
3
4
5
6
7
package devandy.java.inheritance;

public class Car {
Car() {
System.out.println("Super 클래스, Car 생성자입니다.");
}
}

Bus.java

1
2
3
4
5
6
7
package devandy.java.inheritance;

public class Bus extends Car {
Bus() {
System.out.println("Sub 클래스, Bus 생성자입니다.");
}
}

CarExam.java

1
2
3
4
5
6
7
package devandy.java.inheritance;

public class CarExam{
public static void main(String[] args){
Bus b = new Bus();
}
}

실행결과는 다음과 같다.

1
2
Super 클래스, Car 생성자입니다.
Sub 클래스, Bus 생성자입니다.

Bus 클래스의 인스턴스를 선언했을뿐인데, Bus 클래스의 수퍼 클래스인 Car 클래스의 생성자가 먼저 호출되고, Bus 클래스의 생성자가 호출된걸 확인할 수 있다.

서브 클래스에 해당하는 객체를 호출하면, JVM에서 자동으로 해당 객체의 수퍼 클래스를 호출하는데, 이는 **JVM에서 서브 클래스에 super() 를 자동으로 생성**했기 때문이다.

위의 코드는 정확히 아래의 코드와 같다.

달라진 거라곤 서브 클래스에 해당하는 Bus 클래스의 생성자에 super() 를 추가한 것 뿐이다.

아까는 수퍼 클래스인 Car 클래스의 생성자가 기본 생성자였기 때문에 프로그래머가 super()를 생략하더라도, JVM에서 서브클래스에 자동으로 super()를 생성해주면서 수퍼 클래스의 생성자를 먼저 호출할 수 있었다.

반대로 수퍼 클래스의 생성자가 기본 생성자가 아닐 경우에는 프로그래머가 임의로 super()를 작성해주어야 한다.

1
Implicit super constructor Car() is underfined. Must explicityly invoke another constructor 

수퍼 클래스의 생성자를 기본 생성자에서 파라미터를 받는 생성자로 바꾸자 위와 같은 경고 메세지가 출력되었다.
수퍼 클래스인 Car()가 정의되지 않았으며, 다른 생성자를 반드시 명쾌하게 적용하라고 한다.

서브 클래스인 Bus 클래스의 생성자 내부에 임의로 super()를 생성하고, 여기에 수퍼 클래스의 생성자에 주입할 수 있는 문자열을 인자값으로 넣어주었다. 그리고 서브 클래스에서 입력받은 인자값을 출력할 수 있도록 Car 클래스의 생성자 속 출력문도 수정을 해주었다. 정상적으로 출력된 걸 확인할 수 있다.

이를 통해, 수퍼 클래스의 생성자가 기본 생성자일 경우는 프로그래머가 따로 호출하지 않아도 JVM에서 서브클래스의 생성자에 super() 를 생성해주어 서브클래스를 호출하는 것만으로 수퍼 클래스까지 함께 호출할 수 있었다. 또 수퍼 클래스의 생성자가 기본생성자가 아닐 경우에는 프로그래머가 서브 클래스의 생성자 내부에 super() 를 명시함으로써 마찬가지로 수퍼클래스를 호출할 수 있었다.