Programming/Kotlin

코틀린(Kotlin) - 컬렉션과 배열

JunsuKim 2021. 10. 24.
728x90

널 가능성과 컬렉션

컬렉션 안에 널 값을 넣을 수 있는지의 여부는 어떤 변수의 값이 널이 될 수 있는지 여부와 같이 중요하다.

컬렉션 타입 인자 뒤에 ?를 붙이면 널이 될 수 있는 값으로 이루어진 컬렉션을 만들 수 있다.

fun readNumbers(reader: BufferedReader) : List<Int?> {
   val result = ArrayList<Int?>()  
   for (line in  reader.lineSequence()) {
      try {
         val number = line.toInt()
         result.add(number)
      }
      catch(e: NumberFormatException) {
         result.add(null) 
      }
   }
   return result
}

코드를 보면 2행에서 List<Int?>를 볼 수 있다.

List<Int?>는 Int? 타입의 값을 저장할 수 있는데, 이는 그 리스트에 Int나 null을 저장할 수 있다는 것이다.

List<Int?>의 경우 리스트 자체는 null이 아니고, 원소가 null이 될 수 있다.

List<Int>?는 리스트가 null이 될 수 있지만, 원소 자체는 null이 아닌 값만 들어간다.

 

경우에 따라 널이 될 수 있는 값으로 이뤄진 널이 될 수 있는 리스트를 정의해야 할 수 있는데,

이는 물음표(?)를 2개 사용하여 List<Int?>?로 표현한다.

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

코틀린 컬렉션은  컬렉션 안의 데이터에 접근하는 인터페이스와 데이터를 변경하는 인터페이스를 분리한다.

이 구분은 코틀린 컬렉션을 다룰 대부터 사용하는 가장 기초의 인터페이스인 kotlin.collections.Collection에서 시작한다.

Collection 인터페이스를 사용하면 컬렉션 안의 원소에 대해 이터레이션을 하고, 컬렉션의 크기를 얻고, 어떤 값이 컬렉션 안에 들어있는지 검사하고, 컬렉션에서 데이터를 읽는 여러 다른 연산을 수행할 수 있지만, 원소를 추가 또는 제거하는 메소드는 없다.

 

컬렉션의 데이터를 수정하기 위해서는 kotlin.collections.MutableCollection 인터페이스를 사용하면 된다.

MutableCollection은 일반 인터페이스를 확장하며 원소를 추가, 삭제, 모두 지우는 등의 메소드를 제공한다.

 

코드에서 항상 읽기 전용 인터페이스를 사용하는 것을 일반적으로 해두는며, 코드가 컬렉션을 변경할 필요가 있을 때만 변경 가능한 버전을 사용하는 것이 좋다.

컬렉션 인터페이스를 사용할 떄 항상 염두에 둬야 하는 핵심은 읽기 전용 컬렉션이라 해서 꼭 변경 불가능한 컬렉션일

필요는 없다는 것이다.

그 인터페이스는 사실 어떤 컬렉션 인스턴스를 가리키는 수많은 참조 중 하나일 수 있기 때문이다.

위와 같은 상황에서 이 컬렉션을 참조하는 다른 코드를 호출하거나 병렬 실행한다면 컬렉션을 사용하는 도중 다른 컬렉션이 그 컬렉션의 내용을 변경하는 상황이 생겨 ConcurrentModificationException이나 다른 오류가 발생할 수 있다.

따라서 읽기 전용 컬렉션이 항상 스레드 안전(thread safe)하지는 않다.

코틀린 컬렉션과 자바

모든 코틀린 컬렉션은 그에 상응하는 자바 컬렉션 인터페이스와 인스턴스이다.

하지만 코틀린은 모든 자바 컬렉션 인터페이스마다 읽기 전용 인터페이스와 변경 가능한 인터페이스라는 두 가지 표현을 제공한다.

코틀린의 읽기 전용과 변경 가능 인터페이스의 기본 구조는 java.util 패키지에 있는 자바 컬렉션 인터페이스의 구조를

