📘 Backend/Kotlin

Kotlin - Other Features

신건우 2023. 4. 30. 04:13

💡 코틀린 - 그 외 다양한 기능들

목차

  • Type Alias & as import
  • 구조분해와 componentN 함수
  • Jump & Label
  • TakeIf & TakeUnless

Type Alias & as import

긴 이름의 클래스 & 함수 타입이 있을때 축약하거나 더 좋은 이름을 쓰고 싶을 때 사용한다.

  • type alias : 타입이나 클래스 이름에 별칭을 붙여 줄여서 사용 가능하다.
  • as import : 어떤 클래스나 함수를 import 할 때 이름을 바꾸는 기능이다.

이 함수에서 파라미터로 받는 filter 부분이 길다고 느껴질 수 있다.

그리고 filter 함수의 파라미터가 많아진다면 더 길 것이다.

fun filterFruits(fruits: List<Fruit>, filter: (Fruit) -> Boolean ) {}

이럴때 typealias 키워드를 사용해 타입의 별칭을 지정하는게 가능하다.

typealias FruitFilter = (Fruit) -> Boolean

fun filterFruits(fruits: List<Fruit>, filter: FruitFilter ) {}

이름이 킨 클래스 컬렉션에 사용할 때도 간단히 줄일 수 있다.

data class UltraSuperGuardianTribe(
    val name: String
)

typealias USGTMap = Map<String, UltraSuperGuardianTribe>

다른 패키지의 같은 이름 함수를 동시에 가져오고 싶다면 as를 사용한다.

package com.a

fun printHelloWorld() {
    println("Hello World A")
}

// ----- //

package com.b
fun printHelloWorld() {
    println("Hello World B")
}

// ----- //
import com.a.printHelloWorld as printA
import com.b.printHelloWorld as printB

fun main() {
    printA()
    printB()
}

구조분해와 componentN 함수

구조분해란?

  • 복합적인 값을 분해하여 여러 변수를 한 번에 초기화하는 것이다.
  • 구조분해 문법을 쓴다는 것은 componentN 함수를 사용한다는 소리와 마찬가지이다.
  • 아래 예시를 보자.
  • person 객체에 대한 초기화를 한번에 하고있고, 내부적으로 componentN 함수가 호출이 된 것이다.
data class Person(
    val name: String,
    val age: Int
)

fun main() {
    val person = Person("사람", 20)
    val (name, age) = Person
    println("이름 : $name, 나이 : $age")
}

data class를 만들면 기본적으로 componentN 함수를 만들어준다.


componentN 함수란?

  • 데이터 클래스는 기본적으로 자기 자신의 필드에 대한 componentN 함수를 만든다.

이게 무슨 말이냐면 위의 코드의 val (name, age) 부븐을 코드로 변환하면
내부적으로 이런 형태로 호출이 된다.

val name = person.component1()
val age = person.component2()

Data Class가 아닌 Class 에서 ComponentN 함수 구현

data class는 기본적으로 componentN 함수를 만든다고 했는데 data class가 아니면 어떻게 해야 할까?

  • 클래스의 body에 componentN 함수를 직접 구현하면 된다.
  • componentN 함수는 연산자의 속성을 가지므로 연산자 오버로딩 형식으로 구현해야 한다. (operator 키워드)
class Person(
    val name: String,
    val age: Int
) {

    operator fun component1(): String {
        return this.name
    }

    operator fun component2(): Int {
        return this.age
    }
}

리스트를 인덱스와 함께 사용할때와 Map의 entries 함수도 사실 구조분해 문법을 사용한다.

val map = mapOf(1 to "A", 2 to "B")

for ((key, value) in map.entries) {}

Jump & Label

Jump

return : 기본적으로 가장 가까운 Enclosing Function 또는 익명함수로 값이 반환된다.

break : 가장 가까운 Loop가 제거된다.

continue : 가장 가까운 Loop를 다음 Step 으로 보낸다.

위의 기능들은 기본적으로 자바와 100% 동일하다.

하지만, For-Each에서 코틀린과 자바에 차이가 있다.

코틀린의 forEach()에서는 continue나 break를 사용할 수 없다.

fun main() {
    val numbers = listOf(1, 2, 3)

    numbers.map{it + 1}.forEach{println(it)}

    for (num in)
}

forEach에서 break를 쓰고 싶을 경우 run 키워드를 사용해서 return@run 키워드를 사용해야 한다.

fun main() {
    run {
        numbers.forEach {
            number ->
            if (number == 3) {
                return@run
            }
            println(number)
        }
    }
}

forEach에서 continue를 쓰고 싶을 경우 return@forEach를 사용해야 한다.

numbers.forEach {
    number ->
    if (number == 2) {
        return@forEach
    }
}

Label

특정 Expression에 라벨이름@을 붙여 하나의 라벨로 간주하고 break, continew, return 등을 사용하는 기능

Label을 사용한 Jump는 가급적 사용하지 않는게 좋다.

왜냐하면 코드의 흐름이 위아래로 계속 움직일수록 시간복잡도가 기하급수적으로 증가한다.

  • 첫 for 문에 라벨을 붙이고 안쪽 조건문에 부합하면 라벨이 붙은 for문에 break가 걸린다.
loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (j == 2) {
            break@loop
        }
        println("${i}, ${j}")
    }
}

TakeIf & TakeUnless

코틀린은 Method Chaining을 위한 특이한 함수인 takeIf, takeUnless를 제공한다.

fun getNumberOrNull(): Int? {
    return if (number <= 0) null else number
}

코틀린은 Method Chaining을 위한 특이한 함수를 제공한다. (takeIf)

위의 코드를 리팩터링 해보자.

takeIf를 사용하면 조건을 만족하면 그 값이, 아니면 Null을 반환하는 기능을 한다.

fun getNumberOrNullV2(): Int? {
    return number.takeIf { it > 0 }
}

그리고, 주어진 조건을 만족하지 않으면 그 값이, 아니면 Null이 반환되는 takeUnless 기능이 있다.

fun getNumberOrNullV3(): Int? {
    return number.takeUnless { it <= 0 }
}