devseop08 님의 블로그

[OOP] 3. 기본 타입, 컬렉션, 배열 본문

Language/Kotlin

[OOP] 3. 기본 타입, 컬렉션, 배열

devseop08 2025. 6. 9. 11:17

8.1 원시 타입과 기본 타입

  • Int, Boolean, Any 등의 기본 타입을 살펴보자.
  • 자바와 달리 코틀린은 원시 타입과 래퍼 타입을 구분하지 않는다.
  • 원시 타입과 래퍼 타입을 구분하지 않는 이유와 코틀린 내부에서 어떻게 기본 타입에 대한 래핑이 이루어지는지 살펴보자.
  • Object, Void 등의 자바 타입과 코틀린 타입 간의 대응 관계를 살펴보자

8.1.1 정수, 부동소수점 수, 문자, 불리언 값을 원시 타입으로 표현

  • 코틀린은 원시 타입과 래퍼 타입을 구분하지 않는다. 항상 같은 타입을 사용한다.
  • 컬렉션에 담는 래퍼 타입이 따로 있지 않다는 것이다.
val i: Int = 1
val list: List<Int> = listOf(1, 2, 3)
  • 래퍼 타입을 따로 구분하지 않으면 편리한데, 숫자 타입 등 원시 타입의 값에 대해 메서드를 호출할 수 있다
fun showProgress(progress: Int){
     val percent = progress.coreIn(0, 100)
     println("We're $percent % done!")
}

fun main(){
    showProgress(146)
    // We're 100 % done!
}
  • 코틀린이 원시 타입을 항상 객체로 표현하는 것은 아니다.
  • 실행 시점에 숫자 타입은 가능한 한 가장 효율적인 방식으로 표현된다.
  • 코틀린의 Int 타입은 자바 int 타입과 동일하게 컴파일되는데 이런 컴파일이 불가능한 경우는 컬렉션과 같은 제네릭 클래스를 사용하는 경우뿐이다.
  • Int 타입을 컬렉션의 타입 파라미터로 넘기면 그 컬렉션에는 Int의 래퍼 타입에 해당하는 java.lang.Integer 객체가 들어간다.(래퍼 객체)
  • 자바 원시 타입에 해당하는 코틀린 타입의 전체 목록
    • 정수 타입 : Byte, Short, Int, Long
    • 부동소수점 숫자 타입: Float, Double
    • 문자 타입: Char
    • 불리언 타입: Boolean

8.1.2 양수를 표현하기 위해 모든 비트 범위 사용: 부호 없는 숫자 타입

  • 코틀린의 부호 없는 숫자 타입
타입 크기 값 범위
UByte 8비트 0 ~ 255
UShort 16비트 0 ~ 65535
UInt 32비트 0 ~ 2^32 - 1
ULong 64비트 0 ~ 2^64 -1
* 부호없는 숫자 타입들은 상응하는 부호 있는 타입의 범위를 시프트한다    
* 이를 이용해 같은 크기의 메모리를 사용하면서 더 큰 양수 범위를 표현할 수 있게 해준다.    
* 다른 원시 타입과 마찬가지로 코틀린의 부호 없는 수도 필요할 때만 래핑된다.    
  • 부호 없는 숫자 타입의 값을 사용하는 진짜 목적은 비트와 바이트 수준에서 데이터를 다루고 싶을 때이다. 그런 경우가 아니라면 보통의 원시 타입을 이용해 음수 범위가 함수에 전달됐는지 검사하는 편이 더 낫다.
  • 원래 JVM 자체는 부호가 없는 수에 대한 원시 타입을 지정하지 않거나 제공하지 않는다.
  • 코틀린은 이를 바꿀 수 없고 다만 기존 부호가 있는 원시 타입 위에 추상화를 제공한다.
  • 코틀린은 인라인 클래스를 통해 이런 추상화를 제공하는데 부호없는 값을 표현하는 각 클래스는 사실 실제로는 인라인 클래스이며, 각 인라인 클래스는 자신에 상응하는 부호 있는 타입을 사용한다.
  • 내부에서 UInt 타입의 값은 실제로는 Int인 것이다.
  • 코틀린 컴파일러는 쉽게 Int같은 타입을 그에 상응하는 JVM 원시 타입으로 쉽게 변환할 수 있다.
  • 반대로 자바 원시 타입의 값은 결코 null이 될 수 없으므로 자바 원시 타입을 코틀린에서 사용할 때도 널이 될 수 없는 타입으로 취급할 수 있다.