그대로 옮겨놓았다. 추가로 변경 가능한 각 인터페이스는 자신과 대응하는 읽기 전용 인터페이스를 확장(상속)한다.

 

변경 가능한 인터페이스는 java.util 패키지에 있는 인터페이스와 직접적으로 연관되지만 읽기 전용 인터페이스는

컬렉션을 변경할 수 있는 모든 요소가 빠져있다.

컬렉션 타입 읽기 전용 타입 변경 가능 타입
Link linkOf mutalbeListOf, arrayListOf
Set setOf mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf
Map mapOf mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf

컬렉션을 플랫폼 타입으로 다루기

자바 코드에서 정의한 타입을 코틀린에서는 플랫폼 타입으로 본다는 것을 위에서 봤었다.

플랫폼 타입인 컬렉션은 기본적으로 변경 가능성에 대해 알 수 없다. 때문에 코틀린 코드는 그 타입을 읽기 전용 컬렉션이나, 변경 가능한 컬렉션 어느 쪽으로든 다룰 수 있다.

플랫폼 타입 컬렉션을 사용할 때 주의해야 하는 점이 있다.

  • 컬렉션이 널이 될 수 있는가?
  • 컬렉션의 원소가 널이 될 수 있는가?
  • 오버라이드하는 메소드가 컬렉션을 변경할 수 있는가?

객체의 배열과 원시 타입의 배열

코틀린 배열은 타입 파라미터를 받는 클래스이고, 배열의 원소 타입은 타입 파라미터에 의해 정해진다.

코틀린에서 배열을 만드는 다양한 방법을 보자.

  • arrayOf 함수에 원소를 넘겨 배열을 만든다.
  • arrayOfNulls 함수에 정수 값을 인자로 넘겨 모든 원소가 null이고 인자로 넘긴 값과 크기가 같은 배열을 만들 수 
    있다.(원소 타입이 널이 될 수 있는 타입인 경우에만 사용)
  • Array 생성자는 배열 크기와 람다를 인자로 받아  람다를 호출하여 각 배열 원소를 초기화한다.
    (arrayOf를 쓰지 않고 각 원소가 널이 아닌 배열을 만들어야 하는 경우 이 생성자를 사용)

컬렉션을 배열로 변환하기 위해서는 toTypedArray 메소드를 사용하면 된다.

val strings = listOf("a", "b", "c")
println("%s%s%s".format(*strings.toTypedArray())

 

다른 제네릭 타입에서처럼 배열 타입의 타입 인자도 항상 객체 타입이 된다.

Array<Int> 같은 타입을 선언했다면 그 배열은 박싱된 정수의 배열(자바 타입은 java.lang.Integer[])이다.

박싱하지 않은 원시 타입의 배열이 필요하면 특별한 배열 클래스를 사용해야 한다.

 

코틀린은 원시 타입의 배열을 표현하는 별도 클래스를 각 원시 타입마다 하나씩 제공한다.

ByteArray, CharArray, BooleanArray 등의 원시 타입 배열은 자바 원시 타입 배열인 byte[]. char[], bool[] 등으로 컴파일된다.

원시 타입의 배열을 만드는 방법은 다음과 같다.

  • 각 배열 타입의 생성자는 size 인자를 받아 해당 원시 타입의 디폴트 값(보통은 0)으로 초기화된 size 크기의 배열을 반환한다.
  • 팩토리 함수(IntArray를 생성하는 intArrayOf 등)는 여러 값을 가변 인자로 받아서 그런 값이 들어간 배열을 반환한다.
  • 크기와 람다를 인자로 받는 생성자를 사용한다.
val fiveZeros = IntArray(5)
val fizeZerosToo = intArrayOf(0, 0, 0, 0, 0}
val squares = IntArray(5) { i -> (i + 1) * (i + 1) }

또한 박싱된 값이 들어있는 컬렉션이나 배열이 있다면 toIntArray 등의 변환 함수를 사용해 박싱하지 않은 값이 들어있는 배열로 변환할 수 있다.

728x90

댓글