티스토리 뷰

반응형
SMALL

이번 포스트에서는 Kotlin에서 상속을 구현하는 방법에 대해 정리해보려고 합니다. 상속은 Java에서도 마찬가지로 객체 지향에서 가장 중요한 개념 중 하나입니다. Kotlin에서의 상속은 Java에서의 상속과 거의 비슷하나, 생성자 관계, 상속에 의한 캐스팅 부분에서 약간의 차이를 보이고 있습니다.

 

Kotlin에서의 상속

Any 클래스

상속 : 클래스를 선언할 때 다른 상위 클래스를 참조해 작성하는 개념

일반적으로 Kotlin에서 클래스를 선언하면 Any 클래스를 상속받게 됩니다. (우리가 굳이 작성하지 않아도 자동으로 Any 클래스를 상속받도록 되어 있습니다.) 왜냐하면 Kotlin의 모든 클래스는 Any 클래스의 서브 클래스이기 때문이죠.

Any 클래스는 자바에서 Object 클래스와는 다릅니다. Any 클래스는 equals(), toString() 이외의 다른 멤버들은 제공하지 않습니다.

class Shape {
    var x: Int = 0
    var y: Int = 0
    var name: String = "Rect"
    
    fun draw() {
    	println("draw $name : location : $x, $y")
    }
}

fun main(args: Array<String>) {
    val obj1: Any = Shape()
    val obj2: Any = Shape()
    val obj3: obj1
    println("obj1.equals(obj2) is ${obj1.equals(obj2)}")
    println("obj1.equals(obj3) is ${obj1.equals(obj3)}")
}

위 코드의 실행 결과는 어떻게 될까요?

obj1.equals(obj2) is false
obj1.equals(obj3) is true

이런 결과가 나오게 됩니다.

 

Shape라는 클래스를 선언했는데 Any 타입으로 obj1, obj2를 생성하고 있습니다. 또한 equals라는 메소드를 사용하고 있습니다. 이는 Any클래스를 상속받는다는 것을 의미합니다.

이렇듯 class Shape { } 라는 부분은 class Shaep : Any() { } 라고 보면 된다는 것을 알 수 있습니다.

 

상속을 통한 클래스 정의

클래스를 정의할 때 다른 클래스를 상속받아 정의하면 상위 클래스 (상속 대상이 되는 클래스)에 정의된 멤버(함수, 프로퍼티)를 하위 클래스에서 자신의 멤버처럼 사용할 수 있습니다.

상속의 기본적인 개념은 Java에서 배우셨을 거라 생각하고 넘어가겠습니다.(구글에게 물어보세요)

예시 코드를 살펴보겠습니다.

open class Car {
    var speed: Int = 0
        set(value) {
            if(value < 0) field = 0
            else field = value
        }
    
    var oil: Int = 100
        set(value) {
            if(value < 0) field = 0
            else field = value
        }
    
    lateinit var name: String
    
    fun print() {
        println("$name : speed/oil : $x / $y")
    }
}

Car 클래스에 speed, oil, name이라는 프로퍼티와 print()라는 함수를 정의했습니다. 클래스 내부의 코드는 이미 살펴보았던 프로퍼티와 함수 선언입니다.

이제 Car 클래스를 상속 받아 Truck과 Bus 클래스를 생성해 보도록 하겠습니다.

그런데 Kotlin에서 어떤 클래스를 상속을 받으려면 해당 클래스의 선언부에 상속 허용 여부를 결정하는 Keyword(예약어)를 명시해야 합니다. Kotlin에서는 open이라는 Keyword를 사용합니다. Kotlin에서 상속 허용 여부를 결정하는 기본 값은 final 입니다. 즉, 개발자가 어떤 클래스를 선언할 때 상속 허용 여부를 명시하지 않으면 기본으로 final이 적용됩니다.

class Truck: Car() {
    var loadObjSize: Int = 0
        set(value) {
            if(value < 0) field = 0
            else field = value
        }
    
