오늘 배운 것

24-05-05 Kotlin 구조 분해 선언

무무11 2024. 5. 5. 21:27

오늘은 스프링 입문 강의도 보고 알고리즘 공부도 하는 시간을 가졌다.

 

다른 사람들이 작성한 코드들을 보고 구조 분해 선언이라는 것을 알게되었고, 따라서 몇 번 사용해본 적이 있었는데 이것에 대해 제대로 알고있지는 못했어서 오늘은 이 부분에 대해 정리해보았다.

 

1. 구조 분해 선언

1-1. 구조 분해 선언이란?

객체가 가지고 있는 여러 값을 한 번에 여러 개의 변수에 넣어 선언하는 것을 말한다. 이렇게 생성된 변수들은 모두 독립적으로 사용할 수 있다.

 

코드로 직접 작성해 확인해보면 직관적이고 이해하기 쉽다. 아래는 Pair를 구조 분해 선언하여 두 개의 변수에 값을 한 번에 담는 것을 보여준다.

fun main() {
    val pair = "하하" to 20

    val (name, age) = pair

    println("name: $name, age: $age") // name: 하하, age: 20
}

위의 코드에서 한 줄로 적혀있는 구조 분해 선언은 실제로 뜯어보면 아래와 같은 두 줄의 코드와 같다.

val (name, age) = pair
// 위의 한 줄과 아래 두 줄은 동일 하다
val name = pair.component1()
val age = pair.component2()

데이터 클래스의 경우 componentN()이라는 함수가 자동으로 만들어진다.(N은 1부터 시작하는 자연수들이다.) Pair는 코틀린에서 제공하는 표준형 데이터 클래스 중 하나이고 component1(), component2() 함수가 자동으로 만들어지기 때문에 구조 분해 선언을 할 수 있는 것이다.

 

구조 분해 선언에서는 대입연산자의 오른쪽에 위치하는 대입되는 값에 해당하는 부분에 필요한 수의 componentN() 함수를 불러올 수만 있다면 어떤 것이든 대입할 수 있다.

 

코드를 작성해 확인해보는 것이 이해가 더 빠르다. 아래처럼 임의의 클래스를 하나 만들고 연산자 오버로딩을 이용해 componentN() 함수를 오버로드하여 정의해주면 구조 분해 선언을 할 수 있다.

fun main() {
    val test = TestClass(10,20,30)

    val (x,y,z) =  test

    println("x: $x, y: $y, z: $z")
    // x: 10, y: 20, z: 30

    val q = test.component1()
    val w = test.component2()
    val e = test.component3()

    println("q: $q, w: $w, e: $e")
    // q: 10, w: 20, e: 30
}

class TestClass (val a:Int, val b:Int, val c:Int) {
    operator fun component1():Int = a
    operator fun component2():Int = b
    operator fun component3():Int = c
}

위에서 언급했듯 데이터 클래스의 경우는 주생성자에 들어있는 속성의 경우 자동으로 순서에 따라 componentN() 함수가 만들어진다. 배열이나 컬렉션에서도 앞의 5개에 해당하는 요소에 대해서는 componentN() 함수가 자동으로 만들어진다.

 

1-2. 배열과 컬렉션에서의 구조 분해 선언

배열과 컬렉션에서는 앞에서부터 5개의 해당하는 요소에 대해서는 componentN() 함수가 자동으로 만들어지기 때문에 그냥 구조 분해 선언을 이용할 수 있다.

 

만약 5개를 넘어서는 숫자의 요소에 대해 구조 분해 선언을 이용하고 싶다면 확장함수와 연산자 오버로딩을 이용해 componentN() 함수를 정의해주어야 한다. 아래 코드에서는 List에 확장함수를 이용해 component6()를 정의해보고 사용해보았다.

fun main() {
    val list = listOf(1,2,3,4,5,6)

    // 앞에서부터 5개의 요소에 대해서는 그냥 구조 분해 선언이 가능하다
    val (x,y,z) = list

    println("x: $x, y: $y, z: $z")
    // x: 1, y: 2, z: 3

    // 6개부터는 연산자 오버로딩으로 직접 componentN() 함수를 정의해야한다
    val (a,b,c,d,e,f) = list

    println("a: $a, b: $b, c: $c, d: $d, e: $e, f: $f")
    // a: 1, b: 2, c: 3, d: 4, e: 5, f: 6
}

operator fun <E> List<E>.component6() = this[5]

 

1-3. 데이터 클래스, Pair와 Triple

위에서도 언급했다시피 데이터 클래스는 주생성자에 들어있는 속성에 대해서는 componentN() 함수가 자동으로 만들어지기 때문에 그냥 구조 분해 선언을 이용할 수 있다.

 

Pair와 Triple은 두 개, 세 개의 값을 저장할 수 있는 데이터 클래스로 하나의 함수에서 2개 또는 3개의 값을 반환받는 등 필요한 경우에 편하게 사용할 수 있다.

 

아래 코드에서처럼 데이터 클래스를 이용하면 쉽게 구조 분해 선언을 이용해 객체 안의 값들을 여러 변수에 한 번에 넣어줄 수 있다.

fun main() {
    val pair = "메롱" to 30.0
    val triple = Triple("하하", 2, 'A')

    val (a, b) = pair
    val (x, y, z) = triple

    println("a: $a, b: $b, x: $x, y: $y, z: $z")
    // a: 메롱, b: 30.0, x: 하하, y: 2, z: A

    val numbers = Numbers(11, 22, 33, 44, 55, 66)

    val (q, w, e, r, d, f) = numbers
    println("q: $q, w: $w, e: $e, r: $r, d: $d, f: $f")
    // q: 11, w: 22, e: 33, r: 44, d: 55, f: 66
}

data class Numbers (
        val num1:Int,
        val num2:Int,
        val num3:Int,
        val num4:Int,
        val num5:Int,
        val num6:Int
)

이외에도 루프문 안에서 구조 분해 선언을 활용하는 법, 람다안에서 구조 분해하는 법 등 여러가지 더 정리해보고 싶은 것들이 있지만 정리하는데 생각보다 시간이 오래 걸려서 오늘은 여기까지 정리해보겠다.

 

내일마저 정리해서 올릴 예정이다.

 

2. 오늘 배운 것

- 코틀린의 구조 분해 선언에 대해 자세히 알아보고 정리해보았다.

- 스프링 입문 강의를 모두 들었다. 웹 어플리케이션 만들기를 빨리 해보고 싶다.