Kotlin의 상속 다루기
추상 클래스
Animal이란 추상클래스를 구현한 Cat, Penguin
Animal
<–Cat, penguin
Cat, penguin
이Animal
을 상속 받는 구조
Animal Class Java Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class JavaAnimal {
protected final String species; // 동물의 종류
protected final int legCount; // 동물의 다리 갯수
public JavaAnimal(String species, int legCount) {
this.species = species;
this.legCount = legCount;
}
abstract public void move();
public String getSpecies() {
return species;
}
public int getLegCount() {
return legCount;
}
}
move
라는 추상 메서드를 가진 JavaAnimal
추상 클래스
Animal Class Kotlin Code
1
2
3
4
5
6
7
8
abstract class Animal (
protected val species: String, // 동물의 종류
protected val legCount: Int, // 동물의 다리 갯수
){
abstract fun move()
}
Cat Class
Cat Class Java Code
1
2
3
4
5
6
7
8
9
10
11
12
public class JavaCat extends JavaAnimal {
public JavaCat(String species) {
super(species, 4);
}
@Override
public void move() {
System.out.println("고양이가 사뿐 사뿐 걸어가~");
}
}
Cat Class Kotlin Code
1
2
3
4
5
6
7
8
9
10
11
12
// Cat Class 를 만듦
// Animal Class 의 생성자(constructor)를 불러 옮
class Cat (
species: String
) : Animal(species, 4) { // 상속받는 경우 한칸 띄우고 :을 찍어야 한다.
// Kotlin 은 override 를 어노테이션이 아니라 지시어를 사용
override fun move() {
println("고양이가 움직인다.")
}
}
extends
를 사용하지 않고:
을 사용하여 상속:
은 항상 한칸 띄우고 작성
class
를 상속받을 때 상위 클래스의 생성자를 바로 호출override
를 필수적으로 붙여야 하고 어노테이션이 아닌 지시어를 사용
Penguin Class
Penguin Class Java Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class JavaPenguin extends JavaAnimal implements JavaSwimable, JavaFlyable {
private final int wingCount;
public JavaPenguin(String species) {
super(species, 2);
this.wingCount = 2;
}
@Override
public void move() {
System.out.println("펭귄이 움직입니다~ 꿱꿱");
}
@Override
public int getLegCount() {
return super.legCount + this.wingCount;
}
@Override
public void act() {
JavaSwimable.super.act();
JavaFlyable.super.act();
}
}
- 생성자를 생성할 때
wingCount
를this
를 통해 값을 바로 삽입 Animal
의getLegCount
를Override
하고wingCount
를 더해서 반환
Penguin Class Kotlin Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Penguin (
species: String
) : Animal(species, 2) {
private val wingCount: Int = 2
override fun move() {
println("펭권이 움직입니다.")
}
// customGetter 사용
override val legCount: Int
get() = super.legCount + this.wingCount
}
Animal
에protected val legCount: Int
를protected open val legCount: Int
처럼open
을 붙여 줘야getter
를 사용 가능
override
키워드와customGetter
를 사용Java
와 같이 상위 클래스에 접근하는 키워드는super
Java와 Kotlin의 공통점
- 추상 클래스는 인스턴스화 할 수 없다.
인터페이스
Flyable과 Swimable을 구현한 Penguin
Flyable,Swimable
(인터페이스) –>Penguin
Penguin
이Flyable,Swimable
을 구현
Swimable interface
1
2
3
4
5
6
interface Swimable {
fun act(){
println("어푸어푸")
}
}
Flyable interface
1
2
3
4
5
6
7
8
9
interface Flyable {
fun act(){
println("파닥파닥")
}
// 추상 메서드의 경우
fun fly()
}
인터페이스를 구현한 Penguin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Penguin (
species: String
) : Animal(species, 2), Swimable, Flyable {
private val wingCount: Int = 2
override fun move() {
println("펭권이 움직입니다.")
}
// customGetter 사용
override val legCount: Int
get() = super.legCount + this.wingCount
override fun act() {
super<Swimable>.act()
super<Flyable>.act()
}
// 추상 메서드 구현
override fun fly() {
println("날 수 있다.")
}
}
interface
를 상속할 경우에도:
을 사용하기 때문에Animal
옆에,
로 추가super<interface>.methode()
를 사용하여 호출override
를 추상 메서드를 구현할 수 있다.
Java, Kotlin의 인터페이스 공통점
- 인터페이스를 인스턴스화 할 수 없다.
Kotlin
은backing field
가 없는 프로퍼티를interface
에 만들 수 있다.
ex). swimAbility
을 변수를 만들고 Swimable
을 구현하고 있는 Penguin
에서 swimAbility
구현
1
2
3
4
5
6
7
8
9
10
11
interface Swimable {
// customGetter 를 이용해서 상속하고 있는 class 에서 만들어 줘야 함
val swimAbility: Int
fun act(){
// Penguin 에서 customGetter 로 구현한 swimAbility 를 사용 가능
println("swimAbility = $swimAbility")
println("어푸어푸")
}
}
Penguin
에서 customGetter
로 구현 or interface
에서 직접 구현해도 된다.
interface
에서 직접 구현하는 경우default
값이 된다.1 2 3 4 5 6 7 8
// Penguin에서 구현 override val swimAbility: Int get() = 1 // Swimable에서 직접 default 값으로 구현 가능 val swimAbility: Int get() = 1
클래스를 상속할 때 주의할 점
Base Class와 Derived Class
Derived Class
가Base Class
를 상속 받고 있다.Base Class
를 다른Class
가 상속받을 수 있게open
해 준다.
Base Class
1
2
3
4
5
6
7
8
9
open class Base (
open val number: Int = 100
){
// 초기화
init {
println("Base Class")
println("number = $number")
}
}
Derived Class
1
2
3
4
5
6
7
8
9
class Derived(
override val number: Int
) : Base(number){
// 초기화
init {
println("Derived Class")
}
}
출력
1
2
3
fun main() {
Derived(200)
}
- 값
- Base Class
number = 0
Derived Class
0이 출력되는 이유
- 인텔리제이의 경고 :
Accessing non-final property number in constructor
- 상위 클래스의 생성자가 실행되는 동안 하위 클래스(
Derived
)의 프로퍼티에 값을 넣었다는 의미 - 즉, 상위 클래스의
number
를 호출하면 하위 클래스의number
를 가져온다. - 그런데 아직 상위 클래스의 생성자가 실행되는 단계라서 하위 클래스의
number
의 값이 초기화되지 않음 - 이 상태에서 하위 클래스의
number
에 접근하여Int
의 기초 값인0
이 출력됨
- 상위 클래스의 생성자가 실행되는 동안 하위 클래스(
- 그래서
상위 클래스의 생성자 및 초기화 블럭
에서는하위 클래스의 필드에 접근하면 안된다.
- 정확히는
final
이 아닌 프로퍼티에는 접근하면 안된다.
상위 클래스를 설계할 경우
- 생성자 및 초기화 블럭에 사용되는 프로퍼티는
open
사용을 피해야 한다.
상속 관련 키워드 4가지 정리
- final :
override
를 할 수 없게 한다.default
로 보이지 않는 값으로 존재 (기본 값이final
이라는 뜻) - open :
override
를 열어 준다. - abstract : 반드시
override
해야 한다. - override : 상위 타입을
override
하고 있다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.