Kotlin

[Kotlin] 객체 타입의 배열 & 원시 타입의 배열

sh1mj1 2024. 1. 22. 16:39

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` 를 사용하자.