본문으로 바로가기

[Kotlin] 코틀린 Collection과 배열

category 개발이야기/Kotlin 2018. 5. 2. 00:30
반응형

이 글은 Kotlin In Action을 참고 하였습니다.

더욱 자세한 설명이나 예제는 직접 책을 구매하여 확인 하시기 바랍니다

6.3.1 Collection의 null 처리.

앞서 nullable을 표기하기 위해서 type에 ?를 붙이는것을 언급했습니다.
Collection을 사용할때는 nullable을 원소또는 자체에 붙일수 있습니다.
  • List<Int> : list도 null이 아니고 원소에도 null이 없다.
  • List<Int?> :list는 null이 아니나 원소는 null일 수 있다.
  • List<Int>? : list가 null이 될수 있으나 원소는 null이 아니다.
  • List<Int?>? : list와 원소모두 null이 될수 있다.
위 네가지는 전부 의미하는 바가 다릅니다.
따라서 각각에 맞는 null처리를 list 자체 또는 원소의 상태에 따라 해야 합니다.

fun readNumbers(reader: BufferedReader): List {
    val result = ArrayList()
    for (line in reader.lineSequence()) {
        try {
            val number = line.toInt() //숫자로 변경하지 못할수도 있다.
            result.add(number)
        }
        catch(e: NumberFormatException) {
            result.add(null)           // 변경하지 못할경우 null을 
        }
    }
    return result
}

fun addValidNumbers(numbers: List) {
    var sumOfValidNumbers = 0
    var invalidNumbers = 0
    for (number in numbers) {
        if (number != null) {
            sumOfValidNumbers += number
        } else {
            invalidNumbers++
        }
    }
    println("Sum of valid numbers: $sumOfValidNumbers")
    println("Invalid numbers: $invalidNumbers")
}

fun addValidNumbers2(numbers: List) {
    var sumOfValidNumbers = numbers.filterNotNull()
    println("Sum of valid numbers: ${sumOfValidNumbers.sum()}")
    println("Invalid numbers: ${sumOfValidNumbers.size - numbers.size} ")
}

fun main(args: Array) {
    val reader = BufferedReader(StringReader("1\nabc\n42"))
    val numbers = readNumbers(reader)
    addValidNumbers(numbers)
}

addValidNumbers2()는 filterNotNull()을 이용하여 addValidNumbers()을 짧게 표현한 함수입니다.


6.3.2 Collection VS mutableCollection

자바에서는 Collection에 읽고, 쓰기 함수가 모두 정의되어 있으나, 코틀린에서는 읽기용과 쓰기용이 분리되어 interface로 구현되어 있습니다.
interface kotlin.collections.Collection {
    val size ...
    open fun iterator()
    open fun contains()
}

interface kotlin.collections.MutableCollection implements Collection {
    open fun add()
    open fun remove()
    open fun clear()
}

이는 val, var와 같이 명시적으로 수정 가능여부를 표기하여 사용함에 따라 코드상으로 이해하기 쉽고, collection을 전달하여 다른곳에서 수정이 일어나는것을 방지하기 위한 방법으로도 사용할 수 있습니다.

또한 MutableCollection이 인자로 설정된 함수에 읽기전용을 넘기면 compile 에러가 납니다.

하나의 Collection 자체를 읽기전용으로 참조하고 있을수도 있고, Mutable collection으로 참조할수 있기 때문에 병렬처리시 원소가 발생하는 상황이 발생할 수 있습니다.
즉, 이 Collection들은 thread safe하지 않습니다.
이때 ConcurrentModificationException등의 오류가 발생할 수 있습니다.

6.3.3 자바 Collection과의 연결

코틀린은 읽기와 쓰기의 interface를 구분하지만 자바는 둘다 허용하기 때문에 자바의 collection은 MutableCollection을 상속받은것으로 취급하면 됩니다.
Map 역시 Map과 MutableMap으로 나뉘지만 자바의 Map은 MutableMap의 하위 class로 취급하면 됩니다.

  • 읽기전용: listOf, setOf, mapOf
  • 수정가능: mutableListOf, mutableSetOf, mutableMapOf (arrayListOf, hashSetOf, sortedSetOf, linkedSetOf, hashMapOf, linkedMapOf,sortedMapOf)
읽기 전용 setOf나 mapOf는 사실 자바 표준 라이브러리의 인스턴스를 반환하지만 용도에 맞게 읽기 전용으로 쓰는것이 좋습니다.

코틀린 -> 자바 호출시 자바에 인자로 java.util.Collection을 넘겨야 한다면 Collection이나 MutableCollection 어느걸 넘겨도 상관 없습니다.
따라서 읽기전용인 Collection을 자바에 넘기더라도, 이는 자바에서는 변경이 가능함을 염두해 두고 있어야 합니다.

또한 원소가 null이 아닌 형태로 지정하였으나, 자바에서 이 collection을 받아 null을 넣을수 있다는것도 주의할 필요가 있습니다.

6.3.4 플랫폼 타입의 Collection 처리

코틀리넹서 자바에서 정의된 collection 사용시 플랫폼 타입으로 넘어옵니다.
따라서 코틀린에서 이를 읽기전용으로 쓸지, 수정가능으로 쓸지는 사용용도에 따라서 정의하면 됩니다.
(어떤걸로도 정의해서 사용할 수 있습니다.)

다만 자바의 interface를 kotlin에서 상속받아 구현하는 경우 collection이 널이 될수 있는지, 원소가 널이 될수 있는지, override하는 메서드가 collection을 변경할 수 있는지르 염두해 두고 타입을 선정해야 합니다.

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

배열은 아래와 같은 방법으로 생성이 가능합니다.
  • arrayOf(원소1,원소2,...)
  • arrayOfNulls(개수) : 해당 개수만큼 null을 넣어 배열 생성
  • Array(개수, 생성식-lambda): 해당 개수만큼 주어진 람다를 이용해서 배열 생성
fun main(args: Array) {
    for (i in args.indices) { //배열의 인덱스를 읽음.
         println("Argument $i is: ${args[i]}")
    }
}


a~z를 원소로 갖는 람다를 이용한 배열 생성

fun main(args: Array) {
    val letters = Array(26) { i -> ('a' + i).toString() }
    println(letters.joinToString(""))
}

컬렉션을 배열로 변환: toTypedArray()

fun main(args: Array) {
    val strings = listOf("a", "b", "c")
    println("%s/%s/%s".format(*strings.toTypedArray()))
}

Array<Int>와 같은 형태로 배열 생성지 원소는 wrapping된 Integer 형태로 존재 합니다.

따라서 각 primitive type을 위한 Array가 별도로 존재합니다. IntArray, ByteArray, CharArray, BooleanArray


추가적으로 Array에도 filter, map 함수를 사용할 수 있으며 forEachIndexed()같은 확장함수도 사용할 수 있습니다.

fun main(args: Array) {
    args.forEachIndexed { index, element ->
        println("Argument $index is: $element")
    }
}

반응형