자바 상속의 특징
자식 클래스는 부모 클래스의 상태와 기능을 사용 가능
1 | public class Hyundai extends Genesis { |
1 | public class Genesis { |
1 | public class Driver { |
1 | 당신이 선택한 차종은 g70이며, 가격은 48500000원입니다. |
Genesis
클래스와 Genesis
클래스를 상속(extends)받는 Hyundai
클래스이다. 그리고 이를 Driver
클래스에서 인스턴스를 생성해서 사용하는 코드이다.
부모클래스로부터 상속받는 자식클래스에서 부모 클래스의 상태와 기능을 확장하여 할 수 있다는 점에서 상속을 할 때 사용하는 키워드가
extends
이다.
위의 코드에서 Sedan
과 Suv
클래스 코드는 굳이 첨부하지 않았다.
main 메서드가 있는 Driver
클래스를 보면, Hyundai
객체의 인스턴스를 생성했는데, Genesis
의 g70
를 호출한걸 확인할 수 있다.
이게 가능한 이유는 Hyundai
클래스가 Genesis
를 상속 받기 때문에 가능한 일이다.
반면 Genesis
객 인스턴스로 생성해서 sonata
를 호출하면 어떨까?
1 | public class Driver { |
컴파일 에러가 발생한다. genesis
인스턴스에서는 sonata
를 찾을 수 없기 때문이다.
두 객체의 관계는 다음과 같다.
Genesis
객체를 통해서는 g70
만 접근할 수 있지만, Genesis
를 상속받는 Hyundai
는 sonata
, tucson
, g70
모두 접근이 가능하다. 상속은 부모에서 자식에게 가능하지만, 자식에서 부모로는 불가능하다.
다중상속
자바에서는 다중상속을 불허 한다. 이유는 아래의 코드를 통해 확인해보자.
1 | public class ParentA { |
1 | public class ParentB { |
1 | public class Son extends ParentA, ParentB { |
위의 코드는 컴파일 에러가 발생한다. 이유는 Son
클래스에서 다중상속을 허용하지 않기 때문이다. 그렇다면, 자바는 왜 다중상속을 허용하지 않는걸까?
위 코드가 컴파일 에러가 발생하지 않는다고 가정해보자. Son
클래스의 name()
에서 super.familyName
은 무엇을 가져올 수 있을까?
자식 클래스에서 부모 클래스의 속성 또는 기능을 호출하려고 하는데, 위에서처럼 변수명은 같지만 할당된 리터럴이 다른 경우 JVM은 어느것을 호출해야할지 알 수 없게된다.
이런 경우를 다이아몬 문제(Diamond Problem) 이라고 한다. 따라서 자바에서는 다중상속을 지원하지 않는다.
그러나 인터페이스를 통해 다중상속을 비슷하게 구현할수 있긴 하다.
상속관계에서 메서드 호출
1 | class A { |
1 | class B extends A { |
1 | class C extends B { |
1 | class StudyHalle { |
A -> B -> C 순서로 상속관계로 연결된 객체들이다. 상속관계에서 최하위 객체인 C 객체의 인스턴스를 생성해서 printClassName()
와 hello()
를 호출해보았다.
1 | Hello from B |
printClassName()
은 B 객체에서 오버라이딩을, hello()
는 C 객체에서 오버라이딩을 한 메서드들이다.
호출하면, 상속관계에서 가장 가까운 객체의 메서드를 호출 하는 것을 알 수 있다.
업캐스팅/다운캐스팅
상속관계에서는 부모 클래스와 자식 클래스 간에 형변환(Casting)이 가능하다.
- 업캐스팅
- 자식 클래스의 객체가 부모 클래스로 형변환
- 다운캐스팅
- 부모 클래스의 객체가 자식 클래스로 형변환
상속관계에서 업캐스팅과 다운캐스팅이 어떻게 진행되는지 확인해보자.
1 | class Person { |
Student -> Person 의 상속관계이다. Person 타입의 인스턴스 객체 person
을 자식 객체 타입인 Student 타입으로 다운 캐스팅하니까 모든 멤버 필드에 접근이 가능해졌다.
반면 자식 클래스 타입의 Student를 부모 객체 타입인 Person 타입으로 업 캐스팅하니까 기존엔 접근가능했던 자식 객체의 멤버 필드에 접근이 불가능해졌다. 형변환이 되었기 때문에 당연히 접근이 안되는 것이다.
super 키워드
super
라는 키워드도 있는데, super()
라는 메서드도 존재한다. 각각의 차이는 다음과 같다.
super
: 부모 클래스의 필드, 메서드 호출super()
: 부모 클래스의 생성자를 호출하는 메서드
코드를 통해 확인해보겠다. 생활코딩의 코드(출처)를 가져왔다.
1 | class Cal { |
여기서 사용한 super()
를 인텔리J에서 확인해보면 다음과 같이 출력된다.
Cal
클래스를 상속받은 Cal3
클래스는 부모 클래스가 갖고있는 생성자를 반드시 호출 해야 한다.
이유는 부모 클래스인 Cal
클래스의 인스턴스를 생성하게 되면, 생성자가 우선적으로 호출되는데, Cal
클래스의 자식 클래스 역시 이 생성자를 호출해야지만 Cal
클래스를 온전히 계승한다고 볼 수 있기 때문이다.
이번엔 super
키워드를 사용하는 코드를 추가해봤다.
1 | class Cal { |
1 | 30 |
Cal3
클래스에서 부모 클래스의 변수를 호출하는 변수를 선언했다. main 메서드에서 콘솔에 출력되는 수가 30인데, Cal3 클래스의 변수 v3
이 부모 클래스의 v3(10)
와 부모 클래스의 v2
(생성자를 통해 들어온 20)의 합을 가져왔기 때문이다.
메소드 오버라이딩
오버라이딩은 부모 클래스의 메서드를 변형해서 사용하는 것을 의미한다. 부모로부터 상속받은 기능을 자신의 입맛대로 바꿔서 사용하는 것이다.
객체지향을 배울때 오버라이딩과 함께 배우는 개념으로 오버로딩이 있는데, 오버로딩은 부모 클래스의 메서드와 이름만 같을뿐 파라미터 갯수를 다르게 하여 다른 메서드로 사용하는걸 의미한다. 객체지향의 주요 개념중 하나인 다형성(Polymorphism) 이 사용된 개념이다.
1 | class A { |
오버라이딩과 오버로딩의 차이
그럼 오버라이딩과 오버로딩의 차이는 무엇일까?
오버로딩이 기능의 확장이라면, 오버라이딩은 기능의 재정의이라고 할 수 있다.
아래의 코드를 보자.
1 | class Cal1 { |
1 | -10 |
여기서 오버라이딩 을 시도한 코드는 Cal3
클래스의 minus()
이다. 부모 클래스의 메서드를 가져와서 기능을 변형시켰다.
Inheritance
클래스의 main 메서드에서 콘솔에 출력한 결과는 부모 클래스의 함수와 달랐다. 기능의 변경이 확인 된 것이다.
오버로딩 을 시도한 코드는 Cal3
클래스의 sum()
이다. 오버라이딩은 이름만 같을뿐 다른 기능이므로 오버로딩을 시도하면 기능이 하나 더 생긴다. 따라서 Cal3
클래스의 인스턴스로는 sum()
이라는 이름의 메서드 2개를 호출할 수 있다.
오버라이딩은 상속관계에서만 사용되지만, 오버로딩은 같은 클래스내에서도 사용할 수 있다는 특징이 있다.
다이나믹 메서드 디스패치 (Dynamic Method Dispatch)
메서드 디스패치 란, 런타임 시점(실행 시점)이 아닌 컴파일 시점에 어떤 메서드가 호출될지를 결정할 수 있는지 여부 를 정의한다.
메서드 디스패치는 두가지로 분류된다.
- 정적 메서드 디스패치 (Static)
- 컴파일시 결정되는 의존 관계
- 컴파일 시점에 어떤 메서드가 호출될지를 결정하는 코드
- 다이나믹 메서드 디스패치 (Dynamic)
- 런타임시 결정되는 의존 관계
- 컴파일 시점에는 어떤 메서드가 호출될지 알지 못하나 런타임 시점에 메서드가 결정되는 코드
추상 클래스
클래스가 설계도라면, 추상 클래스는 미완성 설계도이다. 추상 메서드를 작성할 일이 있을때, 추상 메서드를 작성하는 클래스에 abstract
라는 키워드를 작성하면, 추상 클래스가 된다.
단독으로 사용될 수 없다. 인스턴스로 생성할 수 없기 때문에 추상 클래스는 반드시 상속으로 구현해서 자식 클래스에서 인스턴스화해서 사용해야한다.
1 | abstract class A { |
1 | 15 |
추상 클래스 A를 상속받는 Class B, Class C를 통해서 sum()
이라는 메서드를 서로 다르게 구현한 코드이다.
추상 클래스는 인스턴스로 생성할 수 없다. 추상 클래스를 상속받은 자식 클래스를 통해서만 인스턴스화 할 수 있다. 같은 이유로 추상 클래스는 final 키워드로 선언할 수 없다.
그럼 추상 클래스는 언제 사용해야할까? 추상 메서드를 선언하려면 메서드를 선언하는 클래스가 추상 클래스여야 한다. 고로 추상 메서드가 필요할때가 추상 클래스가 필요한 시점이 된다.
그럼 추상 메서드는 언제 필요할까? 추상 메서드란 선언부만 있고, 구현부는 없는 메서드 를 말한다. 인터페이스처럼 껍데기만 있는 것이다. 따라서 클래스간의 관계에서 공통 기능을 인터페이스 또는 추상클래스의 추상메서드로 분리할 수 있을 것이다.
여기까지 보면, 인터페이스와 다르지 않은것 같다. 인터페이스와 추상클래스는 어떤 차이가 있는걸까?
8주차 스터디를 하면서 인터페이스를 공부하면서 인터페이스와 추상클래스의 차이를 정리해보았다.
인터페이스와 추상클래스의 차이
final 키워드
final
키워드는 객체의 모든 대상에 적용이 가능하며, 불가변적인 특징을 갖게된다.
final 키워드 적용대상
대상 | 의미 |
---|---|
클래스 | 변경될 수 없는 클래스, 확장(상속)이 불가능해진다. |
메서드 | 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 할 수 없다. |
멤버변수 | 변수 앞에 final 이 붙으면, 값을 변경할 수 없는 상수가 된다. |
지역변수 |
상수의 초기화
final
키워드를 사용해서 상수를 선언하더라도 생성자를 통해서 초기화를 할 수 있다. 만약 생성자를 통한 상수의 초기화가 불가능해지면, 이를 상속하는 모든 클래스에서 똑같은 상수 리터럴을 사용해야할 것이다.
출처 : 남궁성의 Java의 정석
Object 클래스
java.lang 패키지의 대표적인 클래스 중 하나면서, Java에서 사용하는 모든 클래스의 조상이다. 따라서 Object 클래스의 메서드는 모든 클래스에서 바로 사용할 수 있다.
예를 들면, String 클래스에서 equals()
를 사용할 수 있는건, equals()
가 Object 클래스의 메서드이고, Object 클래스를 String 클래스가 상속하기 때문에 가능하다.
Object 클래스의 메서드
Object 클래스의 메서드 | 설명 |
---|---|
public final native Class> getClass() | 객체 자신의 클래스 정보를 담는 Class 인스턴스를 반환한다. |
public native int hashCode() | 객체 자신의 해쉬코드를 반환한다. |
public boolean equals(Object obj) | 객체 자신과 객체 obj가 같은 객체인지 알려준다. |
protected native Object clone() throws CloneNotSupportedException | 객체 자신의 복사본을 반환한다. |
public String toString() | 객체 자신의 정보를 문자열로 반환한다. |
public final native void notify() | 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다. |
public final native void notifyAll() | 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다. |
public final void wait() throws InterruptedException | 다른 쓰레드가 notify() 또는 notifyAll()을 호출할때까지 현재 쓰레드를 잠시 대기하도록 한다. |
public final native void wait(long timeoutMillis) throws InterruptedException | 다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간(timeout, nanos)동안 기다리게 한다. |
더 이상 참조하지 않는 객체를 GC에 의해 호출하도록 하는 finalize()
는 deprecate되었다.
그동안 finalize()
는 아래의 이유로 사용하지 않는 것이 권고 되었다는데 결국 deprecate되었다.
- 객체가 이용할 수 없게된 시점부터
finalize()
가 실행되는 시점까지 긴 시간이 소요될 수 있는데, 이 시간은 GC 알고리즘을 추종함. - 반드시 실행된다는 보장이 없다.
- 예외가 발생하더라도 예외가 무시된다.
- 성능 저하가 발생한다.