1. 추상 클래스
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;
}
}
자바로 작성된 추상클래스를 코틀린으로 옮겨보자.
abstract class Animal(
protected var species: String,
protected var legCount: Int,
){
abstract fun move()
}
class Cat(
species: String
) : Animal(species, 4) {
override fun move() {
TODO("Not yet implemented")
}
}
Cat은 Animal을 상속받은 자식 클래스이다.
다음 코드를 보면 Cat클래스는 species라는 생성자가 존재하고, 부모 클래스를 상속받기 위해 extends라는 키워드를 사용하는 것이 아닌 :(콜론)을 사용한다.
여기에는 컨벤션이 존재하는데 타입을 표시하는 콜론은 띄어쓰기를 사용하지 않는다.
species: String
하지만 상속에서 사용하는 콜론에서는 띄어쓰기가 존재한다.
(species: String) : Animal(species, 4)
자바에서 super()이라는 키워드로 부모 생성자를 호출하는 것과 달리 코틀린에서는 상속 이후 생성자 설정을 바로 코드로 진행한다.
abstract class Animal(
protected val species: String,
protected open val legCount: Int,
){
abstract fun move()
}
추상 클래스의 legCount의 프로퍼티의 getter를 하위클래스에서 재정의하기 위해선 open이라는 키워드를 사용해야 한다.
이는 기본 자바에서 별다른 키워드 없이 오버라이드가 가능했던 점과 다른 방식이다.
class Penguin(
species: String
) : Animal(species, 2) {
private val wingCount: Int = 2
override fun move() {
println("penguin is moving")
}
override val legCount: Int
get() = super.legCount + this.wingCount
}
get()은 custom getter를 생성하는 방식이다.
2. 인터페이스
jdk 8에 추가된 default 메서드를 인터페이스에 구현할 때 코틀린은 기본으로 구현 바디가 있으면 default 메서드로 취급한다.
interface Flyable {
fun act() {
print("default method")
}
}
인터페이스 구현 역시 콜론을 통해서 진행한다.
class Penguin(
species: String
) : Animal(species, 2), Swimable, Flyable {
private val wingCount: Int = 2
override fun move() {
println("penguin is moving")
}
override val legCount: Int
get() = super.legCount + this.wingCount
override fun act() {
super<Swimable>.act()
super<Flyable>.act()
}
}
super <타입>. 함수를 사용하는 문법도 다르다.
interface Flyable {
val swimAbility: Int
fun act() {
print("default method")
}
}
코틀린에서는 backing field가 없는 프로퍼티를 interface에 만들 수 있다.
다음 코드는 구현체에서 getter를 구현하기를 기대한다
override val swimAbility: Int
get() = 3
3. 클래스를 상속할 때 주의할 점
fun main() {
Derived(300)
}
open class Base(
open val number: Int = 100
){
init {
println("base init")
println(number)
}
}
class Derived(
override val number: Int
) : Base(number){
init {
println("derived init")
}
}
open 키워드를 가진 Base 클래스는 다른 클래스에서 상속할 수 있도록 open 하였다.
프로퍼티 또한 open 키워드를 통해 오버라이드를 허용했고 Derived 클래스는 해당 클래스를 상속받고 프로퍼티를 오버라이드 하였다.
다음과 같이 코드를 실행하면 0이라는 Int 기본값이 나온다.
이는 생성자의 실행 순서에 관련이 있다.
1. Derived 클래스 생성
2. Base 생성자 호출
3. number 필드의 값을 출력하기 위해 자식클래스의 프로퍼티값 사용해야 함.
4. 하지만 아직 자식 클래스의 생성자가 호출되기 이전이라 프로퍼티에 접근불가
5. 기본 값사용
상위 클래스에서 하위 클래스가 override 하고 있는 프로퍼티를 생성자 블락이나 init 블록에 사용하면 안 된다.
4. 상속 관련 지시어 정리
final : override 할 수 없게 한다. default로 보이지 않게 존재.
open : override를 열어준다.
abstract : 반드시 override 해야 한다.
override : 상위 타입을 오버라이드 하고 있다.
댓글