Kotlin

[Kotlin] 상속/ interface/ abstract class/ object/ data class/ enum class

노소래 2021. 8. 5. 01:41

- open 키워드와 상속

다른 클래스를 상속하는 방법은

현재클래스이름(생성자) : 상속클래스이름(생성자)

코틀린은 default 로 부모 클래스의 properties 와 member variables 를 private 이 아닌 이상 접근은 가능하지만  override 할 수 없다. 

자식클래스가 override 할 수 있게 하려면, 부모클래스에서 open 키워드를 사용해야한다. 

클래스는 물론 생성자에 있는 멤버변수 모두에 각각 open 키워드를 붙이고 하위클래스에서 override 할 수 있다.

open class Aquarium constructor(open var width: Int = 20, open var height: Int = 40, open var length: Int = 100) {

   
   
    open var water: Double = 0.0
        get() = volume * 0.9
        
   	open val shape = "rectangle"
        
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }

    fun printSize() {
        println(shape)
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
        // 1 l = 1000 cm^3
        println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
    }
}

class TowerTank (override var height: Int, var diameter: Int)
    : Aquarium(height = height, width = diameter, length = diameter) {

    override var water = volume * 0.8
    override val shape = "cylinder"
    override var volume: Int
        // ellipse area = π * r1 * r2
        get() = (width/2 * length/2 * height / 1000 * PI).toInt()
        set(value) {
            height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
        }

    }

Aquarium 클래스를 TowerTank 클래스가 상속하고 있다. 

package aquarium

fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
fun main() {
    buildAquarium()
}

그리고 위와같이 실행하면 결과는 아래와 같다.

rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

 

 

- interface vs abstract class

abstract class AquariumFish {
    abstract val color: String
}

class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
interface FishAction  {
    fun eat()
}

우선 abstract class 에 대해 알아보자

abstract class 는 항상 open 이다. 그래서 따로 open 키워드를 써줄 필요가 없다. 추상클래스 안의 멤버들도 따로 다 abstract 키워드를 붙이면 open 을 따로 적을 필요가 없다.

abstract 키워드가 붙으면 그 추상클래스를 상속한 하위클래스는 반드시 그 멤버를 implement 해야한다.

 

이제 interface 와 비교해보자

일단 공통점은 interface 와 abstract class 는 하위클래스에 가져와서 구현한다는 흐름을 가진다는 것이다. 즉 공통적인 것? 들을 묶어 공유한다는 점에서는 비슷하다. 당연히 두가지 모두 바로 객체를 생성할 수 없다는 점도 그렇다.

차이점은 interface 는 생성자를 가질 수 없는 반면, abstract class 는  생성자를 가질 수 있다.

또 차이점은 구현클래스는 하나의 abstract class 밖에 구현하지 못한다는 점이다. interface 는 여러개를 받아와서 구현할 수 있다.

 

참고로 abstract class 에서 interface 를 구현해주는 방법도 있다.

 

 

- singleton class ( object 키워드 ) 와 delegation 개념

object 키워드가 붙은 클래스는 코틀린이 딱 하나의 객체를 만들고 전역에서 obect 클래스의 이름으로 해당 클래스를 참조할 수 있게 해준다.

이것도 interface delegation 으로 사용할 수 있다. 구현클래스에서 ` : interface이름` 으로 인터페이스를 가져오고 뒤에 `by object클래스이름` 을 써주면된다. 여기서 interface delegation 이란 인터페이스를 대표로 구현한 대표클래스(delegate 대표 뜻이 대리자)를 말하는 것 같다. `: 인터페이스이름 by delegation클래스이름` 으로 써주면 된다. 아래 코드에서 Plecostomus 클래스를 집중적으로 참고한다.  

object 클래스와 그냥 class 를 interface delegation 으로 사용하였다.

 

package aquarium



class Shark:  FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus(fishColor: FishColor = GoldColor) :
    FishAction by PrintingFishAction("eagle"),
    FishColor by fishColor


interface FishAction  {
    fun eat()
}

interface FishColor {
    val color: String
}

object GoldColor : FishColor {
    override val color = "gold"
}
class PrintingFishAction(private val food: String = "bob"): FishAction {
    override fun eat() {
        println("eat $food")
    }
}

이렇게 interface delegation 을 사용함으로써 많은 하위클래스를 만드는 것을 막을 수 있다. 

 

 

- data class

data class Decoration(
    val rock: String
)
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)

    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
    println (decoration1 == decoration2)
    println (decoration2 == decoration3)
    println (decoration1 == decoration3)
}

makeDecorations 을 한번 호출했을 때 결과는 아래와같다.

Decoration(rock=granite)
Decoration(rock=slate)
Decoration(rock=slate)
false
true
false
true
false

우선 equals 를 오버라이딩하지 않았는데도 자동으로 생성되었음을 확인할 수 있다.

자바에서는 마지막에서 두번째 줄도 true 가 아닌 false 가 나올텐데 코틀린은 true 가 나오는 것을 확인할 수 있다. 즉 코틀린에서 같은 data class 객체끼리의 == 연산은 equals 와 같은 것이다. 영어로는 structural equality 라고 한다. contents 가 같다고도 표현한다.

두 객체가 같은 객체를 참조하고 있는지 확인하려면 === 연산자를 사용해야한다. 

copy() 함수로 contents 를 복사한 새 객체를 만들 수 있다.

주의할 점은 copy(), equals() 와 같은 data class utility 는 오직 priamry constructor 에 정의된 properties 만 참조한다는 점이다.

data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations2() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}

val (rock, wood, diver) = d5 에 주목하자 data class 객체로 변수 여러개를 한번의 = 로 처리할 수 있다. 이러한 것을 destructuring 이라고 부른다. 변수들의 개수와 순서가 data class 에 정의된 properties 와 일치하다고 생각해야한다.

 

참고.

디스트럭처링(Destructuring)은 구조화된 배열 또는 객체를 Destructuring(비구조화, 파괴)하여 개별적인 변수에 할당하는 것이다. (출처 : https://poiemaweb.com/es6-destructuring )

  val (rock, _, diver) = d5

 

위와같은 방식으로 하나를 건너뛰고 두개만 받아올 수도 있다.

 

- enum class 

다른 언어의 enum 과 비슷하다.  이름 열거해서 참조한다. 

이름마다 () 안에 생성자 자리에 있는 변수값들을 써준다. 그리고 name, ordinal, degrees 프로퍼티를 사용해서 값을 가져올 수 있다. 

선언과 사용방법은 아래와 같다. 두 번째 코든느 생성자위치에 변수를 두개 둬서 사용해보았다.

enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
EAST
2
90
enum class Direction(val degrees: Int, val a: Int) {
    NORTH(0, 1), SOUTH(180, 181), EAST(90, 91), WEST(270, 271)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
    println(Direction.EAST.a)
}
EAST
2
90
91

 

 

 

 

 

- 출처 : codelabs

https://developer.android.com/codelabs/kotlin-bootcamp-classes