λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

Android & Kotlin

[λ‚΄λ³΄λ‚΄λ²ˆ] Inheritance | Kotlin

Kotlin을 κ³΅λΆ€ν•˜λ©΄μ„œ 상속에 λŒ€ν•œ 뢀뢄을 κ³΅λΆ€ν•˜κ³ μž 곡식 λ¬Έμ„œλ₯Ό 직접 λ²ˆμ—­ν–ˆλ‹€.
ν•΄λ‹Ή λ¬Έμ„œλŠ” 2022λ…„ 3μ›” 2일자 곡식 λ¬Έμ„œμ΄λ‹€. 

Kotlin κ³΅μ‹λ¬Έμ„œ ν™•μΈν•˜κΈ° ➑️

 

Inheritance | Kotlin

 

kotlinlang.org

 

β€» μ˜μ–΄ μ „κ³΅μžλ„ ν•΄μ™Έ μœ ν•™νŒŒλ„ μ•„λ‹ˆκΈ°μ— λ²ˆμ—­μ—λŠ” μ˜μ—­, μ˜€μ—­, κ΅¬κΈ€ λ²ˆμ—­μ΄ λ¬΄μˆ˜νžˆ λ§Žμ„ 수 있으며, 혼자 κ³΅μ‹λ¬Έμ„œλ₯Ό μ°Έμ‘°ν•΄κ°€λ©° λ²ˆμ—­ν•˜λ‹€ λ³΄λ‹ˆ μ˜€νƒ€λ„ λ§Žμ„ 수 μžˆλ‹€. μ •ν™•ν•œ λ‚΄μš©μ€ κ³΅μ‹λ¬Έμ„œλ₯Ό 직접 μ‚΄νŽ΄λ³΄κ±°λ‚˜ λ‹€λ₯Έ 정보듀을 더 μ°Ύμ•„λ³΄λŠ” 것을 μΆ”μ²œν•œλ‹€.

(ν•˜μ§€λ§Œ λŒ“κΈ€ ν”Όλ“œλ°±λ„ ν™˜μ˜ν•©λ‹ˆλ‹€πŸ˜ƒ )

 

All classes in Kotlin have a common superclass, Any, which is the default superclass for a class with no supertypes declared:

코틀린에 μžˆλŠ” λͺ¨λ“  ν΄λž˜μŠ€μ—λŠ” 곡톡 슈퍼클래슀 Anyκ°€ μžˆλŠ”λ°, 이것은 μ„ μ–Έλœ μŠˆνΌνƒ€μž…μ΄ μ—†λŠ” 클래슀의 κΈ°λ³Έ μŠˆνΌν΄λž˜μŠ€μ΄λ‹€.

class Example // Implicitly inherits from Any
 

Any has three methods: equals(), hashCode(), and toString(). Thus, these methods are defined for all Kotlin classes.

AnyλŠ” 3가지 λ©”μ„œλ“œλ₯Ό κ°–κ³  μžˆλ‹€: equals(), hashCode(), toString(). κ·ΈλŸ¬λ―€λ‘œ 이 λ©”μ„œλ“œλ“€μ€ λͺ¨λ“  μ½”ν‹€λ¦° ν΄λž˜μŠ€λ“€μ—λ„ μ •μ˜λ˜μ–΄ μžˆλ‹€.

By default, Kotlin classes are final – they can’t be inherited. To make a class inheritable, mark it with the open keyword:

κΈ°λ³Έκ°’μœΌλ‘œ μ½”ν‹€λ¦° ν΄λž˜μŠ€λ“€μ€ final이닀 즉 상속될 수 μ—†λ‹€. 클래슀λ₯Ό 상속 κ°€λŠ₯ν•˜κ²Œ λ§Œλ“€λ €λ©΄ open ν‚€μ›Œλ“œλ‘œ ν‘œμ‹œλ₯Ό ν•΄μ£Όμ–΄μ•Όν•œλ‹€: 

open class Base // Class is open for inheritance
 

To declare an explicit supertype, place the type after a colon in the class header:

λͺ…μ‹œμ μΈ μŠˆνΌνƒ€μž…μ„ μ„ μ–Έν•˜λ €λ©΄ λͺ…μ‹œν•˜λ €λŠ” κ·Έ νƒ€μž…μ„ class 헀더에 콜둠(:) 뒀에 두어라: 

open class Base(p: Int)

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

If the derived class has a primary constructor, the base class can (and must) be initialized in that primary constructor according to its parameters.

