본문으로 건너뛰기

상속

Kotlin의 모든 클래스는 공통 슈퍼클래스인 Any를 가집니다. Any는 슈퍼타입이 선언되지 않은 클래스의 기본 슈퍼클래스입니다.

class Example // 암묵적으로 Any에서 상속받음

Anyequals(), hashCode(), toString() 세 가지 메서드를 가지고 있습니다. 따라서 이 메서드들은 모든 Kotlin 클래스에 대해 정의됩니다.

기본적으로 Kotlin 클래스는 final 입니다. 즉, 상속될 수 없습니다. 클래스를 상속 가능하게 만들려면 open 키워드로 표시하세요.

open class Base // 클래스가 상속에 대해 열려 있음

명시적인 슈퍼타입을 선언하려면 클래스 헤더에서 콜론 뒤에 타입을 배치합니다.

open class Base(p: Int)

class Derived(p: Int) : Base(p)

파생 클래스가 주 생성자를 가지는 경우, 기본 클래스는 해당 파라미터에 따라 주 생성자에서 초기화될 수 있고 (또는 초기화되어야) 합니다.

파생 클래스가 주 생성자를 가지지 않는 경우, 각 부 생성자는 super 키워드를 사용하여 기본 타입을 초기화하거나, 그렇게 하는 다른 생성자에 위임해야 합니다. 이 경우 서로 다른 부 생성자가 기본 타입의 서로 다른 생성자를 호출할 수 있습니다.

class MyView : View {
constructor(ctx: Context) : super(ctx)

constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

메서드 오버라이딩

Kotlin은 오버라이드 가능한 멤버와 오버라이드에 대해 명시적인 modifier를 요구합니다.

open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}

class Circle() : Shape() {
override fun draw() { /*...*/ }
}

override modifier는 Circle.draw()에 필수적입니다. 이것이 누락되면 컴파일러가 불만을 제기합니다. Shape.fill()처럼 함수에 open modifier가 없는 경우, 서브클래스에서 동일한 시그니처를 가진 메서드를 선언하는 것은 override를 사용하든 사용하지 않든 허용되지 않습니다. open modifier는 open modifier가 없는 클래스인 final 클래스의 멤버에 추가될 때는 효과가 없습니다.

override로 표시된 멤버는 그 자체로 open되어 서브클래스에서 오버라이드될 수 있습니다. 재 오버라이드를 금지하려면 final을 사용하세요.

open class Rectangle() : Shape() {
final override fun draw() { /*...*/ }
}

프로퍼티 오버라이딩

오버라이딩 메커니즘은 메서드에서와 같은 방식으로 프로퍼티에서 작동합니다. 슈퍼클래스에서 선언된 후 파생 클래스에서 재선언되는 프로퍼티는 override가 앞에 붙어야 하며 호환 가능한 타입을 가져야 합니다. 각 선언된 프로퍼티는 초기화 블록이 있는 프로퍼티 또는 get 메서드가 있는 프로퍼티로 오버라이드될 수 있습니다.

open class Shape {
open val vertexCount: Int = 0
}

class Rectangle : Shape() {
override val vertexCount = 4
}

val 프로퍼티를 var 프로퍼티로 오버라이드할 수도 있지만, 그 반대는 불가능합니다. val 프로퍼티는 기본적으로 get 메서드를 선언하고, 그것을 var로 오버라이드하면 파생 클래스에 set 메서드가 추가로 선언되기 때문에 이것은 허용됩니다.

주 생성자에서 프로퍼티 선언의 일부로 override 키워드를 사용할 수 있습니다.

