이번에는 상속 관계의 장점을 알아보기 위해, 상속 관계에 다음 기능을 추가해보자.
기존 코드를 유지하기 위해 ex3 패키지를 새로 만들자
package ex3;
public class Car {
public void move() {
System.out.println("Car move");
}
public void openDoor() {
System.out.println("Car open door");
}
}
모든 차량에 문열기 기능을 추가할 때는 상위 부모인 Car 에 openDoor() 기능을 추가하면 된다.
이렇게 하면 Car 의 자식들은 해당 기능을 모두 물려받게 된다.
만약 상속 관계가 아니었다면 각각의 차량에 해당 기능을 모두 추가해야 한다.
package ex3;
public class ElectricCar extends Car {
public void charge() {
System.out.println("Electric car charged");
}
}
package ex3;
public class GasCar extends Car {
public void fillUp() {
System.out.println("Gas car is filled up");
}
}
기존 코드와 같다.
package ex3;
public class HydrogenCar extends Car {
public void fillHydrogen() {
System.out.println("Fill Hydrogen");
}
}
수소차를 추가했다. Car 를 상속받은 덕분에 move(), openDoor() 와 같은 기능을 바로 사용할 수 있다.
수소차는 전용 기능인 수소 충전(fillHydrogent()) 기능을 제공한다.
package ex3;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
electricCar.openDoor();
GasCar gasCar = new GasCar();
gasCar.move();
gasCar.fillUp();
gasCar.openDoor();
HydrogenCar hydrogenCar = new HydrogenCar();
hydrogenCar.move();
hydrogenCar.fillHydrogen();
hydrogenCar.openDoor();
}
}
Car move
Electric car charged
Car open door
Car move
Gas car is filled up
Car open door
Car move
Fill Hydrogen
Car open door
기능을 추가하고 클래스를 확장했다. 상속 관계 덕분에 중복은 줄어들고, 새로운 수소차를 편리하게 확장(extend)한 것을 알 수 있다.
부모 타입의 기능을 자식에서는 다르게 재정의 하고 싶을 수 있다.
예를 들어서 자동차의 경우 Car.move() 라는 기능이 있다. 이 기능을 사용하면 단순히 "Car move" 라고 출력한다. 전기차의 경우 보통 더 빠르기 때문에 전기차가 move() 를 호출한 경우에는 "EletricCar move fast" 라고 출력을 변경하고 싶다.
이렇게 부모에게서 상속 받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩(Overriding)이라 한다.
package extends1.overriding;
public class Car {
public void move() {
System.out.println("Car move");
}
public void openDoor() {
System.out.println("Car open door");
}
}
전기차 부분만 Override 를 붙이자.
package extends1.overriding;
public class ElectricCar extends Car {
@Override
public void move() {
System.out.println("ElectricCar move fast");
}
public void charge() {
System.out.println("Electric car charged");
}
}
ElectricCar 는 부모인 Car 의 move() 기능을 그대로 사용하고 싶지 않다. 메서드 이름은 갇지만 새로운 기능을 사용하고 싶다.
그래서 ElectricCar 의 move() 메서드를 새로 만들었다.
이렇게 부모의 기능을 자식이 새로 재정의하는 것을 메서드 오버라이딩이라 한다.
이제 ElectricCar 의 move() 를 호출하면 Car 의 move() 가 아니라 ElectricCar 의 move() 가 실행된다.
package extends1.overriding;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
electricCar.openDoor();
GasCar gasCar = new GasCar();
gasCar.move();
gasCar.fillUp();
gasCar.openDoor();
}
}
ElectricCar move fast
Electric car charged
Car open door
Car move
Gas car is filled up
Car open door
@Override
@ 이 붙은 부분을 애노테이션이라 한다. 애노테이션은 주석과 비슷한데, 프로그램이 읽을 수 있는 특별한 주석이라 생각하면 된다. 애노테이션에 대한 자세한 내용은 따로 설명한다.
이 애노테이션은 상위 클래스의 메서드를 오버라이드하는 것임을 나태낸다.
이름 그대로 오버라이딩한 메서드 위에 이 애노테이션을 붙여야 한다.
컴파일러는 이 애노테이션을 보고 메서드가 정확히 오버라이드 되었는지 확인한다. 오버라이딩 조건을 만족시키지 않으면 컴파일 에러를 발생시킨다. 따라서 실수로 오버라이딘을 못하는 경우를 방지해준다. 예를 들어서 이 경우에 만약 부모에 move() 메서드가 없다면 컴파일 오류가 발생한다. 참고로 이 기능은 필수는 아니지만 코드의 명확성을 위해 붙여주는 것이 좋다.
메서드 오버라이딩은 다음과 같은 까다로운 조건을 가지고 있다.
다음 내용은 아직 학습하지 않은 내용들도 있으므로 모두 이해하려고 하기 보다는 참고만 한다.
지금은 단순히 부모 메서드와 같은 메서드를 오버라이딩 할 수 있다. 정도로 이해하면 충분하다.
메서드 오버라이딩 조건