8.1.3 널이 될 수 있는 기본 타입: Int?, Boolean? 등

  • 자바에서 null 참조는 자바의 참조 타입의 변수에만 대입될 수 있기 때문에 코틀린에서 널이 될 수 있는 코틀린 타입은 자바의 원시 타입으로 표현할 수 없다.
  • 따라서 코틀린에서 널이 될 수 있는 원시 타입을 사용하면 그 타입은 자바의 래퍼 타입으로 컴파일된다.
  • 코틀린에서 널이 될 수 있는 원시 타입은 자바의 래퍼 타입으로 컴파일 되기 때문에 널이 될 수 있는 원시 타입(Int?와 같은 형식의 타입)은 null인지 검사 후에야 일반 원시 타입처럼 사용이 가능하다.
data class Person(val name: String, val age: Int? = null){
    fun isOlderThan(other: Person): Boolean? {
        if(age == null || other.age == null) // age > other.age 바로 비교 불가 
            return null
        return age > other.age // null 검사 후에야 원시 타입처럼 비교 연산 가능
    }
}

fun main(){
    println(Person("Sam",35).isOlderThan(Person("Amy", 42)))
    // false
    Println(Person("Sam", 35).isOlderThan(Person("Jane")))
}
  • 제네릭 클래스의 경우 래퍼 타입을 사용한다.
  • 어떤 클래스의 타입 인자로 원시 타입을 넘기면 코틀린은 그 타입에 대한 박스 타입(래퍼 타입)을 사용한다.
  • 이렇게 컴파일하는 이유는 JVM에서 제네릭을 구현하는 방법 때문이다.
  • JVM은 타입 인자로 원시 타입을 허용하지 않는다. 그렇기 때문에 자바나 코틀린 모두에서 제네릭 클래스는 항상 박스 타입을 사용해야 한다.

8.1.4 수 변환

타입이 정해진 변수의 타입 변환: 변환 함수 호출 필요함.
  • 코틀린과 자바의 가장 큰 차이점 중 하나는 수를 변환하는 방식이다.
  • 코틀린은 자바와 달리 한 타입의 수를 다른 타입의 수로 자동 변환하지 않는다.
  • 결과 타입이 허용하는 수의 범위가 원래 타입의 범위보다 넓다 할지라도 자동 변환은 원칙적으로 불가능하다. => 직접 변환 메서드를 호출해야 한다.
val i = 1
val l: Long = i // ERROR: type mismatch 컴파일 오류 발생

val i = 1
val l: Long = i.toLong()
  • 코틀린은 모든 원시 타입(Boolean은 제외)에 대해 toByte(), toShort(), toChar() 등등의 변환 함수를 제공한다.
변수가 아닌 리터럴의 타입 변환: 변환 함수 호출 필요하지 않음
  • 숫자 리터럴을 사용할 때는 보통 변환 함수를 호출할 필요가 없다.
  • 42나 42.0f 처럼 상수 뒤에 타입을 표현하는 문자를 붙이면 변환이 필요 없다.
  • 직접 변환하지 않더라도 숫자 리터럴을 타입이 알려진 변수에 대입하거나 함수에게 인자로 넘기면 컴파일러가 필요한 변환을 자동으로 넣어준다
  • 산술 연산자는 적당한 타입의 값을 받아들일 수 있도록 이미 오버로드돼 있다.
fun printAlong(l: Long) = println(l)

fun main(){
    val b: Byte = 1 // 상수 값은 적절한 타입으로 해석
    val l = b + lL  // +는 Byte와 Long을 인자로 받을 수 있다.(오버로드, 변환 필요 X)
    printAlong(42)  // 컴파일러는 숫자 리터럴 42를 Long으로 해석석
}

