개발/코틀린

(코틀린) 특별한 클래스 사용하기

DinoDev 2022. 10. 23. 19:18
728x90
반응형

enum class

미리 정의된 상수들로 이뤄진 제한된 집합을 표현하는 클래스 입니다.

보통 enum은 대문자로 정의 합니다.

enum class Weekday {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

fun Weekday.isWorkday() =
    this in listOf(
        Weekday.MONDAY,
        Weekday.TUESDAY,
        Weekday.WEDNESDAY,
        Weekday.THURSDAY,
    )

fun main() {
    println(Weekday.MONDAY.isWorkday()) // true
    println(Weekday.SUNDAY.isWorkday()) // false
}

빠뜨린 부분이 없는 when 식

when 식에서 모든 enum 상수를 다룬 경우에는 else를 생략 할 수 있습니다. 이는 새로운 enum이 생겼을 때 컴파일 오류를 발생해서 개발자의 실수를 막아줍니다.

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

fun rotateClockWise(direction: Direction) = when (direction) {
    Direction.NORTH -> Direction.EAST
    Direction.SOUTH -> Direction.WEST
    Direction.WEST -> Direction.NORTH
    Direction.EAST -> Direction.SOUTH
}

커스텀 멤버가 있는 enum 정의하기

enum class도 프로퍼티나 함수를 가질 수 있습니다.

enum class RainbowColor(val isCold: Boolean) {
    RED(false),
    ORANGE(false),
    YELLOW(false),
    GREEN(true),
    BLUE(true),
    INDIGO(true),
    VIOLET(true),
    ;

