Kotlin에서 리스트 추출하기 : subList, slice, take, drop #
리스트의 부분 리스트 구하기 : subList(), slice(), take() #
Kotlin에서는 리스트의 부분 리스트를 구하는 메서드로 여러 메서드를 제공한다. 부분 리스트를 추출하는 기능을 하는 메서드에 대해 알아보자. 원본 리스트를 변경하지 않고 추출한 새로운 리스트를 반환하는 특징이 있다. 이 메서드들은 immutable한 리스트와 mutable한 리스트 모두에서 사용할 수 있다.
subList() #
- 리스트의 인덱스를 기반으로 리스트의 일부분을 추출하여, 새로운 리스트를 생성한다.
- Java의 subList와 유사하게 시작 인덱스부터 끝 인덱스까지 요소를 추출한다.
- 시작 인덱스는 포함, 끝 인덱스는 포함X
val list = listOf(1, 2, 3, 4, 5)
val sub = list.subList(1, 4) // [2, 3, 4]
slice() #
- 리스트의 특정 범위를 추출하여 새로운 리스트를 생성한다.
- subList()와 다르게 IntRange를 받는다.
- IntRange에 해당하는 범위의 리스트를 추출하여 새로운 리스트로 생성한다. -> (1..4)
- 시작 인덱스, 끝 인덱스 모두 포함
val list = listOf(1, 2, 3, 4, 5)
val sliced = list.slice(1..4) // [2, 3, 4, 5]
subList() vs slice() #
- subList() : 추출된 부분 리스트는 원본 리스트의 View에 해당한다. 따라서 원본 리스트의 변경의 영향을 받는다.
- slice() : 추출된 부분 리스트는 원본 리스트와 완전히 독립된 리스트다. 따라서 영향을 받지 않는다.
val mutableList = mutableListOf(1, 2, 3, 4, 5)
val sub = mutableList.subList(1, 4) // [2, 3, 4]
val sliced = mutableList.slice(1..3) // [2, 3, 4]
// 원본 리스트 변경
mutableList[2] = 7
// subList() : 원본 리스트 변경 적용
println(sub) // [2, 7, 4]
// slice() : 원본 리스트 변경 적용 X
println(sliced) // [2, 3, 4]
subList(), slice() 의 동작 방식 #
val mutableList = mutableListOf(1, 2, 3, 4, 5)
val sub = mutableList.subList(1, 4) // [2, 3, 4]
val sliced = mutableList.slice(1..3) // [2, 3, 4]
// mutableList[2] = 7
println(sub) // [2, 7, 4]
println(sliced) // [2, 3, 4]
mutableList, sub, sliced 각각 다른 해시코드를 가지고 있어, 다른 객체임을 확인할 수 있다. mutableList와 sliced는 ArrayList 클래스 타입이고, sub는 ArrayList 클래스의 내부 클래스인 SubList 클래스 타입이다. 각각의 리스트를 구성하는 요소들의 해시코드는 동일하다. 내부적으로 모두 동일한 객체를 가리킨다.
val mutableList = mutableListOf(1, 2, 3, 4, 5)
val sub = mutableList.subList(1, 4) // [2, 3, 4]
val sliced = mutableList.slice(1..3) // [2, 3, 4]
mutableList[2] = 7
println(sub) // [2, 7, 4]
println(sliced) // [2, 3, 4]
mutableList[2] = 7 코드를 수행시켜보자.
- mutablieList, sub : 7을 가리키는 새로운 객체(Integer@831)로 대체된다.
- sliced : 기존의 객체(Integer@837)로 유지된다. subList로 추출한 리스트는 원본 리스트의 참조를 따라가고, slice로 추출한 리스트의 경우 기존 참조를 유지한다.
take(), drop() #
- take() : 리스트의 앞부분부터 지정한 개수만큼의 요소를 추출하여 새로운 리스트를 생성한다.
- drop() : 리스트의 앞부분부터 지정한 개수만큼의 요소를 뺀 새로운 리스트를 생성한다.
- take(), drop() 둘다 원본 리스트와 독립적으로 생성되며, 원본 리스트가 변경되어도 영향을 받지않는다.
val list = listOf(1, 2, 3, 4, 5)
val taken = list.take(3) // [1, 2, 3]
val dropped = list.drop(3) // [4, 5]
subList(slice) vs. take(drop) #
- subList와 slice : 리스트의 범위를 넘어가는 인덱스를 인자로 받을 경우 IndexOutOfBoundsException을 발생
- take와 drop : 리스트의 범위를 넘어가는 인덱스를 인자로 받더라도 별도의 Exception을 발생X
- 리스트의 범위를 넘어가는 인덱스를 인자로 받을 경우
- take : 원본 리스트를 그대로 가져온다.
- drop : 리스트의 모든 요소를 제외하여 부분 리스트를 생성한다.
val list = listOf(1, 2, 3) // 길이가 3이고, maxIndex가 2인 list
val sub = list.subList(0, 4) // IndexOutOfBoundsException
val sliced = list.slice(0..3) // IndexOutOfBoundsException
val taken = list.take(4) // [1, 2, 3]
val dropped = list.drop(4) // []
정리 #
- subList
- 원본 리스트와 부분 리스트 간의 관계를 유지하고자 할 때
- 0번 인덱스부터 자르는 것이 아닌 중간부터 자를 때 (끝까지 자르는 것이 아닌 도중에 자를 때)
- ex: subList(1, 4)
- slice
- 원본 리스트와 관계없는 독립적인 리스트를 생성해야 할 때
- 0번 인덱스부터 자르는 것이 아닌 중간부터 자를 때 (끝까지 자르는 것이 아닌 도중에 자를 때)
- ex: slice(1..4)
- take, drop
- slice를 대체할 수 있으면 사용하는 것이 좋음
- slice(0, 3) 대신 take(3) 사용 → IndexOutOfBoundsException으로부터 안전
- subList(0, 4) 대신 take(3)을 사용할 경우, 원본 리스트 간의 동기화 문제가 발생할 수 있으므로 이 점 참고하여 사용
- IndexOutOfBoundsException을 발생시켜야 하는 상황에서는 다른 메서드 사용