    var weight: Int = 0
        set(value) {
            if(value < 0) field = 0
            else field = value
        }
}

class Bus: Car() {
    var passenger: Int = 0
        set(value) {
            if(value < 0) field = 0
            else field = value
        }
}

Car 클래스를 상속받아 Truck과 Bus 클래스를 선언하였습니다. main 메소드에서 자유롭게 사용해보시기 바랍니다.

 

오버라이드 (Override)

함수 오버라이드 (Function Override)

open class Shape {
    ...
    open fun print() {
        println("$name : location : $x, $y")
    }
}    

print()라는 함수를 Override하는 예시입니다. 역시 open이라는 예약어를 명시해주었습니다.

class Rect: Shape() {
    ...
    override fun print() {
        println("$name : location : $x, $y")
        println("width : $width / height : $height")
    }
}

class Circle: Shape() {
    ...
    override fun print() {
        println("$name : location : $x, $y")
        println("radius : $r")
    }
}

Rect 클래스와 Circle 클래스는 Shape 클래스를 상속받았습니다.

함수를 오버라이드 하려면 override 키워드(예약어)를 사용해야 합니다. 

fun main(args: Array<String>) {
    val rect = Rect()
    rect.name = "Rect"
    rect.x = 10
    rect.y = 10
    rect.width = 20
    rect.height = 20
    rect.print()
    
    val circle = Circle()
    circle.name = "Circle"
    circle.x = 30
    circle.y = 30
    circle.r = 5
    circle.print()
}

자, override 키워드를 사용하는 메소드를 또 상속하는 하위 메소드를 만들 수 있을까요?

YES 입니다. 상속에 상속을 더 할 수도 있습니다. 하지만 이를 막을 수 있는 방법이 있습니다. 아래 코드를 봅시다.

open class Rect: Shape() {
    ...
    final override fun print() {
        // ...
    }
}

final 키워드를 override 앞에 붙이면 더이상 하위 클래스에서 재정의할 수 없도록 막을 수 있습니다.

 

프로퍼티 오버라이드(Property Override)

Property의 오버라이드도 함수의 오버라이드와 같은 개념입니다. 

open class Super {
    open val name: String = "KKIIMM"
}

open class Sub: Super() {
    final override var name: String = "LEE"
}
open class Super {
    open val name: String = "KKIIMM"
    open val age: Int = 10
    open val email: String? = null
    open val address: String = "seoul"
}

open class Sub: Super() {
    final override var name: String = "LEE"  //성공
    final override val age: Int = 20   //실패
    final override val email: String = "test@test.com"   //성공
    final override val address: String? = null    //실패
}

위 코드는 상위클래스에서 val로 정의된 프로퍼티를 하위 클래스에서 var로 재정의하는 예제입니다.

val면 하위 클래스에서 var로 재정의할 수 있습니다. 따라서 final override val age: Int = 20은 에러가 납니다.

final override val address: String?= null 은 null 비허용으로 선언된 프로퍼티를 ?를 사용하여 null 허용으로 재정의하였으므로 컴파일 에러가 나게 됩니다.

 

 

다음 포스트에서는

 

- 상위 클래스 멤버 접근(super 키워드)

- 상위 클래스 생성자 호출

- 상하위 생성자의 수행 흐름

- 상속과 캐스팅

- as를 이용한 캐스팅

- null 허용 객체의 캐스팅(as?)

- 접근 제한자

 

에 대해 살펴보겠습니다.

반응형
LIST

'프로그래밍언어 > Kotlin' 카테고리의 다른 글

Kotlin 접근 제한자  (0) 2019.07.28
Kotlin 상속 (Kotlin Override) - 2  (0) 2019.07.28
Kotlin에 대한 궁금증? (작성중)  (0) 2019.07.07
Kotlin 연산자 재정의  (0) 2019.07.07
Kotlin 전개 연산자의 이해  (0) 2019.07.07
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함