데이타 클래스에서의 구조 분해

코틀린 구조분해선언에 대하여
Jun 28, 2023
데이타 클래스에서의 구조 분해

데이타 클래스에서의 구조 분해

data 클래스는 일반 클래스와 몇가지 차이점을 가지고 있습니다.
그중에 하나가 아래와 같이 생성자로 지정한 맴버를 반환 받을 수 있다는 것 입니다.
data class Person( val age : Int , val name : String) fun main() { val (age , name) = Person(10,"names") println("$name $age") }
 
이러한 용법을 destructucturing-구조 분해, 혹은 분해 등으로 부르는 것 으로 보입니다.

구조분해선언

이 구조분해는 데이타 클레스 에서 만 사용 되는 것은 아니고 destructucturing declarations-구조 분해 선언, 이라고 적힌 공식문서 항목, 을 통해서 구조분해에 반환 될 값들을 정의 할 수 있습니다.
class Person( val age : Int , val name : String){ operator fun component1() = age operator fun component2() = name } fun main() { val (age , name) = Person(10,"names") println("$name / $age") }
 
반환 값의 첫번째가 component0 이 아니라 component1 이라는 점을 유의 하셔야합니다. 단 0으로 선언을 해도 선언상의 에러는 발생 하지 않습니다만 호출에서 무시 됩니다.
그렇다면 구조분해선언의 componentN 은 몇개 까지 선언이 가능 할 까?
링크에서와 같이. 299개 까지 가능한 것은 확인 했지만 그 이상은 확인 해보지 못했습니다.
혹시 더 해보신 분 이나 관련 스팩을 확인 하신 분 있으시면 제보 부탁 드립니다.
 
그렇다면 componentN 함수 선언 상의 중간에 빈값이 있을 땐 어떻게 될까요?
class Lost(){ operator fun component1() = 1 operator fun component2() = 2 operator fun component3() = 3 operator fun component4() = 4 operator fun component6() = 6 operator fun component7() = 7 operator fun component8() = 8 operator fun component9() = 9 operator fun component10() = 10 operator fun component1000() = 11 } fun main() { val (a,b,c,d,e,f,g,h) = Lost() val outher = Lost().component1000() }
실제 코드를 돌려보면 e 부분에서 component5가 없다고 에러가 나게 됩니다. 결국 중간에 함수가 빠지만 이후 부터는 구조 분해로 사용 할 수 없습니다. ( 이후에 설명할 _ 로 생략 해도 에러가 사라지지 않습니다. )
다만 component1000() 의 호출에서 볼 수 있듯이 일반 함수처럼 직접 호출 해서 쓰는데는 문제가 없습니다.

데이타 클래스의 구조분해와 구조분해선언

하지만 데이타 클래스에서는 이러한 구조분해선언에 의해서 정의 되는 구조분해와 좀 다르게 동작하는데.

구조분해의 사용

함수 반환값

자바와 코틀린에서는 함수의 반환 값으로 여러개의 값을 반환 할 수 없습니다. 다만 아래와 같이 구조분해 선언이 정의 된 클래스 (해당 예시에서는 Pair) 을 반환 하고 반환을 받는 곳에서 구조분해를 사용하여 여러개의 값을 반환 하는 것 처럼 사용 할 수 는 있습니다.
Pair 는 코틀린의 tuples 파일에서 데이트 클래스로 선언 된 클래스입니다. 확장 함수로 public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) 를 가지고 있어 간단하게 A to B 로 선언 할 수 있습니다.
fun subString(str : String,count : Int): Pair<String, String> { return str.substring(0,count) to str.substring(count) } fun main() { val string = "string and string" val (pre,post) = subString(string,6) println(pre) println(post) }

For in 문에서 Map 을 사용 할때의 구조 분해