λ§Œμ•½ νŒŒμƒλœ ν΄λž˜μŠ€κ°€ κΈ°λ³Έ(primary) μƒμ„±μžλ₯Ό 가지고 μžˆλ‹€λ©΄ κΈ°λ³Έ(base) ν΄λž˜μŠ€λŠ” κΈ°λ³Έ μƒμ„±μžμ—μ„œ νŒŒλΌλ―Έν„°μ— 따라 μ΄ˆκΈ°ν™” 될 수 (μ΄ˆκΈ°ν™” λ˜μ–΄μ•Ό ν•œλ‹€)μžˆλ‹€.

If the derived class has no primary constructor, then each secondary constructor has to initialize the base type using the super keyword or it has to delegate to another constructor which does. Note that in this case different secondary constructors can call different constructors of the base type:

λ§Œμ•½ νŒŒμƒ ν΄λž˜μŠ€μ— κΈ°λ³Έ(primary) μƒμ„±μžκ°€ μ—†λ‹€λ©΄, 각 보쑰(secondary) μƒμ„±μžλŠ” κΈ°λ³Έ νƒ€μž…μ„ super ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ μ΄ˆκΈ°ν™” ν•˜κ±°λ‚˜ κ·Έλ ‡κ²Œ ν•˜λŠ” λ‹€λ₯Έ μƒμ„±μžμ— μœ„μž„ν•΄μ•Όν•œλ‹€. 이 경우 λ‹€λ₯Έ 보쑰 μƒμ„±μžκ°€ κΈ°λ³Έ νƒ€μž…μ˜ λ‹€λ₯Έ μƒμ„±μžλ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€: 

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

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

 

Overriding methods

Kotlin requires explicit modifiers for overridable members and overrides:

코틀린은 μ˜€λ²„λΌμ΄λ”© κ°€λŠ₯ν•œ 멀버와 μ˜€λ²„λΌμ΄λ“œ ν•¨μˆ˜μ— λŒ€ν•΄ λͺ…μ‹œμ μΈ μˆ˜μ‹μ–΄κ°€ μš”κ΅¬ λœλ‹€: 

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

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

The override modifier is required for Circle.draw(). If it were missing, the compiler would complain. If there is no open modifier on a function, like Shape.fill(), declaring a method with the same signature in a subclass is not allowed, either with override or without it. The open modifier has no effect when added to members of a final class – a class without an open modifier.

μ˜€λ²„λΌμ΄λ“œ μˆ˜μ‹μ–΄λŠ” Circle.draw()에 μš”κ΅¬ λœλ‹€. λ§Œμ•½ 이 μˆ˜μ‹μ–΄κ°€ μ—†μœΌλ©΄, μ»΄νŒŒμΌλŸ¬κ°€ 이것에 λŒ€ν•΄ λΆˆν‰ν•  것이닀. (ν•œκ΅­μ–΄λ‘œλŠ” μ–΄μƒ‰ν•œ ν‘œν˜„μ΄μ§€λ§Œ;;) Shape.fill()κ³Ό 같이 open μˆ˜μ‹μ–΄κ°€ ν•¨μˆ˜ μ•žμ— μ—†μœΌλ©΄ override μˆ˜μ‹μ–΄κ°€ μžˆλ“  μ—†λ“  μ„œλΈŒν΄λž˜μŠ€μ—μ„œ λ™μΌν•œ signatureλ₯Ό κ°–κ³  μžˆλŠ” λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜λŠ” 것은 ν—ˆμš©λ˜μ§€ μ•ŠλŠ”λ‹€. open μˆ˜μ‹μ–΄λŠ” final 클래슀 - open μˆ˜μ‹μ–΄κ°€ μ—†λŠ” 클래슀의 λ©€λ²„μ—κ²Œ 뢙여져도 아무 효λ ₯이 μ—†λ‹€. 

A member marked override is itself open, so it may be overridden in subclasses. If you want to prohibit re-overriding, use final:

override둜 ν‘œμ‹œλœ 멀버 μžμ²΄κ°€ μ—΄λ €(open)있기 λ•Œλ¬Έμ— μ„œλΈŒν΄λž˜μŠ€μ—μ„œ μ˜€λ²„λΌμ΄λ“œ 될 수 μžˆλ‹€. 재-μ˜€λ²„λΌμ΄λ”©μ„ κΈˆμ§€ν•˜κ³  μ‹Άλ‹€λ©΄ final을 μ‚¬μš©ν•˜λΌ:

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

 

Overriding properties

The overriding mechanism works on properties in the same way that it does on methods. Properties declared on a superclass that are then redeclared on a derived class must be prefaced with override, and they must have a compatible type. Each declared property can be overridden by a property with an initializer or by a property with a get method:

