Inheritance
Kotlin의 모든 클래스에는 supertype(부모타입)이 선언되지 않은 클래스의 기본 superclass(부모클래스) Any라는 공통 슈퍼클래스가 있다. (자바의 Object 클래스와 비슷함). Any 클래스는 3가지 메소드를 가지고 있다. equals(), hashCode(), toString(). 모든 Kotlin 클래스가 해당 메서드를 가지고 있다. 기본적으로, 코틀린 클래스는 불변(final)이며, 상속할 수 없다. 클래스를 상속 가능하게 만드려면 open 키워드를 클래스에 붙여 표시한다.
class Example // Implicitly inherits from Any
open class Base // Class is open for inheritance
명시적 상위 유형을 선언하려면 클래스 헤더에서 클론 뒤에 유형을 배치한다.
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)
}
Overriding methods
코틀린에는 오버라이딩 가능한 멤버 및 오버라이딩에 대한 명시적 키워드가 필요하다. 오버라이딩 가능한 멤버는 open 키워드를 사용하며, 오버라이딩은 override 키워드를 사용한다.
open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}
class Circle() : Shape() {
override fun draw() { /*...*/ }
}
만약에, override 키워드가 빠져있다면, 컴파일 에러가 뜰 것이다. 만약에 부모 클래스에서 open 키워드가 존재하지 않는 경우에는 오버라이드 여부와 관계 없이 자식 클래스에서 동일한 정의를 가진 메소드를 선언하는 것은 허용되지 않는다. open 키워드는 open 클래스가 아닌 클래스의 멤버에 붙으면 아무런 효과가 없다.
override 키워드로 표시된 멤버는 그 자체로 open 멤버가 되기 때문에, 또 자식 클래스에서 오버라이딩이 가능해진다. 이를 막으려면 final 키워드가 필요하다.
open class Rectangle() : Shape() {
final override fun draw() { /*...*/ }
}
Overriding properies
코틀린의 오버라이딩 메커니즘은 메소드에서와 동일한 형태로 속성(properties)에서도 동작한다. 자식 클래스에서 다시 선언된 부모 클래스의 properties는 override 키워드가 붙어야 하고 호환 가능한 타입을 가져야 한다. 선언된 각 속성은 초기화가 있는 속성이나 get 메소드가 존재하는 속성으로 재정의 될 수 있다.
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
var 속성으로 val 속성을 재정의할 수도 있지만, 그 반대는 불가능하다. val 속성은 get 메소드를 자동으로 만들어내는데, var로 재정의 하는 경우, 자식 클래스에서 set 메서드를 추가로 선언하기 때문에 허용된다.
기본생성자에서 속성 선언의 일부로 override 키워드를 사용할 수 있다.
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // val 속성이므로 항상 4
class Polygon : Shape {
override var vertexCount: Int = 0 // var 속성이므로 이후 언제든 숫자가 변경될 수 있음
}
Derived class initialization order
: 자식 클래스의 초기화 순서
자식 클래스의 새 인스턴스를 생성하는 동안, 기본 클래스의 초기화는 첫번째 단계로 수행되며( 기본 클래스 생성자에 대한 인수 평가에 의해서만 선행됨 ), 이는 자식 클래스의 초기화 로직이 실행되기 전에 발생할 수 있음을 의미한다.
즉, 부모 클래스 생성자가 실행될 때, 자식 클래스에서 선언되거나 재정의된 속성이 아직 초기화 되지 않았다. 기본 클래스 초기화 로직에서 이러한 속성을 사용하면 논리적 오류 또는 런타임 오류가 뜬다. 따라서, 부모 클래스를 디자인할 때, 생성자, 속성 초기화 또는 초기화 블록에서 open 멤버를 사용하면 안된다.
Calling the superclass implementation
자식 클래스의 코드내에서 super 키워드를 사용하여 부모 클래스 함수와 속성 접근자 구현을 호출할 수 있다.
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
inner 클래스의 내부에서 외부 클래스의 부모 클래스로 접근하는 것은 super@Outer로 한정된 super 키워드를 사용하여 수행된다.
Overriding rules
코틀린에서, 클래스가 직계 부모 클래스에서 동일한 멤버의 여러 상속을 구현하는 경우 이 멤버를 재정의하고 자체 구현을 제공해야 한다. 상속된 구현을 가져오는 상위 유형을 나타내려면 super<Base>와 같이 꺽쇠 괄호로 묶인 상위 타입 이름으로 super 정규화를 사용한다.
open class Rectangle {
open fun draw() { /* ... */ }
}
interface Polygon {
fun draw() { /* ... */ } // interface members are 'open' by default
}
class Square() : Rectangle(), Polygon {
// The compiler requires draw() to be overridden:
override fun draw() {
super<Rectangle>.draw() // call to Rectangle.draw()
super<Polygon>.draw() // call to Polygon.draw()
}
}
Rectangle과 Polygon 모두를 상속하는 것은 괜찮지만, 둘 다 draw()의 구현을 갖고 있기 때문에 Square 클래스에서 draw()를 재정의하고 모호성을 제거하기 위해 별도의 구현을 제공해야된다.