리스트를 순환 하여 처리 하기 위하여 For 문을 사용 할 때 for in 으로 사용 하는 경우가 많습니다. 다만 리스트가 아니라 Map 을 사용하는 경우 변수에 지정 되는 값은 List 때와 다르게 Map.Entry의 형태라서 별도로 key 와 Value 를 가져와야 합니다. (간혹 List 에서 인덱스가 필요하여 withIndex()를 사용하는 경우에도 유사하게 IndexedValue 로 순환 하게 됩니다.
이경우에도 아래와 같이 구조분해를 사용하면 인덱스와 값만을 선언 하여 사용 할 수 있습니다.
for ((key, value) in mapOf(1 to "2")) { println("$key - $value") }
 

언더바./언더스코어 (_)

구조분해에서 수신 값들에 대하여 실제 사용하지 않는 변수에 대하여는 변수 명을 지정 하지 않고 _를 사용하여 생략 할 수 있습니다.
이래와 같이 맵에서 키를 사용하지 않을때 키 자리에 _ 를 넣어서 변수명 지정을 생략 할 수 있습니다.
다만 변수명으로 _ 를 지정 하는 것이 아니기 때문에, 여러 변수를 _ 로 생략 가능하며, _를 이후에 변수처럼 사용할 수도 없습니다.
for ((_, value) in mapOf(1 to "2")) { println("$value") }

람다 함수에서의 구조분해

코틀린에서 람다 함수를 쓸 때에도 아래와 같이 구조분해를 적용 할 수 있습니다.
mapOf( "key" to 1, "key2" to 2 ).mapValues {entry: Map.Entry<String, Int> -> } mapOf( "key" to 1, "key2" to 2 ).mapValues {(key: String, value: Int) -> }
아래와 같이 구조분해와 일반 변수도 같이 사용 할 수있습니다.
val lambda1 : (Pair<Int,Int>,Int)->Unit = { p: Pair<Int, Int>, i: Int -> } val lambda2 : (Pair<Int,Int>,Int)->Unit = { (first: Int, scond: Int): Pair<Int, Int>, i: Int -> }
인자를 표시 할때 자료형의 명시가 필수는 아니며, 이해의 편의를 위해서 명시적으로 자료명을 삽입하였습니다.

리스트 나 어레이 에서의 구조분해.

코틀린의 리스트나 Array<T>, XXXArray 등에서도 구조 분해를 사용 할 수 있습니다.
다만 componentN 함수가 전체 구현 되어 있지는 않고 1~5 까지만 선언 되어 있어 여섯번째 이후의 인덱스를 구조분해로 사용하려면 직접 선언해서 써야합니다.
또한 해당 구조분해 선언에서 참조를 XXX.get(index) 로 하고 있기 때문에 해당 인덱스에 값이 없으면 IndexOutOfBoundsException 이 발생하게 됩니다. 만약 아웃오브바운스익셉션이 나지 않도록 하고 싶을 때 처리할 방법은 이후에 다시 설명 하도록 하겠습니다.

구조분해 선언을 지원하지 않는 클래스

외부 라이브러리에서 구조 분해 선언이 되어 있지 않은 상태로 작성 되었거나 자바로 작성 되어 있어서 기존에 구조분해 선언이 되어 있지 않은 클래스는 수정이 불가능하기 때문에 구조 분해를 위함 함수를 선언 할 수 없습니다.
하지만 이 경우 아래와 같이 구조분해 선언을 확장 함수로 선언 하면 정상적으로 구조분해가 동작 하는 것을 볼 수 있습니다.
 

이미 구조분해선언이 있는 클래스에 대한 구조분해선언의 재 선언

 
앞에서 이야기한 바와 같이 Collection 은 자체적으로 Component1~5 까지의 함수가 선언 되어 있 어 이러한 Collection을 상속 받은 List 를 구조 분해로 사용 할 수 있습니다.
다만 Collection의 구조 분해 선언은 널을 리턴하지 못하기 때문에. 인자가 3개인 리스트에서 구조분해 선언으로 네개의 인자를 가져오면. 익셉션-ArrayIndexOutOfBounds 가 발생 합니다.
 
 
그렇다면 널불가능한 값을 반환하는 구조 분해 선언을 이미 가지고 있는 리스트에다가 확장 함수로 구조분해 선언을 추가로 작성 하면 어떻게 될까?
 
 
이렇게 널가능한 인자를 반환하는 구조분해선언을 확장 함수로 추가하면 추가된 구조분해 선언이 동작 하는 것을 볼 수 있습니다.
 

내부 함수로 구조 분해 선언을 가지고 있는 클래스를 대상으로 구조분해선언 확장 함수 추가

다만 List의 경우는 구조분해선언이 확장함수로 정의 되어 있어서 확장 함수로 덮어 씌우는 것이 가능 했습니다만. 내부 함수로 구조분해 선언이 되어 있는 클래스에 대하여 확장 함수로 구조분해 선언을 정의 하면 확장함수 말고 내부함수가 동작 하게 됩니다.
 
참고자료
 
 
💡
수정테스트입니다.
 
Share article
RSSPowered by inblog