μ˜€λ²„λΌμ΄λ”© λ©”μ»€λ‹ˆμ¦˜μ€ λ©”μ„œλ“œμ— 적용된 것과 같은 λ°©μ‹μœΌλ‘œ 속성듀에도 μ μš©λœλ‹€. μŠˆνΌν΄λž˜μŠ€μ—μ„œ μ„ μ–Έλœ ν›„ νŒŒμƒν΄λž˜μŠ€μ—μ„œ μž¬μ„ μ–Έλœ 속성은 override둜 μ‹œμž‘ν•΄μ•Όν•˜κ³  ν˜Έν™˜ κ°€λŠ₯ν•œ νƒ€μž…μ΄ μžˆμ–΄μ•Όν•œλ‹€. 각 μ„ μ–Έλœ 속성은 initializerκ°€ μžˆλŠ” μ†μ„±μ΄λ‚˜ get λ©”μ„œλ“œκ°€ μžˆλŠ” 속성에 μ˜ν•΄ μ˜€λ²„λΌμ΄λ“œ 될 수 μžˆλ‹€:

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

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

You can also override a val property with a var property, but not vice versa. This is allowed because a val property essentially declares a get method, and overriding it as a var additionally declares a set method in the derived class.

val 속성을 var μ†μ„±μœΌλ‘œ μ˜€λ²„λΌμ΄λ“œν•  수 μžˆμ§€λ§Œ λ°˜λŒ€μ˜ κ²½μš°λŠ” λΆˆκ°€λŠ₯ν•˜λ‹€. πŸ‘€ 이것이 ν—ˆμš©λ˜λŠ” μ΄μœ λŠ” val 속성이 본질적으둜 get λ©”μ„œλ“œλ₯Ό μ„ μ–Έν•˜κ³  이것을 var둜 μ˜€λ²„λΌμ΄λ“œν•˜λ©΄ νŒŒμƒν΄λž˜μŠ€μ—μ„œ set λ©”μ„œλ“œλ₯Ό μΆ”κ°€ μ„ μ–Έν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. 

Note that you can use the override keyword as part of the property declaration in a primary constructor:

κΈ°λ³Έ μƒμ„±μžμ—μ„œ 속성 μ„ μ–Έμ˜ μΌλΆ€λΆ„μœΌλ‘œ 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

During the construction of a new instance of a derived class, the base class initialization is done as the first step (preceded only by evaluation of the arguments for the base class constructor), which means that it happens before the initialization logic of the derived class is run.

νŒŒμƒν΄λž˜μŠ€μ˜ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λŠ” λ™μ•ˆ, 기본클래슀의 μ΄ˆκΈ°ν™”λŠ” 첫 번째둜(기본클래슀 μƒμ„±μžμ— λŒ€ν•œ 인자 평가에 μ˜ν•΄μ„œλ§Œ 선행됨) μ΄λ£¨μ–΄μ§€λŠ”λ°, μ΄κ²ƒμ˜ μ˜λ―ΈλŠ” νŒŒμƒν΄λž˜μŠ€μ˜ μ΄ˆκΈ°ν™” 둜직이 μ‹€ν–‰λ˜κΈ° 전에 ν•΄λ‹Ή λ™μž‘μ΄ μ΄λ£¨μ–΄μ§„λ‹€λŠ” 것이닀.

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() {
    println("Constructing the derived class(\"hello\", \"world\")")
    Derived("hello", "world")
}
 
μ•„λž˜λŠ” μœ„μ˜ μ½”λ“œμ— λŒ€ν•œ 좜λ ₯이닀.
 
Constructing the derived class("hello", "world")
Argument for the base class: Hello
Initializing a base class
Initializing size in the base class: 5
Initializing a derived class
Initializing size in the derived class: 10​

This means that when the base class constructor is executed, the properties declared or overridden in the derived class have not yet been initialized. Using any of those properties in the base class initialization logic (either directly or indirectly through another overridden open member implementation) may lead to incorrect behavior or a runtime failure. When designing a base class, you should therefore avoid using open members in the constructors, property initializers, or init blocks.

이 μ˜λ―ΈλŠ” κΈ°λ³Έ 클래슀 μƒμ„±μžκ°€ μ‹€ν–‰λ˜μ—ˆμ„ λ•Œ νŒŒμƒν΄λž˜μŠ€μ— μžˆλŠ” μ„ μ–Έλœ μ†μ„±λ“€μ΄λ‚˜ μ˜€λ²„λΌμ΄λ“œ 된 것듀이 아직 μ΄ˆκΈ°ν™” λ˜μ§€ μ•Šμ•˜λ‹€λŠ” 것이닀. 기본클래슀 μ΄ˆκΈ°ν™” λ‘œμ§μ—μ„œ (μ§μ ‘μ μœΌλ‘œλ“  μ˜€λ²„λΌμ΄λ“œλœ λ‹€λ₯Έ open 멀버 κ΅¬ν˜„μ„ 톡해 κ°„μ ‘μ μœΌλ‘œλ“ ) μ΄λŸ¬ν•œ 속성듀 쀑 μ–΄λŠ 것이든 μ‚¬μš©ν•˜λŠ” 것은 잘λͺ»λœ λ™μž‘μ΄λ‚˜ λŸ°νƒ€μž„ 였λ₯˜λ₯Ό μ΄ˆλž˜ν•  수 μžˆλ‹€. κ·ΈλŸ¬λ―€λ‘œ 기본클래슀λ₯Ό 섀계할 λ•Œ μƒμ„±μž, 속성, initializer ν˜Ήμ€ μ΄ˆκΈ°ν™” λΈ”λŸ­μ—μ„œ open 멀버λ₯Ό μ‚¬μš©ν•˜λŠ” 것은 ν”Όν•΄μ•Όν•œλ‹€. 

 

