아홉 번째, 배열입니다.
배열은 같은 타입(해당 타입의 하위 타입도 가능)의 값들을 고정 수로 가지고 있는 자료 구조입니다. Kotlin에서 가장 일반적인 배열 형태는 Array 클래스로 나타내는 객체 타입 배열입니다.
객체 타입 배열에서 원시타입을 사용하는 경우 원시 타입은 상응하는 래퍼 클래스로 변경(box)되기 때문에 성능이 저하되는 문제가 있습니다. 이러한 오버헤드를 피하기 위해서는, (아래에 나오는) 원시 타입 배열을 사용합니다.
배열을 사용하는 경우 (When to use arrays)
코틀린에서 배열은 특별한 하위 수준(low level)의 요구 사항을 만났을 때 사용합니다. 예를 들어, 일반적인 애플리케이션에게 요구되는 것 이상의 성능이 요구된다거나, 맞춤화된 자료 구조를 만들어야 할 때 배열을 사용합니다. 이러한 종류의 제약들이 없다면, 배열 대신 컬렉션을 사용합니다.
컬렉션은 배열에 비해서 다음과 같은 장점을 가지고 있습니다.
- 컬렉션은 읽기 전용일 수 있어서 보다 많은 제어권을 제공하고, 명확한 의도를 가지는 견고한 코드를 작성할 수 있습니다. 즉, 연속된 값의 자료 구조에 불변성을 지정함으로써 불변성으로부터 얻을 수 있는 이점들을 얻을 수 있습니다.
- 컬렉션에는 요소의 추가, 삭제가 쉽습니다. 그에 비해 비열은 크기(size)가 고정돼 있습니다. 배열에서 요소를 추가하거나 삭제하는 유일한 방법은 매번 새로운 배열을 만드는 것입니다. 매우 비효율적입니다.
fun main() {
var riversArray = arrayOf("Nile", "Amazon", "Yangtze")
// += 연산은 원래 요소들을 복사하고 "Mississippi"를 추가한
// 새로운 riversArray를 만듭니다.
riversArray += "Mississippi"
println(riversArray.joinToString())
// Nile, Amazon, Yangtze, Mississippi
}
- 컬렉션에는 구조적으로 같은지 검사할 수 있는 동등 연산자(==)를 사용할 수 있습니다. 배열에는 사용할 수 없습니다. 배열에 대해 동등 연산을 하려면 특별한 함수를 사용해야 합니다. 이에 대한 내용은 아래에 있는 '배열 비교하기'에서 살펴볼 수 있습니다.
컬렉션에 대한 보다 자세한 내용은 Collections overview에서 살펴볼 수 있습니다.
배열 생성
코틀린에서 배열은 다음과 같이 만들 수 있습니다.
- arrayOf(), arrayOfNulls(), emptyArray() 같은 함수를 사용하여 생성
- Array 생성자로 생성
다음은 배열 각 요소의 값을 넘기며 arrayOf() 함수를 호출하는 예입니다.
fun main() {
// [1, 2, 3] 값을 갖는 배열 생성
val simpleArray = arrayOf(1, 2, 3)
println(simpleArray.joinToString())
// 1, 2, 3
}
다음은 arrayOfNulls() 함수를 사용하여 특정 사이즈의 모든 요소의 값이 null인 배열을 만드는 예입니다.
fun main() {
// [null, null, null] 인 배열 생성
val nullArray: Array<Int?> = arrayOfNulls(3)
println(nullArray.joinToString())
// null, null, null
}
다음은 emptyArray() 함수를 사용하여 빈 배열을 만드는 예입니다.
var exampleArray = emptyArray<String>()
코틀린의 타입 추론 능력 덕분에, 빈 배열을 만들 때 타입은 왼쪽이나 오른쪽 부분 어디에도 지정할 수 있습니다.
var exampleArray = emptyArray<String>()
var exampleArray: Array<String> = emptyArray()
Array 생성자는 배열 크기와 배열 색인에 따라 해당 요소의 값을 반환하는 함수를 인수로 받습니다.
fun main() {
// 0 으로 초기화 되는 Array<Int> 배열 생성. 0, 0, 0]
val initArray = Array<Int>(3) { 0 }
println(initArray.joinToString())
// 0, 0, 0
// ["0", "1", "4", "9", "16"] 값을 갖는 Array<String> 생성
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { print(it) }
// 014916
}
대부분의 언어들처럼, 코틀린에서 배열의 색인은 0부터 시작합니다.
중첩 배열
다차원 배열을 만들기 위해서 배열은 중첩될 수 있습니다.
fun main() {
// 2차원 배열 생성
val twoDArray = Array(2) { Array<Int>(2) { 0 } }
println(twoDArray.contentDeepToString())
// [[0, 0], [0, 0]]
// 3차원 배열 생성
val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
println(threeDArray.contentDeepToString())
// [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]
}
중첩된 배열은 꼭 같은 크기나 타입일 필요는 없습니다.
요소 접근 및 변경
배열은 항상 가변성입니다. 배열의 요소를 접근하기 위해서 색인 접근 연산자인 [ ] 를 사용합니다.
fun main() {
val simpleArray = arrayOf(1, 2, 3)
val twoDArray = Array(2) { Array<Int>(2) { 0 } }
// 요소에 접근 및 변경
simpleArray[0] = 10
twoDArray[0][0] = 2
// 변경된 요소 출력
println(simpleArray[0].toString()) // 10
println(twoDArray[0][0].toString()) // 2
}
Kotlin에서 배열에는 무변성(invariant)입니다. 즉, 실행 시간에 발생할 수 있는 실패(failure)를 방지하기 위해 Array<Any>에 Array<String>을 할당하는 것을 허용하지 않습니다. 이를 가능하게 하려면, (공변성covariant을 갖도록) Array<Any> 대신에 Array<out Any>를 사용해야 합니다. 더 자세한 정보는 Type Projections에서 살펴볼 수 있습니다.
※ 변성에 대해 익숙하지 않은 경우, 당장은 '이렇구나' 정도로만 이해하시면 됩니다. 추후, 제네릭부분에서 변성과 관련해서 상세히 살펴보게 됩니다.
배열 활용 (Work with arrays)
Kotlin에서 배열은 가변 인수(정해지지 않은 수의 인수)를 함수에 넘기는 데 사용하거나 배열 자체의 연산을 실행하는데 사용할 수 있습니다. 배열 자체의 연산에는 배열 비교, 내용 변경, 컬렉션으로의 변환 등이 있습니다.
함수에 가변 인수 전달
Kotlin에서는 vararg 매개변수를 통해서 정해지지 않은 수의 인수(argument)를 함수에 전달할 수 있습니다. 이는 메시지를 서식화 할 때나 SQL 쿼리를 만들 때 같이 미리 인수의 수를 알 수 없을 때 유용합니다.
가변 인수를 담고 있는 배열을 함수에 전달할 때, 확산(spread) 연산자( * )를 사용합니다. 확산 연산자는 배열의 각각의 요소를 개별적인 인수로 함수에 전달합니다.
fun main() {
val lettersArray = arrayOf("c", "d")
printAllStrings("a", "b", *lettersArray)
// abcd
}
fun printAllStrings(vararg strings: String) {
for (string in strings) {
print(string)
}
}
보다 자세한 내용은 여기를 참고하시면 됩니다.
배열 비교
두 개의 배열이 같은 요소들을 같은 순서로 가지고 있는지 비교하기 위해서는 .contentEquals()와 .contentDeepEquals() 함수를 사용합니다.
fun main() {
val simpleArray = arrayOf(1, 2, 3)
val anotherArray = arrayOf(1, 2, 3)
// 배열 비교
println(simpleArray.contentEquals(anotherArray))
// true
// 중위 연산 표기법을 사용
simpleArray[0] = 10
println(simpleArray contentEquals anotherArray)
// false
}
배열 비교를 위해 == 나 !=를 사용하지 않습니다. 이 연산자들은 변수들이 가리키고 있는 객체가 동일한 것인지 검사합니다.
JVM하에서 오랫동안 문제 중 하나로 배열과 컬렉션에서 equals()가 다르게 동작하여 컬렉션에서는 구조적인 동등성을 확인하지만 배열에 대해서는 동일성을 확인한다는 것입니다. 관련하여 수정하기 위해서는 Java부터 수정이 되야 하는데 쉽지 않은 문제이며, 현재 상황에서 이와 관련된 부작용을 최소화하기 위해 현재 처럼 결정했다고 합니다. 보다 자세한 내용은 이 블로그 글을 통해 확인하실 수 있습니다.
배열 변경
코틀린은 배열은 변경하는 다수의 유용한 함수를 가지고 있습니다. 여기서는 그 중 (아주 소수인) 두 가지만 살펴 봅니다. 전체 목록은 API reference에서 확인할 수 있습니다.
합
배열 모든 요소의 합을 구하기 위해서는 .sum() 함수를 사용합니다.
fun main() {
val sumArray = arrayOf(1, 2, 3)
// 배열 모든 요소의 합
println(sumArray.sum())
// 6
}
.sum() 함수는 배열의 모든 요소가 Int 같은 숫자형 타입일 때만 사용할 수 있습니다.
섞기
배열 요소들을 임의로 섞을 때는 .shuffle() 함수를 사용합니다.
fun main() {
val simpleArray = arrayOf(1, 2, 3)
simpleArray.shuffle()
println(simpleArray.joinToString())
simpleArray.shuffle()
println(simpleArray.joinToString())
}
컬렉션으로 변환
배열을 사용하는 API와 컬렉션을 사용하는 API를 같이 작업하는 경우, 배열을 컬렉션으로, 컬렉션을 배열로 변환해서 사용할 수 있습니다.
리스트나 셋으로 변환
배열을 List나 Set 타입으로 변환할 때는 .toList()와 .toSet() 함수를 사용합니다.
fun main() {
val simpleArray = arrayOf("a", "b", "c", "c")
// Set으로 변환
println(simpleArray.toSet())
// [a, b, c]
// List로 변환
println(simpleArray.toList())
// [a, b, c, c]
}
맵으로 변환
배열을 Map 타입으로 변환할 때는 .toMap() 함수를 사용합니다.
Pair<K, V> 타입의 배열만이 Map으로 변환될 수 있습니다. Pair 인스턴스의 첫번째 값은 키가 되고 두 번째는 값이 됩니다. 다음 예에서는 Pair 인스턴스(튜플)를 만들기 위해 to 함수를 중위 표기법으로 사용합니다.
fun main() {
val pairArray = arrayOf("apple" to 120, "banana" to 150, "cherry" to 90, "apple" to 140)
// Map으로 변환
// 키는 과일명이고 값은 해당 과일의 칼로리
// key는 유일해야하기 때문에 마지막의 apple이 처음의 apple을 덮어씁니다.
println(pairArray.toMap())
// {apple=140, banana=150, cherry=90}
}
원시 타입 배열
Array 클래스를 원시(primitive) 타입 값들과 사용하는 경우, 이 값들은 해당 타입에 대응하는 래퍼 클래스로 박스(box)화 됩니다. 이에 대한 대안으로 원시 타입 배열을 사용할 수 있습니다. 그렇게 하면 박스화 오버 헤드라는 부작용 없이 배열에 원시 타입 값을 저장할 수 있습니다.
원시 타입 배열 | 자바에서 동일 형태 |
BooleanArray | boolean[] |
ByteArray | byte[] |
CharArray | char[] |
DoubleArray | double[] |
FloatArray | float[] |
IntArray | int[] |
LongArray | long[] |
ShortArray | short[] |
이 클래스들은 Array와 아무런 상속 관련성이 없지만, Array와 같은 함수와 프로퍼티들을 가지고 있습니다.
다음은 IntArray 클래스의 인스턴스를 만드는 예입니다.
fun main() {
val exampleArray = IntArray(5)
println(exampleArray.joinToString())
// 0, 0, 0, 0, 0
}
원시 타입 배열을 객체 타입 배열로 변경하기 위해서는 .toTypedArray() 함수를 사용합니다.
객체 타입 배열을 원시 타입 배열로 변경할 때는 .toBooleanArray(), toByteArray(), toCharArray() 등 각 타입에 맞는 함수를 사용합니다.
'Kotlin' 카테고리의 다른 글
공식 문서로 배우는 코틀린 - 11. Control flow (0) | 2024.03.06 |
---|---|
공식 문서로 배우는 코틀린 - 10. Types - Type checks and casts (0) | 2024.03.05 |
공식 문서로 배우는 코틀린 - 8. Basic Types - Strings (0) | 2024.03.02 |
공식 문서로 배우는 Kotlin - 7. Basic Types - Characters (0) | 2024.03.02 |
공식 문서로 배우는 Kotlin - 6. Basic Types - Booleans (0) | 2024.03.02 |