8.1.5 Any와 Any? : 코틀린 타입 계층의 뿌리

  • 자바에서 Object가 클래스 계층의 최상위 타입이듯 코틀린에서는 Any 타입이 모든 널이 될 수 없는 타입의 조상 타입이다.
  • 자바에서는 참조 타입만 Object를 정점으로 하는 타입 계층에 포함, 원시 타입은 그런 계층에 포함되지 않는다.
  • 코틀린에서는 Any가 Int 등의 원시 타입을 포함한 모든 타입의 조상 타입이다.
  • 코틀린에서 널을 포함하는 모든 값을 대입할 변수를 선언하려면 Any? 타입을 사용해야 한다.
  • 자바 메서드에서 Object를 인자로 받거나 반환하면 코틀린에서는 Any로 그 타입을 취급한다.
  • 코틀린 함수가 Any를 사용하면 자바 바이트코드의 Object로 컴파일된다.
  • java.lang.Object에서 toString, equals, hashCode 이외의 다른 메서드는 Any에서 사용할 수 없으므로 그런 메서드를 호출하고 싶다면 java.lang.Object 타입으로 값을 캐스트 해야 한다.

8.1.6 Unit 타입: 코틀린의 void

  • 코틀린 Unit 타입은 자바 void와 같은 기능을 한다.
  • 관심을 가질 만한 내용을 전혀 반환하지 않는 함수의 반환 타입으로 Unit을 쓸 수 있다.
fun f(): Unit { /* ... */}
fun f() { /* ... */ } // 반환 타입 명시 X => 반환 타입이 Unit이다.
  • 코틀린 함수의 반환 타입이 Unit이고 그 함수가 제네릭 함수를 오버라이드 하는 경우가 아니라면, 반환 타입이 Unit인 코틀린 함수는 내부에서 자바 void 함수로 컴파일된다.
  • 그런 코틀린 함수를 자바에서 오버라이드 하는 경우엔 void를 반환 타입으로 해야 한다.
  • 코틀린의 Unit이 자바 void와 다른 점
  • Unit은 모든 기능을 갖는 일반적인 타입, Unit 타입에 속한 값은 단 하나, 그 이름도 Unit이다.
  • Unit은 void와 달리 타입 파라미터로 사용 가능
  • Unit이 반환 타입인 함수는 암시적으로 Unit 값을 반환
  • Unit이 자바 void와 같은 것처럼 보일 수 있지만, 본질적인 '값 없음' 상태를 나타내는 void와는 완전히 다른 개념인 것이다.
  • Unit이 반환 타입인 함수는 아무 값도 반환하지 않는 것이 아니다.
  • 함수형 프로그래밍에서 전통적으로 Unit은 '단 하나의 인스턴스만 갖는 타입'을 의미해왔고 바로 그 유일한 인스턴스 유무가 자바 void와 코틀린 Unit을 구분하는 가장 큰 차이다.
interface Processor<T> {
    fun processor(): T
}
class NoResultProcessor: Processor<Unit> {
    override fun process() { // Unit을 반환하지만 반환 타입 명시할 필요는 없다.
        // 명시적으로 return문을 작성할 필요가 없다.
    }
}
  • 자바에서는 타입 인자로 '값 없음'을 표현하는 문제를 코틀린과 같이 깔끔하게 해결 할 수 없다.
  • 굳이 해결하자면 별도의 인터페이스를 사용해 값을 반환하는 경우와 값을 반환하지 않는 경우를 분리하는 방법을 사용할 수 있다.
  • 다른 방법으로는 타입 파라미터로 특별히 java.lang.Void 타입을 사용하는 방법도 있다.
  • 하지만 Void를 사용한다 하더라도 Void 타입에 대응할 수 있는 유일한 값인 null을 반환하기 위해 return null을 명시해줘야하기 때문에 진정으로 그 어떤 값도 반환하지 않는 상태를 나타낼 수는 없다.
  • 어쩔 수 없이 프로그래밍 언어에서 관례적으로 사용해 온 Void라는 이름을 사용할 수도 있겠지만 코틀린에서는 아무 값도 반환하지 않는 상태를 나타내기 위한 Nothing이라는 전혀 다른 기능을 하는 타입이 하나 존재한다.

