목표
자바의 상속에 대해 학습하기
자바 상속의 특징
상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 이용하여 공통 클래스를 작성하면, 적은 양의 코드를 추가하여 새로운 클래스를 만들 수 있고, 공통 부분에 대한 관리가 용이해진다. 자바에서 상속을 구현하는 방법은 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 extends와 함께 써준다. 상속해주는 클래스를 부모(조상, parent) 클래스, 상위 클래스 라고 하며, 상속받는 클래스를 자식(child) 클래스, 하위 클래스라고 한다.
class Parent {
int age;
}
class Child extends Parent {
void play() { }
}
하위 클래스는 상위 클래스의 모든 멤버를 상속받으므로 아래와 같은 포함관계를 갖는다. 이 때, 생성자와 초기화 블럭은 상속되지 않는다. 하위 클래스에서 추가된 멤버들은 상위 클래스에 영향을 주지 않는다.

클래스 간의 포함관계를 가지는 방법은 다른 클래스를 멤버변수의 타입으로 가지는 방법도 있다.
class Child {
Parent parent = new Parent();
}
상속하는 것과 멤버변수로 선언하는 방법을 구분할 수 있는 방법은 '하위클래스는 상위클래스이다.' 라는 문장이 자연스러운지 보면된다.
class Father {
Son son = new Son();
Daughter daughter = new Daughter();
}
class Daughter extends Father {
}
'딸은 아버지다' 자연스럽지 못하므로 상속이 아닌 멤버변수 활용을 통한 포함관계로 가는 것이 맞다.
class Animal {
Dog dog = new Dog();
Cat cat = new Cat();
}
class Cat extends Animal {
}
반면에 '고양이는 동물이다', '개는 동물이다' 의 문장은 자연스럽다. 따라서 동물을 상위클래스로 하여 상속관계를 맺을 수 있다. 좀 더 명확히는 '하위클래스는 상위클래스의 한 종류이다.' 의 문장을 만족해야한다.
C++ 에서는 여러 상위클래스로부터 상속받는 것이 가능하지만, 자바에서는 단일 상속만을 허용한다. 다중 상속시에, 다른 상위클래스에 같은 멤버변수가 있을 경우, 어떤 변수를 참조해야할 지 알 수 없다. 또한 같은 메소드 선언부를 갖는 메소드가 존재할 경우에도, 어떤 메소드를 호출해야할지 구별하기가 힘들다. 이와 같은 다중상속을 허용할 때의 문제점 때문에 자바에서는 다중상속을 과감히 포기했다.
super 키워드
super는 하위 클래스에서 상위 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수다. 인스턴스 변수와 매개변수 또는 지역변수의 이름이 같을 때, this를 붙여서 구별했듯이 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때는 super를 붙여서 구별할 수 있다.
class Parent {
int x = 10;
}
class Child extends Parent {
int x = 20;
void printThisAndSuper() {
System.out.println("x=" + x); // x=20
System.out.println("this.x=" + this.x); // this.x=20
System.out.println("super.x=" + super.x); // super.x=10
}
}
super()
this()와 마찬가지로 super() 역시 생성자이다. 하위 클래스의 생성자는 첫 줄에 super()를 수행해야하는데, 하위 클래스의 초기화에서 상위 클래스의 멤버변수를 사용할 수도 있기 때문에, 상위 클래스의 초기화가 먼저 이루어져야 하기 때문이다. 하위 클래스의 생성자에 첫 줄에 super();가 없으면 컴파일러가 자동으로 첫 줄에 super()를 추가해준다. 이 때, 모든 클래스는 최종적으로 Object 클래스를 상속하기 때문에 모든 클래스의 생성자에는 super();가 자동으로 추가된다.
메소드 오버라이딩
하위 클래스에서 상위 클래스로부터 상속받은 메소드의 내용을 변경하는 것을 오버라이딩이라고 한다. 오버라이딩은 메소드의 내용만을 새로 작성하는 것이므로 메소드의 선언부는 상위 클래스의 메소드와 완전히 일치해야한다. 이 때, 접근제어자는 상위 클래스보다 넓은 범위로 변경할 수 있으며,선언된 예외를 상위 클래스보다 줄이는 것도 가능하다.
class Parent {
protected void method() throws IOException, SQLException {
System.out.println("Parent method");
}
}
class Child extends Parent {
public void method() throws IOException {
System.out.println("Child extends Parent method");
}
}
다이나믹 메소드 디스패치
메소드 디스패치는 어떤 클래스의 메소드를 실행할지 결정하여 실제로 수행하는 것을 말한다.
정적 메소드 디스패치 (static method dispatch)
컴파일 시점에서, 컴파일러가 어떤 메소드를 수행할 지 정확히 알고 있는 경우이다. 일반적인 클래스의 메소드는 모두 정적으로 컴파일 타임에 바인딩된다. 오버로딩 된 메소드도 매개변수와 반환값이 명확히 다르므로 메소드를 특정할 수 있으며, 상속관계 있는 경우에도 오버라이딩 된 함수가 없다면 정적 디스패치에 해당한다.
class Parent {
}
class Child extends Parent {
}
Parent parent = new Parent();
Child child = new Child();
동적 메소드 디스패치 (dynamic method dispatch)
'하위 클래스는 상위 클래스이다' 를 만족한다는 상속의 제약사항처럼 참조변수를 상위 클래스의 타입으로, 하위 클래스의 인스턴스를 생성할 수 있다. 이 때, 런타임에서야 animal 이라는 참조변수가 가리키는 주소가 Dog의 인스턴스임을 알게 된다. 따라서 animal.cry가 Animal 이 아닌, Dog의 cry를 수행해야 함을 런타임에 알게 되는데 이를 동적 메소드 디스패치라 한다.
class Animal {
void cry() {
}
}
class Dog extends Animal {
void cry() {
System.out.println("멍멍");
}
}
Animal animal = new Dog();
animal.cry();
추상 클래스
단순한 상속의 경우, 상위 클래스에서 정의된 메소드를 하위 클래스에서 오버라이드하지 않는다면 그대로 사용하게 된다. 예를 들어 Animal 과 Animal을 상속하는 Dog, Cat 클래스가 아래와 같이 정의되어 있다고 가정해보자.
class Animal {
void cry() {
}
}
class Dog extends Animal {
void cry() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
void cry() {
System.out.println("냥냥");
}
}
상위 클래스인 Animal은 울음소리를 정의할 수 없어서 비워두었다. 이 후에 Duck 이라는 Animal을 상속하는 클래스를 추가하다가 실수로 cry() 메소드를 오버라이드 하지 않으면 어떻게 될까?
class Duck extends Animal {
}
class Application {
public static void main(String[] args) {
Duck duck = new Duck();
duck.cry();
}
}
결과는 아무것도 출력되지 않는다. 즉, 오리가 울지 못하게 된다.
이처럼 하위 클래스에서 반드시 오버라이드 하도록 강제해야 하는 경우에 이 메소드를 추상 메소드 (abstract method) 로 정의하며, 1개 이상의 추상 메소드를 포함하는 클래스는 추상 클래스 (abstract class) 로 정의해야한다. 추상 클래스는 new를 사용할 수 없고, 인스턴스를 만들 수 없다. 따라서, 추상 클래스를 인스턴스로 생성하기 위해서는 이를 상속받는 클래스를 정의해야 하며, 추상 클래스를 상속받은 클래스는 추상 메소드를 반드시 오버라이드 해야한다. 만일 오버라이드 하지 않을 경우, 해당 클래스도 추상 클래스로 선언되어야 한다.
abstract class Animal {
abstract void cry() {
}
}
class Dog extends Animal {
void cry() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
void cry() {
System.out.println("냥냥");
}
}
// class Duck extends Animal {
// }
abstract class Duck extends Animal {
}
class Donald extends Duck {
void cry() {
System.out.println("도날드덕덕");
}
}
final 키워드
final 키워드는 클래스, 변수, 메소드에 함께 사용할 수 있는 키워드 이다.
final class
public final class Parent {
}
클래스 앞에 final이 붙었다면, 상속을 허락하지 않겠다는 의미이다. 이 클래스를 extends 시도할 경우 컴파일 에러가 난다.
final variable
public class Parent {
final int birthYear;
}
변수 앞에 final이 붙었다면, 변경 불가능한 상수가 된다. 최초 한 번 초기화 후에는 값을 변경할 수 없다.
final method
public class Parent {
final void move() {
}
}
메소드 앞에 final이 붙었다면, 오버라이딩을 금지하게 된다.
Object 클래스
Object 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다. 아무것도 상속받지 않은 클래스를 정의하면 컴파일러가 자동으로 Object 클래스를 상속받도록 컴파일 하여, 모든 클래스의 최상위에 Object 클래스가 위치하도록 한다.
class Student {
}
// 컴파일러 자동 추가 후,
class Student extends Object {
}
이 때문에 모든 객체는 Object 클래스의 멤버변수와 메소드에 접근이 가능한데, toString이나 equals와 같은 메소드를 따로 정의하지 않고도 사용할 수 있었던 이유도 이 메소드들이 Object 클래스에 정의된 것들이기 때문이다.
※ 참고
Java의 정석
최근 7년동안 자바 분야의 베스트 셀러 1위를 지켜온 '자바의 정석'의 최신판. 저자가 카페에서 12년간 직접 독자들에게 답변을 해오면서 초보자가 어려워하는 부분을 잘 파악하
m.yes24.com
스프링 입문을 위한 자바 객체 지향의 원리와 이해
자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량 애플리케이션 프레임워크인 스프링은 자바와 객체 지향이라는 기반 위에 굳건히 세워져 있다. 따라서 스프링을 제대로
m.yes24.com
mongzza/java-study
https://github.com/whiteship/live-study/issues 자바 온라인 스터디 - mongzza/java-study
github.com
'Java | Spring' 카테고리의 다른 글
| 인터페이스 (0) | 2021.02.24 |
|---|---|
| 패키지 (0) | 2021.02.11 |
| 클래스 (0) | 2021.02.11 |
| 연산자 (0) | 2021.02.05 |
| 자바 데이터 타입, 변수 그리고 배열 (0) | 2021.02.02 |