Kotlin Array, List, Data Class destructuring and ComponentN max count
코틀린 Array, List, Data Class 의 구조분해와 ComponentN 사용
Destructuring (구조 분해)
코틀린에서 리스트
나 배열
, Data Class
등의 값을 각각 변수에 담고 싶을 때 구조 분해를 사용할 수 있다.
fun main() {
val li = listOf(1, 2, 3)
val (n1, n2, n3) = li
println("$n1, $n2, $n3") // 1, 2, 3
val arr = charArrayOf('a', 'b', 'c')
val (c1, c2, c3) = arr
println("$c1, $c2, $c3") // a, b, c
data class DoubleClass(val d1: Double, val d2: Double, val d3: Double)
val dc = DoubleClass(3.2, 1.5, 5.43)
val (d1, d2, d3) = dc
println("$d1, $d2, $d3") // 3.2, 1.5, 5.43
}
파이썬을 사용해 본 사람이 있다면 unpack
과 비슷한 개념이라고 생각하면 될 것 같다. (파이썬과 다르게 코틀린에서는 괄호로 감싸야 함)
fun main() {
val fruitPrice = mapOf("melon" to 10000, "banana" to 3000, "tomato" to 1000)
for ((fruit, price) in fruitPrice) {
println("$fruit 가격은 $price 원")
}
/*
melon 가격은 10000 원
banana 가격은 3000 원
tomato 가격은 1000 원
*/
}
위와 같이 for 문으로 접근할 수도 있다. 코틀린에서 Map
은 각각의 Key-Value 셋을 Pair
로 관리한다.
이 말은 즉, Pair
도 구조 분해가 가능하고, 비슷한 타입인 Triple
역시 구조 분해가 가능하다.
fun main() {
val science = Pair("science", 100)
val pe = Triple("pe", 100, 80)
val (subject1, score) = science
val (subject2, testScore, activityScore) = pe
println("$subject1 점수는 $score 점") // science 점수는 100 점
println("$subject2 시험 점수는 $testScore, 활동 점수는 $activityScore 점") // pe 시험 점수는 100, 활동 점수는 80 점
}
fun main() {
val pe = Triple("pe", 100, 80)
val (subject2, _, activityScore) = pe
println("$subject2 활동 점수는 $activityScore 점") // pe 활동 점수는 80 점
}
사용하지 않을 값은 _
로 하면 값을 구조분해 하지 않고 넘어간다.
그 외에도 .map
같은 확장 함수에서도 활용할 수 있다.
구조 분해 최대 개수
구조 분해도 상황에 따라 무한히 할 수 있는 것은 아니다.
- Pair - 2 개
- Triple - 3 개
- List, Array - 5 개
- Data Class - 제한 없음
Pair
나 Triple
은 당연한 말이고, List
, Array
, Data Class
는 다른 개수를 가지고 있다.
내부를 확인해보자
Data Class - ComponentN
Data Class
를 만들어서 자바 코드로 확인해보자.
data class Person(
val name: String,
val age: Int,
val married: Boolean
)
디컴파일 결과 다양한 내용이 있지만 표시해 둔 component1, 2, 3
이 있는 것을 볼 수 있다. 그리고 각각은 멤버 변수가 선언된 순서대로 각각을 반환하는 역할을 한다.
그렇다면 이것을 몇 개 까지 만들 수 있을까?
궁금해서 많은 수의 멤버 변수를 가지는 Data Class
를 만들어 보았다.
…
총 510 개의 멤버 변수를 가지고 있다. 디컴파일 해보자.
…
예상했던대로(?) 선언한 510 개 모두에 대해서 componentN()
메서드가 생긴다. 그리고 좀 더 알아본 결과 시스템이 허용하는 한 무제한 생길 수 있다.
왜 갑자기 componentN
이야기를 꺼냈냐면 componentN 이 바로 구조 분해를 하기 위한 메서드
이기 때문이다.
확인해보자.
data class Person(
val name: String,
val age: Int,
val married: Boolean
)
fun personTest() {
val me = Person("mangbaam", 27, false)
val (name, age, married) = me
}
위와 같이 코드를 작성하고 디컴파일 해보면 다음과 같이 나온다.
name
과 age
가 var1
, var2
로 표현되긴 했지만 me.component1, 2, 3
을 통해서 값을 할당하는 것을 확인할 수 있다.
val me = Person("mangbaam", 27, false)
me.component1()
me.component2()
me.component3()
componentN
은 public
메서드이기 때문에 위와 같이 직접 접근도 가능하다.
구조 분해는 componentN() 을 통해서 이루어진다
(보너스) Pair와 Triple 이 구조 분해가 가능한 이유
Pair
와 Triple
의 구현체는 Data Class
이다. 그래서 당연하게도 구조 분해가 가능한 것이다.
List, Array - ComponentN
List
와 Arr
모두 Data Class
와 마찬가지로 componentN()
가 있다.
그런데 이상한 점은 li
, arr
모두 값을 8개나 가지고 있는데 component
는 5개만 존재한다.
그래서 구조 분해를 시도하니 위에서 확인했던 것과는 다르게 에러가 발생하고 있는 것을 볼 수 있다.
에러 내용을 보면 component6()
, component7()
, component8()
이 없다는 것 같다.
디컴파일 해보자
val li = listOf(1, 2, 3, 4, 5, 6, 7, 8)
val (n1, n2, n3, n4, n5) = li
컴파일 에러가 발생하면 디컴파일을 할 수 없기 때문에 구조 분해 개수를 줄이고 디컴파일을 해보았다.
이상한 점이 있다. 분명 Data Class
를 디컴파일 했을 때는 componentN()
으로 값을 할당하고 있었는데 List
를 구조 분해 해보니 get()
을 통해서 값을 할당하고 있다. 하지만 분명 component1()
과 같이 접근할 수는 있었다.
사실은 확장함수
사실 List 나 Array 는 자체적으로 componentN 메서드를 가지고 있는 것이 아니라 확장 함수로써 가지고 있다.
그리고 그 내용은 get()
을 통해서 가져오고 있기 때문에 디컴파일 해보면 get()
으로 나타나고 있던 것이다.
확장 함수가 정의된 곳은 List 는 _Collections.kt
, Array 는 _Arrays.kt
이다.
Data Class
와 List
, Array
가 다르게 작성된 이유는 아마도 List
와 Array
는 기존에 자바에서 제공되는 것을 사용하기 때문이고, Data class
는 코틀린에서 새롭게 설계되었기 때문에 더 유연한 것이 아닌가 생각한다.
componentN 직접 만들기
List
와 Array
에서 기본으로 만들어지는 componentN
은 1 ~ 5 이지만 더 많은 값을 구조 분해 하고싶은 경우가 있을 것이다.
코틀린 기본 라이브러리에서 1 ~ 5 번을 만든 것처럼 우리도 확장 함수를 사용해서 만들어볼 수 있다.
inline operator fun <T> List<T>.component6() = this[5]
operator fun <T> List<T>.component7() = this[6]
두 가지 방식으로 만들어 보았다. (inline
의 유무)
그리고 구조 분해를 해보자.
에러가 발생하지 않는다. 그럼 구조 분해를 해보자.
구조 분해 된 코드에서 var6(원래 코드에서 n6) 와 n7 의 차이를 확인해보자.
var6
는 앞선 동작과 같이 get()
을 통해서 값을 가져오지만 var7
는 component7()
을 호출하면서 값을 가져온다.
약간 주제에서 벗어난 이야기일 수 있지만 inline
키워드를 붙이면 함수를 호출하는 것이 아니라 함수에 정의된 기능을 코드에 직접 삽입하는 방식으로 이루어진다. 참조가 줄어들기 때문에 시스템 리소스 낭비를 줄일 수 있는 경우도 생긴다.
이번에는 똑같이 만드는데 자연스럽게 붙였던 operator
키워드를 빼고 만들어 보았다.
보이는 것과 같이 컴파일 에러가 뜨면서 operator
를 붙이라는 힌트를 보여준다.
구조 분해를 위해서는 operator
키워드가 필요하기 때문이다.
원하는 만큼 추가해서 사용해보자.
코틀린 코드를 자바 코드로 Decompile 하기
-
코틀린 코드를 작성하고 [Tools] - [Kotlin] - [Show Kotlin Bytecode] 를 선택한다
-
오른쪽 창에 바이트 코드가 나타난다
-
바이트 코드 창에서 상단에 있는 [Decompile] 버튼을 누른다. 그러면 새 창으로 디컴파일 된 자바 코드가 보인다.