8.1.7 Nothing 타입: 이 함수는 결코 반환되지 않는다.

  • 코틀린에는 결코 성공적으로 값을 돌려주는 일이 없어서 '반환값'이라는 개념 자체가 의미가 없는 함수가 일부 존재한다.
fun fail(message: String) : Nothing {
    throw IllegalStateException(message)
}
fun main() {
    fail("Error Occured")
    // java.lang.IllegalStateException: Error Occured
}
  • Nothing 타입은 아무 값도 포함하지 않는다.
  • Nothing은 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 쓸 수 있다.
  • 그 외의 다른 용도로 사용하는 경우 Nothing 타입의 변수를 선언하더라도 그 변수에 아무 값도 저장할 수 없으므로 아무 의미가 없다.
  • Nothing을 반환하는 함수를 엘비스 연산자으 오른쪽에 사용해서 전제 조건을 검사할 수 있다.
val address = company.address ?: fail("No address")
println(address.city)

8.2 컬렉션과 배열

8.2.1 널이 될 수 있는 값의 컬렉션과 널이 될 수 있는 컬렉션

  • List< Int? >와 List< Int >?
fun readNumbers(text: String): List<Int?> {
    val result = mutableListOf<Int?>()
    for (line in text.lineSequence()) {
        val numberOrNull = line.toIntOrNull()
        result.add(numberOrNull)
    }
    return result
}
  • 어떤 변수 타입의 널 가능성과 타입 파라미터로 쓰이는 타입의 널 가능성 사이의 차이를 알아둬야 한다.
  • List< Int? > => 리스트 자체는 항상 null이 아니지만 리스트에 들어있는 각 원소는 null이 될 수 있다.
  • List< Int >? => 리스트를 가리키는 변수에는 null이 들어갈 수 있지만 리스트 안에는 null이 아닌 값만 들어간다.
  • 널이 될 수 있는 값으로 이뤄진 널이 될 수 있는 리스트를 정의해야할 수도 있다.
  • 코틀린에서는 물음표를 2개 사용해 List< Int? >?로 이를 표현한다.

8.2.2 읽기 전용과 변경 가능한 컬렉션

  • 코틀린 컬렉션과 자바 컬렉션을 나눈는 가장 중요한 특성 중 하나는 코틀린에서는 컬렉션 안의 데이터에 접근하는 인터페이스와 컬렉션 안의 데이터를 변경하는 인터페이스를 분리했다는 점이다.
  • kotlin.collections.Collection 인터페이스 => 컬렉션 안의 원소에 대해 이터레이션하기, 컬렉션의 크기 구하기, 컬렉션 안에 어떤 값이 들어있는지 검사하기, 컬렉션 안의 데이터 읽기를 수행할 수 있지만 원소를 추가하거나 제거하는 메서드가 없다.
  • kotlin.collections.MutableCollection 인터페이스 => 일반 인터페이스인 kotlin.collections.Collection 인터페이스를 확장하면서 원소를 추가하거나, 삭제하거나, 컬렉션 안의 원소를 모두 지우는 등의 메서드를 좀 더 제공한다.
  • 가능하면 코드에서 항상 읽기 전용 인터페이스를 사용하는 것을 일반적인 규칙으로 한다.
  • 코드가 컬렉션을 변경할 필요가 있을 때만 변경 가능한 버전을 사용한다.
  • 어떤 컴포넌트의 내부 상태에 컬렉션이 포함된다면 그 컬렉션을 MutableCollection을 인자로 받는 함수에 전달할 때는 어쩌면 원본의 변경을 막기 위해 컬렉션을 복사해야 할 수도 있다.
fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) {
    for(item in source){ // 
        target.add(item)
    }
}
fun main() {
    val source: Collection<Int> = arrayListOf(3, 5, 7)
    val target: MutableCollectin<Int> = arrayListOf(1)
    copyElements(source, target)
    println(target)
    // [1, 3, 5, 7]
}
  • 객체가 실제로는 변경 가능한 컬렉션이라 할지라도 copyElements 함수의 target에 해당하는 인자로 읽기 전용 타입의 값을 넘길 수는 없다.
fun main() {
    val source: Collection<Int> = arrayListOf(3, 5, 7)
    val target: Collectin<Int> = arrayListOf(1)
    copyElements(source, target)
    println(target)
    // Error: Type mismatch: inferred type is Collection<Int>
    // but MutableCollection<Int> was expected
}
  • 코드의 일부분이 가변 컬렉션에 대한 참조를 갖고 있고 다른 부분에서 같은 컬렉션에 대한 '뷰'를 갖고 있다면 후자의 코드는 전자가 컬렉션을 동시에 변경할 수 없다는 가정에 의존할 수 없다.
  • 이런 경우 코드가 컬렉션을 사용하는 도중에 다른 스레드 등에 의해 컬렉션이 변경되는 상황이 생길 수 있고, 이로 인해 ConcurrentModificationException이나 다른 오류가 발생할 수 있다.
  • 읽기 전용 컬렉션이 항상 스레드 안전하지는 않다는 점을 명시해야 한다.
  • 함수가 얻은 컬렉션의 뷰가 실제로는 내부에서 변경 가능한 컬렉션을 가리킬 수 있다.
  • 다중 스레드 환경에서 데이터를 다루는 경우 그 데이터를 적절히 동기화하거나 동시 접근을 허용하는 데이터 구조를 활용해야 한다.

  • 같은 컬렉션 객체를 가지키는 다른 타입(읽기 전용 리스트와 변경 가능 리스트)의 참조들
  • list에 접근하는 코드는 대상 컬렉션을 변경할 수 없지만 mutableList를 통해 접근하는 코드를 통해 대상 컬렉션을 변경할 수 있다.

8.2.3 코틀린 컬렉션과 자바 컬렉션은 밀접히 연관됨

  • 모든 코틀린 컬렉션은 그에 상응하는 자바 컬렉션 인터페이스의 인스턴스라는 점은 사실이다.
  • 코틀린과 자바 사이를 오갈 때 아무 변환도 필요 없다. 래퍼 클래스를 만들거나 데이터를 복사할 필요가 없다.
  • 다만 코틀린은 모든 자바 컬렉션 인터페이스마다 읽기 전용 인터페이스와 변경 가능 인터페이스라는 2가지 표현을 제공한다.

  • 코틀린의 읽기 전용 인터페이스와 변경 가능 인터페이스의 기본 구조는 java.util 패키지에 있는 자바 컬렉션 인터페이스의 구조와 같다.
  • 추가로 각 변경 가능 인터페이스는 자신과 대응하는 읽기 전용 인터페이스를 확장한다.
  • 변경 가능한 인터페이스는 java.util 패키지에 있는 인터페이스와 직접적으로 연관되지만 읽기 전용 인터페이스에는 컬력션을 변경할 수 있는 모든 요소가 빠져있다.
  • 자바 ArrayList와 HashSet은 코틀린의 변경 가능 인터페이스를 확장한다.
  • 코틀린은 java.util.ArrayList와 java.util.HashSet 클래스가 각각 코틀린의 MutableList와 MutableSet 인터페이스 상속한 것처럼 취급한다.
  • 컬렉션과 마찬가지로 Map 클래스(Map은 Collection이나 Iterable을 확장하지 않는다.)도 코틀린에서 Map과 MutableMap이라는 2가지 버전으로 나타난다.
  • 컬렉션과 맵 생성 함수
