[Kotlin] 객체 타입의 배열 & 원시 타입의 배열
Kotlin in Action 을 공부하고 Effective kotlin 의 내용을 조금 참조하여 정리한 글입니다.
코틀린 배열 사용하기
자바 `main` 함수의 표준 시그니처에는 배열 파라미터가 들어있다.
코틀린의 `main` 함수에도 배열 파라미터가 들어갈 수 있다. (kotlin 1.3 이후에는 문자열 배열이 들어가지 않아도 된다.)
코틀린 `main` 함수에서 배열 사용
fun main(args: Array<String>) {
for (i in args.indices) { // 배열의 인덱스 값의 범위에 대해 iteration (array.indices 확장 함수 사용)
println("Argument $i is: ${args[i]}") // array[index] 로 인덱스를 사용해 배열 원소에 접근
}
}
코틀린 배열은 타입 파라미터를 받는 클래스이다.
public class Array<T> {
public inline constructor(size: Int, init: (Int) -> T)
public operator fun get(index: Int): T
public operator fun set(index: Int, value: T): Unit
public val size: Int
public operator fun iterator(): Iterator<T>
}
코틀린에서는 배열을 인자로 받는 자바 함수를 호출하거나,
`vararg` 파라미터를 받는 코틀린 함수를 호출할 때 가장 자주 배열을 사용한다.
코틀린에서 배열을 만드는 방법
- `arrayOf` 함수에 원소를 넘기면 배열을 만든다.
- `arrayOfNulls` 함수에 정수값을 인자로 넘기면, 모든 원소가 null 이고 인자값과 같은 크기의 배열을 만든다.
- 물론 원소 타입이 nullable 인 타입일 때만 가능
- `Array` 생성자는 배열 크기와 람다를 인자로 받아서 람다를 호출해서 각 배열의 원소를 초기화한다.
알파벳으로 이뤄진 배열 만들기
val letters = Array<String>(26) { i -> ('a' + i).toString() }
println(letters.joinToString(""))
// print /* abcdefghijklmnopqrstuvwxyz */
타입을 분명히 보이기 위해 `Array<String>(26)` 처럼 타입 인자를 명시했지만,
생략해도 컴파일러가 알아서 원소 타입을 추론한다.
컬렉션을 배열로 변환
`toTypedArray` 메서드를 사용하면 쉽게 컬렉션을 배열로 바꿀 수 있다.
컬렉션을 `vararg` 메서드에게 넘기기
val strings = listOf("a", "b", "c")
println("%s/ %s/ %s".format(*strings.toTypedArray()))
// print /* a/ b/ c */
` vararg` 인자를 넘기기 위해 스프레드 연산자(`*`) 를 써야 한다.
코틀린에서는 다른 Generic 타입에서처럼 배열 타입의 타입 인자도 항상 객체 타입이 된다.
즉, `Array<int>` 는 박싱된 정수의 배열(`java.lang.Integer[]`) 이다.
만약 박싱하지 않은 원시 타입의 배열이 필요하다면, 그런 타입을 위한 특별한 배열 클래스를 써야 한다.
원시 타입 배열
코틀린은 원시 타입의 배열을 표현하는 별도 클래스를 각 원시 타입마다 하나씩 제공한다.
`IntArray`, `ByteArray`, `CharArray`, `BooleanArray` 등의 모습으로 사용하면 된다.
이는 자바의 `int[]`, `byte[]`, `char[]` 등으로 컴파일된다.
원시타입의 배열을 만드는 방법
- 각 배열 타입의 생성자는 `size` 인자를 받아서, 해당 원시 타입의 디폴트 값(보통은 0)으로 초기화된 `size` 크기의 배열을 리턴
val fiveZeros = IntArray(5)
- 팩토리 함수(`IntArray` 를 생성하는 `intArrayOf` 등)는 여러 값을 가변 인자로 받아서 그런 값이 들어간 배열을 리턴
val fiveZeros = intArrayOf(0, 0, 0, 0, 0)
- 크기와 람다를 인자로 받는 생성자를 사용하기(일반 배열과 마찬가지)
val squares = IntArray(5) { i -> (i+1) * (i+1) }
// 1, 4, 9, 16, 25
- 이 밖에 박싱된 값이 들어있는 컬렉션이나 배열에 `toIntArray` 등의 변환 함수를 사용해 박싱하지 않은 값이 들어있는 배열로 변환할 수 있다.
배열로 할 수 있는 일
kotlin stlib 는 배열 기본연산(배열 길이 구하기, 원소 읽기/쓰기) 뿐 아니라,
컬렉션에 사용할 수 있는 모든 확장 함수를 배열에도 제공한다.(`filter`, `map` 등의 함수들)
다만 이런 함수가 리턴하는 값은 배열이 아니라 리스트이다.
배열에 `forEachIndexed` 사용하기
fun main(args: Array<String>) {
args.forEachIndexed { index, s ->
println("Argument $index is: $s")
}
}
성능이 중요한 부분에는 기본 자료형 배열을 사용하라
기본 자료형은 아래와 같은 특징이 있다.
- 가볍다.
- 일반적인 객체와 다르게 추가적으로 포함되는 것들이 없다.
- 빠르다.
- 값에 접근할 때 추가 비용이 들어가지 않는다.
코틀린과 자바 타입 정리
코틀린 타입 | 자바 타입 |
Int | int |
List<Int> | List<Integer> |
Array<Int> | Integer[] |
IntArray | int[] |
기본 자료형 배열의 가벼움
코틀린/JVM 에서 1,000,000 개의 정수를 갖는 컬렉션을 만든다고 하자.
IntArray 는 400,000,016 바이트, List<Int> 는 2,000,006,994 바이트를 할당한다.
즉, 단순 할당 영역만 생각해도 5배 정도의 차이가 난다.
기본 자료형 배열의 성능
10,000,000 개의 숫자를 갖는 컬렉션을 사용하여 평균을 구하는 처리를 해보면, 배열을 사용하는 경우가 33% 정도 더 빠르다.
class InlineFilterBenchmarkTest {
private lateinit var list: List<Int>
private lateinit var array: IntArray
@BeforeEach
fun setUp() {
list = List(10_000_000) { it }
array = IntArray(10_000_000) { it }
}
@Test
fun averageOnIntList() = println(measureTimedValue { list.average() }) // 34.792625ms
@Test
fun averageOnIntArray() = println(measureTimedValue { array.average() }) // 23.915ms
}
이렇게 기본 자료형 배열은 코드 성능이 중요한 부분을 최적화할 때 활용하면 좋다.
배열은 더 적은 메모리를 차지하고, 더 빠르게 동작한다.
다만 일반적인 경우에는 `List` 를 사용하는 것이 좋다.
`List` 가 훨씬 더 기능이 다양하며, 더 많은 곳에서 쉽게 사용될 수 있다.
성능이 중요한 경우에는 `Array` 를 사용하자.