상속이란 한 클래스가 다른 클래스의 상태(State)와 행위(Behavior)을 활용해 새로운 클래스를 정의하는 것 의미한다. 이는 캡슐화, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나입니다. 그리고 재밌는 점으로 모든 자바의 클래스는 Object 클래스를 조상으로 가지고 있다. 다시말해 모든 자바 클래스는 Object의 속성을 가지고 있다.
- 기존에 작성된 클래스를 재활용 가능
- 자식 클래스 설계 시 중복되는 요소 감소
- 계층적 구조를 통해 다형성의 토대 제공
- 상속을 해주는 부모 클래스를 Parent 또는 Super Class라고 부른다
- 상속을 받는 자식 클래스를 Child 또는 Sub Class라고 부른다
- 다단계 상속은 가능하지만, 상속을 동시에 여러개를 받을 순 없다(다중 상속X)
- Object 클래스는 모든 클래스의 조상
상속을 통해 중복되는 필드나 메소드를 줄일 수 있고 재사용성이 증가하면 무조건 상속은 많이 할 수록 좋다고 생각할수도 있다. 하지만, 반대로 이것이 문제가 될 수 있다.
개인적으로 상속의 문제 중 가장 대표적인 것은 아래와 같을 것 같다.
클래스 간의 높은 의존도는 특정 부모 클래스의 기능에 추가/변경이 일어날 때 이를 하위 클래스가 활용중이었다면 연쇄 작용이 일어나기 때문에 수정 후에도 하위 클래스가 문제 없이 작동하는지 확인해야 한다.
시스템 복잡성 증가는 어떻게 보면 위의 문제의 연장선이라고도 볼 수 있는다. 상속되는 관계가 많아지면 어떤 기능을 수정할 때 시스템 차원에서 일어날 수 있는 변화를 예측하기 힘들어진다는 문제가 있다.
그래서 상속은 객체 지향 프로그래밍에서 중요한 특징이지만 적절히 잘 쓰는 것이 중요하고 생각한다.
super 키워드는 부모 클래스의 주소를 가리키는 참조 변수로서 자식 클래스가 부모 클래스의 필드나 메서드를 호출할 수 있게 해준다.
주의할 점은 위의 super() 메서드는 super 키워드와 같이 부모 클래스를 참조하는 게 아니라 부모 클래스의 기본 생성자를 호출하는 키워드니 혼동하면 안된다.
오버라이딩이란 상속받은 자식 클래스에서 상속해준 부모 클래스의 메서드와 같은 시그니쳐를 갖는 메소드로 재정의하는 것이다.
특히 자바에서 자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메서드를 상속받는데, 이때 상속받은 메서드를 그대로 사용해도 되지만 메서드 오버라이딩을 통해 필요한 다른 동작을 하도록 재정의 가능하다.
메서드 시그니쳐란 메서드 이름과 매개변수 리스트의 조합을 의미한다
오버라이딩의 조건은 아래와 같다.
- 같은 메서드 시그니쳐, 부모의 리턴 타입으로 변환 가능한 리턴 타입
- 접근 제어자 범위 축소 불가
- 더 큰 범위의 예외 선언 불가
메서드 디스패치란 어떤 메서드를 호출할지 결정하여 실제로 실행시키는 과정이다. 그리고 다이나믹이란 Runtime 즉, 실행 중에 어떤 메서드를 호출할지 결정한다는게 다이나믹 메서드 디스패치의 의미이다.
자바에서 이러한 기능을 제공하는 이유는 런타임 전에는 객체가 생성이 되지 않기 때문이다. 그래서 자바는 런타임에 오버라이딩된 메서드가 실행된다. 예를 들어보자면 아래와 같다.
public class Animal {
public void move() {
System.out.println("Animals can move");
}
}
public class Cat extends Animal {
@Override
public void move() {
System.out.println("Cat can run");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal(); // Animal 참조, Animal 객체
Animal cat = new Cat(); // Animal 참조, Cat 객체
animal.move(); // Animal 클래스에 정의된 메서드 실행
cat.move(); // Cat 클래스에 정의된 메서드 실행(다이나믹 메소드 디스패치)
}
}
컴파일 타임에는 참조 변수의 타입만을 확인하지만, 런타임에는 JVM이 객체의 타입을 파악하고 그 객체 정의된 메서드를 실행한다.
우선 추상(abstract)란 단어에 대해서 짚고 넘어가야 한다. 객체 지향 프로그래밍에서 "추상화"이란 필요한 부분만을 보여주고 나머지 디테일한 부분은 숨기는 것을 의미한다. 즉, 내가 어떤 객체의 디테일한 부분들을 알지 못해도 필요한 기능을 사용하는 것에 전혀 지장이 없는 것이 "추상화"의 목적이다.
이런 의미에서 추상 클래스를 다시 생각해보자
클래스란 기본적으로 객체를 만들기 위한 설계도 역할로 상태와 행위가 정의되어 있다. 하지만 추상 클래스는 객체의 상태 또는 행위의 구체적인 내용 없이 보여주기 위한 설계도로서 그 자체가 객체를 만들 수 없는 클래스이다. 하지만 여느 클래스와 같이 상속을 줄 수는 있다.
같은 의미로 추상 메서드를 유추해보면 메서드의 선언만 있고 실행부가 정의되어 있지 않은 메서드라는 것을 알 수 있다. 그래서 이 추상 메서드는 상속받은 자식 클래스에서 오버라이딩을 통해 구현부를 만들어줘야 한다.
이러한 특징 때문에 추상 메서드가 하나라도 있는 클래스는 추상 클래스가 된다.
final 키워드는 Java에서 변경 불가능을 의미한다.
- 변수의 값 변경 불가
- 메서드는 오버라이드 불가
- 클래스는 서브 클래싱 불가
java.lang 패키지에 존재하는 Object 클래스는 모든 클래스의 조상이고 알아두면 좋은 메서드들이 있다.
객체의 복사본을 만들어 반환하는 메서드이고 복사가 불가능한 경우 예외를 발생시킨다. 주의할 점은 clone() 은 얕은 복사이다.
다른 객체와 비교해서 같은 객체인지를 비교하는 메서드이다. 이 메서드를 오버라이딩 하려면 hashCode() 메서드 또한 오버라이딩 해주어야 한다.
hashCode()는 객체가 가지고 있는 고유한 값이다 즉, 이 hashCode()값이 같다면 두 객체는 같은 객체이다. 그래서 equals()를 오버라이딩할 때 hashCode() 또한 오버라이딩 해야한다.
객체를 문자열 형태를 반환한다. 특정 정보를 원하는 포맷의 문자열로 반환하고자 한다면 오버라이딩 해주면 된다.