컬레션 타입 읽기 전용 타입 변경 가능 타입
List listOf, List mutableListOf, MutableList, arrayListOf, buildList
Set setOf mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf, buildSet
Map mapOf mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf, buildMap
* setOf()와 mapOf()는 Set과 Map 읽기 전용 인터페이스의 인스턴스를 반환하지만 내부적으로는 변경 가능한 클래스다.    
* 중요한 것은 그 둘이 변경 가능한 클래스라는 사실에 의존하면 안 된다는 것    
* setOf()나 mapOf()가 진정한 불변 컬렉션 인스턴스를 반환하게 바뀔 수도 있다.    
  • 자바 메서드를 호출하되 컬렉션을 인자로 넘겨야 한다면 따로 변환하거나 복사하는 등의 추가 작업없이 직접 컬렉션을 넘기면 된다.
  • java.util.Collection을 파라미터로 받는 자바 메서드가 있다면 아무 Collection이나 MutableCollection 값을 인자로 넘길 수 있다.
  • 이 때, 자바는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않으므로 코틀린에서 읽기 전용Collection으로 선언된 객체라도 자바 코드에서는 그 컬렉션 객체의 내용을 변경할 수 있다.
  • 컬렉션을 자바로 넘기는 코틀린 프로그램을 작성한다면 호출하려는 자바 코드가 컬렉션을 변경할 지 여부에 따라 올바른 파라미터 타입을 사용할 책임은 코드 작성자가 져야 한다.
/* 자바 코드 */
// CollectionUtils.java
public classs CollectionUtils {
    public static List<String> uppercaseAll(List<String> items) {
        for (int i = 0; i < items.size(); i++){
            items.set(i, items.get(i).toUpperCase());
        }
        return items;
    }
}

// 코틀린 코드
// collections.kt
fun printInUppercase(list: List<String>) { // 파라미터 타입 선언: 읽기 전용 리스트
    println(CollectionUtils.uppercaseAll(list))
    println(list.first())
}

fun main() {
    val list = listOf("a", "b", "c")
    printInUppercase(list)
    // [A, B, C]
    // A
}

8.2.4 자바에서 선언한 컬렉션은 코틀린에서 플랫폼 타입으로 보임

  • 자바의 널 가능성을 코틀린에서 다루는 방식: 자바 코드에서 정의한 타입을 코틀린에서는 플랫폼 타입으로 본다.
  • 플랫폼 타입의 경우 코틀린 쪽에는 널 관련 정보가 없다.
  • 코틀린 컴파일러는 코틀린 코드가 그 타입을 널이 될 수 있는 타입이나 널이 될 수 없는 타입 어느 쪽으로든 사용할 수 있도록 허용한다.
  • 타입의 널 가능성과 마찬가지로 자바 쪽에서 선언한 컬렉션 타입의 변수코틀린에서는 플랫폼 타입으로 본다.
  • 플랫폼 타입인 컬렉션은 기본적으로 변경 가능성에 대해 알 수 없다.
  • 따라서 코틀린 코드는 그 타입을 읽기 전용 컬렉션이나 변경 가능한 컬렉션 어느 쪽으로든 다룰 수 있다.
  • 컬렉션 타입이 시그니처에 들어간 자바 메서드 구현을 코틀린에서 오버라이드 하려는 경우 읽기 전용 컬렉션과 변경 가능 컬렉션의 차이가 문제가 된다.
  • 플랫폼 타입에서 널 가능성을 다룰 때처럼 이런 경우에도 오버라이드 하려는 메서드의 자바 컬렉션 타입을 코틀린의 어떤 컬렉션 타입으로 표현할지, 읽기 전용 컬렉션 타입으로 할지, 변경 가능 컬렉션 타입으로 표현할지 결정해야 한다.
  • 판단 기준
    • 컬렉션이 null이 될 수 있는가?
    • 컬렉션의 원소가 널이 될수 있는가?
    • 작성하고자 하는 메서드가 컬렉션을 변경할 수 있는가?
