- 추상 클래스
- 인터페이스
- 클래스 상속 시 주의할 부분
- 상속 관련 지시어
상속
추상 클래스
Animal이라는 추상 클래스를 구현한 Cat, Penguin 클래스를 만들어보자.
자바 코드와 코틀린 코드를 비교해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public abstract class Animal {
protected final String species;
protected final int legCount;
public Animal(String species, int legCount) {
this.species = species;
this.legCount = legCount;
}
abstract public void move();
public String getSpecies() {
return species;
}
public int getLegCount() {
return legCount;
}
}
|
1
2
3
4
5
6
7
| abstract class Animal(
protected val species: String,
protected val legCount: Int,
) {
abstract fun move()
}
|
클래스 자체의 문법을 제외하고 크게 다른 부분은 보이지 않는다.
1
2
3
4
5
6
7
8
9
10
| public class Cat extends Animal {
public Cat(String species) {
super(species, 4);
}
@Override
public void move() {
System.out.println("고양이가 걸어다닙니다.");
}
}
|
1
2
3
4
5
6
7
8
| class Cat(
species: String
) : Animal(species, 4) {
override fun move() {
println("고양이가 걸어다닙니다.")
}
}
|
- 주생성자를 통해 생성자를 대체했다.
- extends 대신 :를 사용한다.
- species: String과 다르게 컨벤션에 따라 상속을 표현하는 : 사용 시 괄호 옆에 띄어쓰기를 한다.
- 상속받을 경우 상위 클래스의 생성자를 무조건 바로 호출해주어야 한다.
- Override 애노테이션 대신 override를 필수적으로 붙여주어야 한다.
그렇다면 wingCount라는 필드가 존재하는 Penguin의 경우 어떨까?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public final class Penguin extends Animal {
private final int wingCount;
public Penguin(String species) {
super(species, 2);
this.wingCount = 2;
}
@Override
public void move() {
System.out.println("펭귄이 움직인다.");
}
@Override
public int getLegCount() {
return super.legCount + this.wingCount;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Penguin(
species: String,
) : Animal(species, 2) {
private val wingCount: Int = 2
override fun move() {
println("펭귄이 움직인다.")
}
override val legCount: Int
get() = super.legCount + this.wingCount
}
|
코틀린에서 super.legCount를 사용할 때 에러가 발생한다.
상위 클래스의 프로퍼티에서 수정해주어야 한다.
1
2
3
4
| class Animal(
protected val species: String,
protected open val legCount: Int
)
|
- 추상 프로퍼티가 아니라면, 상속 시 open 키워드를 꼭 붙여야 한다.
- 상위 클래스에 접근하는 키워드는 super로 동일하다.
자바와 코틀린 모두 추상클래스는 인스턴스화 할 수 없다.
인터페이스
Flyable과 Swimmable이라는 인터페이스를 펭귄이 구현하도록 만들어보자.
1
2
3
4
5
6
7
8
9
10
11
| public interface Swimmable {
default void act() {
System.out.println("어푸 어푸");
}
}
public interface Flyable {
default void act() {
System.out.println("파닥 파닥");
}
}
|
코틀린에서는 어떻게 할 수 있을까?
1
2
3
4
5
6
7
8
9
10
11
12
13
| interface Swimmable {
fun act() {
println("어푸 어푸")
}
}
interface Flyable {
fun act() {
println("파닥 파닥")
}
fun fly() //추상 메서드
}
|
자바와 크게 다를게 없어보인다. defualt 메서드를 구현할 때 default 키워드 없이 구현할 수 있다는 차이 뿐이다. 이러한 인터페이스를 펭귄에서 어떻게 구현할까?
1
2
3
4
5
6
7
8
| public final class Penguin extends Animal implements Flyable, Swimmable {
@Override
public void act() {
Swimmable.super.act();
Flyable.super.act();
}
}
|
1
2
3
4
5
6
7
8
9
| class Penguin(
species: String
) : Animal(species, 2), Swimable, Flyable {
override fun act() {
super<Swimmable>.act()
super<Flyable>.act()
}
}
|
- 인터페이스의 상속도 :를 사용한다.
- 중복되는 인터페이스를 특정할 때 super<>를 사용한다.
- 인터페이스 역시 자바, 코틀린 모두 인스턴스화 할 수 없다.
코틀린에서는 backing field가 없는 프로퍼티를 인터페이스에 만들 수 있다.
1
2
3
4
5
6
7
8
9
| interface Swimmalbe {
val swimAbility: Int
//...
}
class Penguin() {
override val swimAbility: Int
get() = 3
}
|
클래스 상속 시 주의할 점
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| open class Base(
open val number: Int = 100
) {
init {
println("Base Class")
println(number)
}
}
class Derived(
override val number: Int
) : Base(number) {
init {
println("Derived Class")
}
}
|
- Base 클래스를 다른 클래스가 상속받을 수 있게 open으로 열어주었다.
- 이 부분 말고 위의 예시들은 추상클래스, 인터페이스이다.
- number라는 프로퍼티 또한 open으로 열어주었다.
- 그런데 Derived(300)과 같이 Derived를 인스턴스화 하면 어떤 결과가 나올까?
1
2
3
| Base Class
0
Derived Class
|
- 왜 100도 300도 아닌 0이 들어갈까?
- 보면 상위클래스의 init 블록이 먼저 실행되는 것을 볼 수 있다.
- 그런데 아직 하위 클래스에 number라는 값은 초기화가 이루어 지지 않았는데 출력해야 한다.
- 이런 상태에서 하위 클래스의 number를 출력하려고 하니 100도 300도 아닌 int의 기초 값인 0이 나오게 되는 것이다.
- 따라서 상위 클래스의 constructor 및 init 블록에서는 하위 클래스의 프로퍼티에 접근하면 안된다.
- 정확히는 final이 아닌 프로퍼티에 접근하면 안된다.
상위 클래스를 설계할 때 생성자 또는 초기화 블록에 사용되는 프로퍼티에는 open을 피해야 한다.
상속 관련 키워드 정리
- final
- override를 할 수 없게 한다.
- 기본적으로 보이지 않게 존재한다. 따라서 상속받게 하려면 open을 붙여준다.
- open
- abstract
- override
정리
- 상속 또는 구현을 할 때 :을 사용한다.
- 상위 클래스 상속을 구현할 때 생성자를 반드시 호출해야 한다.
- override를 필수로 붙여야 한다.
- 추상 멤버가 아니면 기본적으로 override가 불가능하다.
- 상위 클래스의 생성자 또는 초기화 블록에서 open 프로퍼티를 사용하면 얘기치 못한 버그가 생길 수 있다.