Kotlin

[Kotlin] 구조 분해 선언과 component 함수

sh1mj1 2024. 1. 24. 14:29

Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.

 

이전에 data class 를 다룬 글에서 간단히 구조 분해 선언(destructuring declaration) 을 살펴보았다

구조 분해를 사용하면 복합적인 값을 분해해서 여러 다른 변수를 한꺼번에 초기화할 수 있다.

구조 분해를 사용하는 방법

val p = Point(10, 20)
val (x, y) = p
assert(x == 10 && y == 20)

 구조 분해 선언은 일반 변수 선언과 비슷하지만 `=` 의 좌변에 여러 변수를 괄호로 묶는다.

구조 분해 선언은 내부적으로 관례를 사용

내부에서 구조 분해 선언은 관례를 사용한다.

구조 분해 선언의 각 변수를 초기화하기 위해 `componentN` 이라는 함수를 호출한다.

N 은 구조 분해 선언에 있는 변수 위치에 따라 붙은 번호이다.

data 클래스의 주 생성자에 들어있는 프로퍼티에 대해서는 컴파일러가 자동으로 `componentN` 함수를 만들어준다.

 

data 클래스가 아닌 클래스에서 `componentN` 함수 구현

class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

 구조 분해 선언은 여러 값을 리턴할 때 유용하다. 

 

보통 여러 값을 한번에 리턴해야 하는 함수가 있다면  리턴해야 하는 모든 값이 들어갈 data class 를 정의하고 함수의 리턴 타입을 그 데이터 클래스로 바꾼다.

구조 분해 선언 구문을 사용하면 이런 함수가 리턴하는 값을 쉽게 풀어서 여러 변수에 넣을 수 있다.

 

구조 분해 선언을 사용해 여러 값 리턴하기

data class NameComponents(val name: String, val extension: String)

fun splitFileName(fullName: String): NameComponents {
    val result = fullName.split('.', limit = 2)
    return NameComponents(result[0], result[1])
}

val (name, ext) = splitFileName("example.kt")
assert(name == "example" && ext == "kt")

 배열이나 컬렉션에도 `componentN` 함수가 있다. 

`split` 확장 함수는 `List` 를 리턴하므로, 즉, `splitFileName` 함수는 아래처럼 개선할 수 있다.

 

컬렉션에 대해 구조 분해 선언 사용하기

fun splitFileName(fullName: String): NameComponents {
    val (name, ext) = fullName.split('.', limit = 2)
    return NameComponents(name, ext))
}

 물론 무한히 `componentN` 을 선언할 수는 없다.

코틀린 stlib 에서는 컬렉션의 맨 앞의 다섯 원소만 `componentN` 을 제공한다. 

 

표준 라이브러리의 `Pair` 나 `Triple` 클래스를 사용하면 함수에서 여러 값을 더 간단히 리턴할 수 있다.

물론 Pair 와 Triple 은 그 안에 담겨있는 원소의 의미를 말해주지 않으므로 가독성이 떨어질 수 있다.

이와 관련해서는 이 글의 하단 에서 설명하고 있다.

구조 분해 선언과 루프

함수 바디 내의 선언문 뿐 아니라 변수 선언이 들어갈 수 있는 장소라면 어디든 구조 분해 선언이 가능하다.

루프 안에서도 구조 분해 선언이 가능하다. 특히 맵의 원소에 대해 iteration 할 때 유용하다.

 

구조 분해 선언을 사용해 맵 iteraiton 하기

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) { // 루프 변수에 구조 분해 선언 사용
        println("$key -> $value")
    }
}

val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
printEntries(map)
// print
/*
Oracle -> Java
JetBrains -> Kotlin
*/

 위 코드는 객체를 iteration 하는 관례와, 구조 분해 선언 관례를 사용하고 있다. 

  • 코틀린 stlib 에는 맵에 대한 확장함수로 맵 원소에 대한 iterator 를 리턴하는 iterator 가 들어있다.
    • 그래서 자바와 달리 코틀린에서는 맵을 직접 iteration 할 수 있다.
  • 또한 kotlin lib 는 `Map.Entry` 에 대한 확장 함수로 `component1` 과 `component2` 를 제공한다. 
    • 위 루프는 아래와 같은 확장 함수를 사용하는 아래 코드와 같다.
for (entry in map.entries) {
    val key = entry.component1()
    val value = entry.component2()
    // ...
}