/* 자바 */
interface FileContentProcessor {
    void processContents(
        File path,
        byte[] binaryContents,
        List<String> textContents
    );
}
  • 일부 파일이 이진 파일이고 이진 파일 내용은 텍스트로 표현할 수 없는 경우가 있으므로 리스트는 null이 될수 있다.
  • 파일의 각 줄은 null일 수 없으므로 이 리스트의 원소는 null이 될 수 없다.
  • 이 리스트는 파일 내용을 표현하면 그 내용을 바꾸지 않을 것임로 읽기 전용 타입이다.
// 코틀린 코드
class FileIndexer: FileContentProcess {
    override fun proessContents(
        File path,
        binaryContents: ByteArray?,
        textContents: List<String>?        
    ){ ... }
}

8.2.5 성능과 상호운용을 위해 객체의 배열이나 원시 타입의 배열을 만들기

  • 코틀린 배열은 타입 파라미터를 받는 클래스
  • 배열의 원소 타입은 바로 그 타입 파라미터에 의해 정해진다.
fun main(args: Array<String>) {
    for (i in args.indices) {
        println("Argument $i is: ${args[i]}")
    }
}
  • arrayOf 함수는 인자로 받은 원소들을 포함하는 배열을 만든다.
  • arrayOfNulls 함수는 모든 원소가 null인 정해진 크기의 배열을 만들 수 있다. 원소 타입이 널이 될 수 있는 타입인 경우에만 사용 가능
  • Array 생성자는 배열 크기와 람다를 인자로 받아 람다를 호출해서 각 배열 원소를 초기화해준다.
fun main() {
    val letters = Array<String>(26) { i -> ('a' + i).toString() }
    println(letters.joinToString(""))
    // abcdefghijklmnopqrstuvwxyz
}
  • 원소 타입 추론
fun main() {
    val letters = Array(26) { i -> ('a' + i).toString() }
    println(letters.joinToString(""))
    // abcdefghijklmnopqrstuvwxyz
}
  • 컬렉션을 배열로 바꾸기: toTypedArray 메서드
fun main() {
    val strings =  listOf("a", "b", "c")
    println("%s%s%s".format(*strings.toTypedArray())) // vararg 인자로 넘기기 위해
}                                                     // 스프레드 연산자(*) 사용
  • Array< Int >와 같은 타입을 선언하면 그 배열은 박싱된 정수의 배열이다.(java.lang.Integer[])
  • 박싱하지 않은 원시 타입의 배열이 필요하다면 그런 타입을 위한 배열 클래스 사용 필요
  • ByteArray, CharArray, BooleanArray, IntArray 등의 원시 타입 배열을 코틀린에서 제공
  • 이 모든 타입은 자바 원시 타입 배열인 int[], byte[], char[] 등으로 컴파일된다.
  • 원시 타입 배열 생성
val fiveZeros = IntArray(5)
val fiveZerosToo = intArrayOf(0, 0, 0, 0, 0)
  • 람다를 인자로 받는 원시 타입 배열 생성자
fun main(){
    val squares = IntArray(5) { i -> (i + 1) * (i + 1) }
    println(squares.joinToString())
    // 1, 4, 9, 16, 25
}
  • 이미 박싱된 값이 들어있는 컬렉션이나 배열이 있다면 toIntArray 등의 변환 함수를 사용해 박싱하지 않은 원시 타입 값이 들어있는 배열로 변환할 수 있다.
  • 코틀린 라이브러리에서는 배열 기본 연산에 더해 컬렉션에 사용할 수 있는 모든 확장 함수를 배열에도 제공한다.(filter, map, forEach 등)
  • 원시 타입인 원소로 이루어진 배열에도 그런 확장 함수를 똑같이 사용할 수 있다.
fun main(args: Array<String>) {
    args.forEachIndexed{
        index, element -> println("Argument $index is: $element")
    }
}

'Language > Kotlin' 카테고리의 다른 글

[API] 3. 고차 함수  (2) 2025.07.14
[API] 2. 컬렉션과 시퀀스  (1) 2025.07.07
[API] 1. 람다를 사용한 프로그래밍  (3) 2025.06.04
[OOP] 2. 널이 될 수 있는 값  (0) 2025.06.04
[OOP] 1. 클래스, 객체, 인터페이스  (1) 2025.06.02