    val isWarm: Boolean
        get() = isCold.not()
}

fun main() {
    println(RainbowColor.RED.isWarm) // true
    println(RainbowColor.BLUE.isCold) // true
}

enum class의 공통 멤버 사용하기

kotlin enum class는 kotlin.Enum 클래스의 하위 타입입니다. 그렇기 때문에 모든 enum class가 사용할 수 있는 몇 가지 공통 함수와 프로퍼티가 있습니다.

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

fun main() {
    println(Direction.WEST.name) // WEST
    println(Direction.SOUTH.ordinal) // 1

    println(Direction.SOUTH > Direction.EAST) // false
    println(Direction.SOUTH < Direction.EAST) // true

    println(Direction.values()[1]) // SOUTH
    println(Direction.valueOf("EAST")) // EAST
    println(Direction.valueOf("NORTH_EAST")) // Exception in thread "main" java.lang.IllegalArgumentException: No enum constant Direction.NORTH_EAST

    println(enumValues<Direction>()[1]) // SOUTH
    println(enumValueOf<Direction>("NORTH")) // NORTH
    println(enumValueOf<Direction>("NORTH_EAST")) // Exception in thread "main" java.lang.IllegalArgumentException: No enum constant Direction.NORTH_EAST
}

data class

데이터를 저장하기 위한 목적으로 사용하고 몇가지 유용한 함수들을 제공합니다.

data class와 data class에 대한 연산

일반 클래스의 비교는 인스턴스가 같은지 비교하고 내부 프로퍼티가 같은지는 비교하지 않습니다.

class Person(
    val firstName: String,
    val familyName: String,
    val age: Int,
)

fun main() {
    val person1 = Person("석준", "정", 31)
    val person2 = Person("석준", "정", 31)
    val person3 = person1

    println(person1 == person2) // false
    println(person1 == person3) // true
}

클래스에서 동등성 비교가 필요하면 equals()와 hashCode()를 구현해야 합니다.
하지만 data class는 이런 메서드를 자동으로 생성해 줍니다.

data class Person(
    val firstName: String,
    val familyName: String,
    val age: Int,
)

fun main() {
    val person1 = Person("석준", "정", 31)
    val person2 = Person("석준", "정", 31)
    val person3 = person1

    println(person1 == person2) // true
    println(person1 == person3) // true
}

person1과 person2의 비교에서 true가 나온 것을 확인 할 수 있습니다.

equals()화 hashCode()말고 toString()도 자동으로 생성해 줍니다.

data class Person(
    val firstName: String,
    val familyName: String,
    val age: Int,
)

fun main() {
    val person = Person("석준", "정", 31)
    println(person) // Person(firstName=석준, familyName=정, age=31)
}

copy()함수를 통해서 깊은 복사를 할 수 있습니다.
새로운 인스턴스를 만들면서 프로퍼티도 변경 할 수 있습니다.

data class Person(
    val firstName: String,
    val familyName: String,
    val age: Int,
)

fun Person.show() = println("$firstName, $familyName, $age")

fun main() {
    val person = Person("석준", "정", 31)

    person.show() // 석준, 정, 31
    person.copy().show() // 석준, 정, 31
    person.copy(firstName = "디노").show() // 디노, 정, 31
}

kotlin 표준 라이브러리에는 Pair와 Triple라는 범용 data class가 있습니다.

fun main() {
    val pair = Pair(1, "two")
    println(pair.first + 1) // 2
    println("${pair.second}") // two

    val triple = Triple("one", 2, false)
    println("${triple.first}!") // one!
    println(triple.second - 1) // 1
    println(!triple.third) // true
}

Destructuring declaration(구조 분해 선언)

Destructuring declaration을 사용하면 하나하나 변수 선언하지 않고 변수를 선언 할 수 있습니다.
Destructuring declaration은 () 괄호를 이용해서 사용합니다.

data class Person(
    val firstName: String,
    val familyName: String,
    val age: Int,
)

fun main() {
    val person = Person("석준", "정", 31)
    // val firstName: String = person.firstName
    // val familyName: String = person.familyName
    // val age: Int = person.age
    val (firstName, familyName, age) = person // 구조 분해해서 각각 변수에 대입
    val (_, familyName2) = person // 0번째 프로퍼티를 생략해서 구조 분해

    val pairs = listOf(1 to "one", 2 to "two", 3 to "three")
    for ((number, name) in pairs) {
        // List<Pair>를 for에서 구조 분해
        println("$number $name")
    }
}

value class

단순히 값 하나를 랩핑해서 사용하는 클래스인 래퍼 클래스를 만드는 일이 간혹 있는데 클래스 타입으로 구분해서 개발자의 실수를 줄여 주는 역할을 합니다.
하지만 이런 방법은 런타임 시점에 비용이 추가로 발생하게 됩니다.

value class 정의하기

value class를 정의 할 때 JVM을 사용하는 경우 @JvmInline annotation을 추가 해줘야 합니다.
value class는 주 생성자에 불변 프로퍼티를 하나만 선언해야 합니다. 런타임에 클래스 인스턴스는 별도의 래퍼 객체를 생성하지 않고 프로퍼티의 값으로 표현됩니다. 또한 함수와 프로퍼티를 포함할 수 있습니다.

@JvmInline
value class Euro(val amount: Int)

@JvmInline
value class Dollar(val amount: Int) {
    fun add(d: Dollar) = Dollar(amount + d.amount)
    val isDebt: Boolean
        get() = amount < 0
}

fun main() {
    println(Dollar(15).add(Dollar(20)).amount) // 35
    println(Dollar(-100).isDebt) // true
}

널이 될 수 있는 타입에 대해서는 value class가 inline이 되지 않습니다.

@JvmInline
value class Dollar(val amount: Int)

fun safeAmount(dollar: Dollar?) = dollar?.amount ?: 0

fun main() {
    println(Dollar(15).amount) // 인라인됨
    println(Dollar(15)) // Any?로 사용되기 때문에 인라인되지 않음
    println(safeAmount(Dollar(15))) // Dollar?로 사용되기 때문에 인라인되지 않음
}

부호 없는 정수

부호가 없는 정수 타입이 kotlin 1.5 부터 표준 라이브러리 기능으로 정식 도입 됐습니다.
부호 없는 정수 타입의 이름은 상응하는 부호 있는 정수 타입의 이름 앞에 U를 붙이는 방식입니다.

val uByte: UByte = 1u // 명시적으로 UByte
val uShort: UShort = 100u // 명시적으로 UShort
val uInt = 1000u // 자동으로 UInt 추론
val uLong: ULong = 1000u // 명시적으로 ULong
val uLong2 = 1000uL // L접미사를 붙었기 때문에 명시적으로 ULong

부호가 있는 타입과 부호가 없는 타입의 값을 toXxx()를 통해 반대쪽 타입으로 변환할 수 있습니다.

fun main() {
    1.toUByte() // 1, Int -> UByte
    (-100).toUShort() // 65436, Int -> UShort
    200u.toByte() // -56, UInt -> Byte
    1000uL.toInt() // 1000, ULong -> Int
}
728x90
반응형

'개발 > 코틀린' 카테고리의 다른 글

(코틀린) 클래스 계층 이해하기  (0) 2022.11.06
(코틀린) 컬렉션  (0) 2022.10.30
(코틀린) 함수형 프로그래밍  (0) 2022.10.10
(코틀린) 클래스  (0) 2022.10.03
(코틀린) 예외처리  (0) 2022.09.25