interface Shape {
val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // 항상 4개의 정점을 가짐

class Polygon : Shape {
override var vertexCount: Int = 0 // 나중에 임의의 숫자로 설정 가능
}

파생 클래스 초기화 순서

파생 클래스의 새로운 인스턴스를 생성하는 동안, 기본 클래스 초기화가 첫 번째 단계로 수행됩니다 (기본 클래스 생성자에 대한 인수의 평가가 선행됨). 이는 파생 클래스의 초기화 로직이 실행되기 전에 발생함을 의미합니다.


open class Base(val name: String) {

init { println("기본 클래스 초기화 중") }

open val size: Int =
name.length.also { println("기본 클래스에서 크기 초기화 중: $it") }
}

class Derived(
name: String,
val lastName: String,
) : Base(name.replaceFirstChar { it.uppercase() }.also { println("기본 클래스에 대한 인수: $it") }) {

init { println("파생 클래스 초기화 중") }

override val size: Int =
(super.size + lastName.length).also { println("파생 클래스에서 크기 초기화 중: $it") }
}

fun main() {
println("파생 클래스(\"hello\", \"world\") 생성 중")
Derived("hello", "world")
}

이는 기본 클래스 생성자가 실행될 때 파생 클래스에서 선언되거나 오버라이드된 프로퍼티가 아직 초기화되지 않았음을 의미합니다. 기본 클래스 초기화 로직에서 해당 프로퍼티를 사용하는 경우 (직접적으로 또는 다른 오버라이드된 open 멤버 구현을 통해 간접적으로) 잘못된 동작이나 런타임 오류가 발생할 수 있습니다. 따라서 기본 클래스를 설계할 때 생성자, 프로퍼티 초기화 블록 또는 init 블록에서 open 멤버를 사용하지 않아야 합니다.

슈퍼클래스 구현 호출

파생 클래스의 코드는 super 키워드를 사용하여 슈퍼클래스 함수 및 프로퍼티 접근자 구현을 호출할 수 있습니다.

open class Rectangle {
open fun draw() { println("사각형 그리기") }
val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("사각형 채우기")
}

val fillColor: String get() = super.borderColor
}

내부 클래스 내부에서 외부 클래스의 슈퍼클래스에 접근하는 것은 외부 클래스 이름으로 한정된 super 키워드를 사용하여 수행됩니다. super@Outer:

open class Rectangle {
open fun draw() { println("사각형 그리기") }
val borderColor: String get() = "black"
}

class FilledRectangle: Rectangle() {
override fun draw() {
val filler = Filler()
filler.drawAndFill()
}

inner class Filler {
fun fill() { println("채우기") }
fun drawAndFill() {
super@FilledRectangle.draw() // Rectangle의 draw() 구현을 호출합니다.
fill()
println("색상 ${super@FilledRectangle.borderColor}으로 채워진 사각형 그림") // Rectangle의 borderColor의 get() 구현을 사용합니다.
}
}
}

fun main() {
val fr = FilledRectangle()
fr.draw()
}

오버라이딩 규칙

Kotlin에서 구현 상속은 다음과 같은 규칙에 의해 규제됩니다. 클래스가 직계 슈퍼클래스에서 동일한 멤버의 여러 구현을 상속받는 경우, 이 멤버를 오버라이드하고 자체 구현을 제공해야 합니다 (상속받은 구현 중 하나를 사용할 수 있음).

상속된 구현이 가져온 슈퍼타입을 나타내려면 꺾쇠 괄호 안에 슈퍼타입 이름으로 한정된 super (예: super<Base>)를 사용하십시오.

open class Rectangle {
open fun draw() { /* ... */ }
}

interface Polygon {
fun draw() { /* ... */ } // 인터페이스 멤버는 기본적으로 'open'입니다.
}

class Square() : Rectangle(), Polygon {
// 컴파일러는 draw()가 오버라이드되도록 요구합니다.
override fun draw() {
super<Rectangle>.draw() // Rectangle.draw() 호출
super<Polygon>.draw() // Polygon.draw() 호출
}
}

RectanglePolygon 모두에서 상속받는 것은 괜찮지만, 둘 다 draw()의 구현이 있으므로 Square에서 draw()를 오버라이드하고 모호성을 없애기 위해 별도의 구현을 제공해야 합니다.