본문 바로가기

프로그래밍/Java 프로그래밍

자바의 상속

여지껏 자바의 기본 문법과 클래스에 대해 배웠습니다. 이제부터는 클래스의 속성에 대해 알아보겠습니다. 클래스는 객체지향언어에서 가장 기본적인 형태이고 이 클래스는 상속, 다형성, 캡슐화 등의 속성이 있습니다. 객체 지향프로그래밍에서 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있습니다. 프로그램에서는 부모 클래스를 상위 클래스라고 부르고, 자식 클래스를 하위 클래스 또는 파생 클래스라고 부릅니다.


상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여줍니다. 상속을 이용하면 부모 클래스에서 가지는 모든 멤버를 가져올 수 있고 부모 클래스의 수정으로 모든 자식 클래스들도 수정되는 효과를 가져오기 때문에 유지 보수 시간을 최소화할 수도 있습니다.



클래스 상속


현실에서 상속은 부모가 자식을 선택해서 물려주지만, 프로그램에서는 자식이 부모를 선택합니다. 자식 클래스를 선언할 때 어떤 부모 클래스를 상속받을 것인지 결정하고, 선택된 부모 클래스는 다음과 같이 extends 뒤에 기술합니다.


class 자식클래스 extends 부모클래스 {

  // 필드

  // 생성자

  // 메소드

}


예제를 통해 자세히 살펴보도록 하겠습니다.




위의 프로그램에서 DmbCellPhone 클래스는 CellPhone 클래스를 상속받습니다. 그래서 CellPhone 클래스에 있는 필드인 model과 color를 상속받고 powerOn(), powerOff(), bell(), sendVoice(), receiveVoice(), hangUp() 메소드도 상속받습니다. 하지만 DmbPhone에는 CellPhone에는 없는 channel 필드가 필요하고 turnOnDmb(), changeChannelDmb(), turnOffDmb() 메소드가 필요합니다. CellPhone에는 없고 DmbPhone에만 있는 기능입니다. 이렇듯 자식 클래스는 부모 클래스에 있는 기능은 모두 사용할 수 있으며 자기 자신만의 필드나 메소드를 가질 수 있습니다. 자식 클래스는 먼저 부모 클래스가 존재하여야만 합니다. 그래서 자식 클래스의 생성자를 부를때 부모 클래스를 생성자를 호출하게 되어있습니다.



부모 생성자 호출


현실에서 부모 없는 자식이 있을 수 없듯이 자바에서도 자식 객체를 생성하면, 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성됩니다. 위의 생성자 호출인 DmbCellPhoneExample의 라인 5를 보면 DmbCellPhone의 생성자만 호출한 것 처럼 보이지만 내부적으로 부모인 CellPhone 객체가 먼저 생성되고 자식인 DmbCellPhone 객체가 생성됩니다. 모든 객체는 클래스의 생성자를 호출해야지만 생성됩니다. 부모 객체도 예외가 아닙니다. 하지만 위의 예제에서는 DmbCellPhone의 생성자만 호출하고 부모클래스인 CellPhone의 생성자는 호출하지 않았습니다. 하지만 자식생성자가 호출되면 자동으로 부모생성자를 호출합니다. 소스코드에는 보이지 않지만 컴파일러가 자동으로 자식생성자안에 부모의 기본생성자인 super() 생성자를 호출합니다. 컴파일러가 아닌 우리가 직접 부모생성자를 호출할 수도 있습니다. 이 경우는 부모에 있는 생성자와 자식에서 호출하는 생성자가 같아야합니다. 예제를 통해 살펴보겠습니다.




People 클래스에는 2개의 생성자가 존재합니다. Student 클래스를 People 클래스를 상속받습니다. 그리고 하나의 생성자가 존재합니다. 이전 프로그램에서는 기본생성자를 이용해서 자동으로 자식 클래스에서 부모 클래스의 생성자를 호출했지만 이번에는 super를 이용해서 부모 생성자 중에서 매개변수가 있는 생성자를 호출했습니다. 부모 클래스에 2개의 생성자가 있기 때문에 Student 클래스의 라인 7과 8은 오류가 발생하게 됩니다. 그래서 super를 통해 부모 생성자를 호출하고 매개변수를 전달하게 됩니다.



메소드 재정의 (Method Overriding)


자식 클래스가 부모 클래스의 모든 메소드를 상속받는데 보통 부모 클래스는 포괄적인 개념이기 때문에 모든 자식 클래스의 메소드를 구현할 수는 없습니다. 그래서 일부 메소드는 자식 별로 다르게 수정되어 사용할 필요가 있습니다. 자바는 이런 경우를 대비해 메소드 재정(메소드 오버라이딩)의 기능을 제공합니다.


메소드 재정의 방법


메소드 재정의는 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 말합니다. 메소드를 재정의할 때는 다음과 같은 규칙에 주의해서 작성해야 합니다.


1. 부모 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)을 가져야 합니다.

2. 접근 제한을 더 강하게 재정의할 수 없습니다.

3. 새로운 예외를 throws할 수 없습니다.


접근 제한을 더 강하게 재정의 할 수 없다는 뜻은 부모 메소드가 public인데 자식 메소드가 default나 private으로 접근 제한을 수정할 수는 없다는 뜻입니다. 단, 반대는 가능합니다. 예외는 뒤에 배우도록 하겠습니다.




부모 클래스인 Calculator에 areaCircle 이라는 메소드가 있습니다. 하지만 자식 클래스인 Computer 클래스에도 areaCircle이라는 메소드가 있습니다. 이것이 메소드 오버라이딩인데 areaCircle 이라는 이름은 같지만 메소드의 내용이 약간 다릅니다. 자식 클래스에서 좀 더 정확한 값을 구하기위해 Math.PI를 사용했습니다. Calculator 객체는 calculator를 사용했을 경우는 부모 클래스의 areaCircle이 호출되지만 자식 클래스 객체에서는 자식클래스 메소드가 호출되어 출력값이 다르게 됩니다.



부모 메소드 호출


자식 클래스에서 부모 클래스의 메소드를 재정의하게 되면, 부모 클래스의 메소드는 숨겨지고 재정의된 자식 메소드만 사용하게 됩니다. 하지만 자식 클래스 내부에서 재정의된 부모클래스의 메소드를 호출해야 하는 상황이 발생한다면 super 키워드를 붙여서 부모 메소드를 호출할 수 있습니다.


super.부모메소드();



SuperAirplane 클래스에서 flyMode에 따라 일반비행을 하는지 초음속 비행을 하는지 결정하게 됩니다. 일반 비행인 경우는 부모 클래스의 fly()를 호출하면 되기 때문에 super.fly()를 사용했습니다.



final 클래스와 final 메소드


final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있는데, 해당 선언이 최종 상태이고 결코 수정될 수 없음을 뜻합니다. 우리는 이미 필드에서 final을 사용하여 상수를 만드는 것을 배웠습니다. 그럼 클래스와 메소드에는 어떤 의미가 있을까요?


상속할 수 없는 final 클래스


클래스를 선언할 때 final 키워드를 class 앞에 붙이면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 됩니다. 즉, final 클래스는 부모 클래스가 될 수 없습니다.


재정의할 수 없는 final 메소드


메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종 메소드이므로 재정의 할 수 없는 메소드가 됩니다. 즉, 부모 클래스에서 상속해서 자식클래스에서 사용할 수는 있지만 재정의할 수는 없는 것입니다.