Any
- 코틀린의 모든 클래스의 공통 조상supertype 을 선언 하지 않은 경우 기본 superclass
class Example // Implicitly inherits from Any
Any
는 세가지 메서드를 가진다.equals()
hashCode()
, andtoString()
.
따라서 이 메서드 들은 모든 코틀린 클래스에서 정의 되어 있다.
기본적으로 코틀린 클래스는 final 이다. 즉, 상속 할 수 없다.
상속할 수 있게 해주려면
open
키워드로 표시 해 주어야 한다.classtype 뒤에 colon 과 타입 명을 써줌으로 써 명시적인 supertype (부모 클래스)를 선언할 수 있다.
open class Base(p: Int) class Derived(p: Int) : Base(p)
만약 derived class 가 주생성자를 가진다면, base 클래스는 꼭 derived class 의 주생성자를 호출 해야한다.
부모,자식 클래스의 주생성자, 부생성자가 가질 수 있는 상황을 다양하게 가정하고 클래스를 구성해서 공부해봅시다.
Overriding methods
코틀린은 재정의 가능한 멤버와 재정의에 대해서 명시적인 제어자를 필요로 합니다.
open class Shape { open fun draw() { /*...*/ } fun fill() { /*...*/ } } class Circle() : Shape() { override fun draw() { /*...*/ } }
위에서
override
제어자는 Circle.draw()
메서드에서 필요합니다.없으면 컴파일 에러가 발생합니다.
상위 클래스인
Shape
의 fill
메서드에는 open
제어자가 없습니다.open
제어자가 없는 메서드와 같은 메서드 시그니처의 메서드를 하위 클래스에서 선언할 수 없습니다. (override
써도 안됨)클래스의 멤버에
open
제어자를 달아도 클래스가 open
이 아니라면 아무 효과가 없습니다.override
한 메서드는 자동으로 open
이기 때문에 아래로 쭉쭉 재정의 가능합니다.명시적으로 여기서 딱 재정의를 막고 싶으면
final
키워드를 사용해서 재정의를 막을 수 있습니다.open class Rectangle() : Shape() { final override fun draw() { /*...*/ } //Rectangle 을 상속해도 Shape 의 draw 는 Rectangle의 구현을 사용하게 제한함 }
속성 재정의 Overriding properties
속성을 오버로딩 할 때도 메커니즘은 메서드에서와 같습니다.
- 부모 클래스의 재정의 하고 싶은 속성에는
open
- 자식 클래스의 재정의 하는 속성에는
override
를 명시해야 합니다.
둘은 compatiable type 이어야 합니다.
open class Shape { open val vertexCount: Int = 0 } class Rectangle : Shape() { override val vertexCount = 4 }
불변 속성을 가변 속성으로 재정의 할 수 있습니다. 하지만 반대는 안됩니다.
getter 와 setter 를 생각해보고 왜 그런지 고민해 봅시다.
override
키워드를 주 생성자의 속성 선언에 사용할 수 있습니다.interface Shape { val vertexCount: Int } class Rectangle(override val vertexCount: Int = 4) : Shape // Always has 4 vertices class Polygon : Shape { override var vertexCount: Int = 0 // Can be set to any number later }
자식 클래스와 초기화 순서 - Derived class initialization order
자식 클래스의 새 인스턴스를 생성 할때,
본인의 초기화 보다 부모 클래스의 초기화가 가장 먼저 진행 됩니다.
open class Base(val name: String) { init { println("Initializing a base class") } open val size: Int = name.length.also { println("Initializing size in the base class: $it") } } class Derived( name: String, val lastName: String, ) : Base(name.replaceFirstChar { it.uppercase() }.also { println("Argument for the base class: $it") }) { init { println("Initializing a derived class") } override val size: Int = (super.size + lastName.length).also { println("Initializing size in the derived class: $it") } } fun main(){ val derived = Derived("deukyun", "nam") println(derived.size) }
Argument for the base class: Deukyun Initializing a base class Initializing size in the base class: 7 Initializing a derived class Initializing size in the derived class: 10 10
replaceFirstChar
, also
- 나중에 공부하자 (inline 함수?)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 }
내부 클래스에서
super
키워드에 qualifier 를 통해 외부 클래스의 부모에 접근 할 수 있다.class FilledRectangle: Rectangle() { override fun draw() { val filler = Filler() filler.drawAndFill() } inner class Filler { fun fill() { println("Filling") } fun drawAndFill() { super@FilledRectangle.draw() // Calls Rectangle's implementation of draw() fill() println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // Uses Rectangle's implementation of borderColor's get() } } }
재정의 규칙 - Overriding rules
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() } }
같은 메서드 시그니처를 가진 인터페이스와 클래스를 동시에 상속받은 경우 angle brackets
<Base>
를 통해 super
의 대상을를 qualify 해 줄 수 있다.