Calling the superclass implementation

Code in a derived class can call its superclass functions and property accessor implementations using the super keyword:

νŒŒμƒν΄λž˜μŠ€μ˜ μ½”λ“œλŠ” 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
}
 

Inside an inner class, accessing the superclass of the outer class is done using the super keyword qualified with the outer class name: super@Outer:

λ‚΄λΆ€ 클래슀 μ•ˆμ—μ„œ μ™ΈλΆ€ 클래슀의 μŠˆνΌν΄λž˜μŠ€μ— μ ‘κ·Όν•˜λŠ” 것은 super ν‚€μ›Œλ“œμ™€ μ™ΈλΆ€ 클래슀 이름을 μ‚¬μš©ν•˜λ©΄ κ°€λŠ₯ν•˜λ‹€: super@Outer:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

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()
        }
    }
}

fun main() {
    val fr = FilledRectangle()
        fr.draw()
}
μ•„λž˜λŠ” μœ„μ˜ μ½”λ“œμ— λŒ€ν•œ 좜λ ₯이닀.
Drawing a rectangle
Filling
Drawn a filled rectangle with color black

 

Overriding rules

In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits multiple implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones).

μ½”ν‹€λ¦°μ—μ„œ 상속 κ΅¬ν˜„μ€ λ‹€μŒμ˜ κ·œμΉ™λ“€λ‘œ κ·œμ •λœλ‹€: ν΄λž˜μŠ€κ°€ 직계 μŠˆνΌν΄λž˜μŠ€λ‘œλΆ€ν„° λ™μΌν•œ λ©€λ²„μ˜ μ—¬λŸ¬ κ΅¬ν˜„μ„ μƒμ†ν•˜λŠ” 경우, ν΄λž˜μŠ€λŠ” λ°˜λ“œμ‹œ 이 멀버λ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜κ³  κ³ μœ ν•œ κ΅¬ν˜„μ„ μ œκ³΅ν•΄μ•Όν•œλ‹€(μ•„λ§ˆλ„ μƒμ†λœ 것 쀑 ν•˜λ‚˜λ₯Ό μ‚¬μš©ν•˜μ—¬μ„œ).

To denote the supertype from which the inherited implementation is taken, use super qualified by the supertype name in angle brackets, such as super<Base>:

μƒμ†λœ κ΅¬ν˜„μ΄ μ–΄λ””μ„œλΆ€ν„° 온 것인지 μŠˆνΌνƒ€μž…μ„ ν‘œμ‹œν•˜κΈ° μœ„ν•΄μ„œλŠ” 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()
    }
}
 

It's fine to inherit from both Rectangle and Polygon, but both of them have their implementations of draw(), so you need to override draw() in Square and provide a separate implementation for it to eliminate the ambiguity.

Rectangleκ³Ό Polygon λͺ¨λ‘λ‘œλΆ€ν„° μƒμ†λ°›λŠ” 것은 문제 μ—†μ§€λ§Œ λ‘˜λ‹€ draw()이 κ΅¬ν˜„λ˜μ–΄ 있기 λ•Œλ¬Έμ— Squareμ—μ„œ draw()λ₯Ό μ˜€λ²„λΌμ΄λ“œν•΄μ•Όν•˜κ³  λͺ¨ν˜Έμ„±μ„ μ—†μ• κΈ° μœ„ν•΄ λ³„λ„μ˜ κ΅¬ν˜„μ„ μ œκ³΅ν•΄μ•Όν•œλ‹€. 

'Android & Kotlin' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

[λ‚΄λ³΄λ‚΄λ²ˆ] Properties | Kotin  (0) 2022.03.03
[λ‚΄λ³΄λ‚΄λ²ˆ] Classes | Kotlin  (0) 2022.03.03
Overloading / Overriding  (0) 2022.03.02
Lesson 6: App Architecture (Persistence)  (0) 2022.02.23
Lesson 5: App Architecture(UI Layer)  (0) 2022.02.22