본문으로 바로가기

[Kotlin] 코틀린의 Collection

category 개발이야기/Kotlin 2018. 4. 15. 02:25
반응형

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

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

3.1 코틀린에서 컬렉션 사용

시작하면서 먼저 언급하자면 코틀린은 따로 collection을 라이브러로 만들어서 제공하지 않습니다.
Java의 collection을 그대로 사용한다는 얘기입니다.
다만 몇가지 확장성 있는 API를 제공합니다.

먼저 코틀린에서 collection을 사용해 보겠습니다.
val set = hashSetOf(1, 7, 53)
val list = arrayListOf(1, 7, 53)
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

fun main(args: Array) {
    println(set.javaClass)
    println(list.javaClass)
    println(map.javaClass)
}
Collection을 만들어 쓰는건 어렵지 않습니다.
hasSetOf(...), arrayListOf(...), hashMapOf(key to value,....)

위에서 사용한 To는 키워드가 아니라 일반 함수입니다.
이건 어떻게 만들고 사용하는지 뒤에 다시 정리하돌고 하겠습니다.
여기서는 사용법만 먼저 보시죠.

그리고 .javaClass는 자바에서는 getClass()의 의미 입니다.
따라서 이들의 return 값은 아래와 같습니다.

class java.util.HashSet
class java.util.ArrayList
class java.util.HashMap

fun main(args: Array) {
    val strings = listOf("first", "second", "fourteenth")
    println(strings.last())
    val numbers = setOf(1, 14, 2)
    println(numbers.max())
}

코틀린은 자체적으로 컬렉션을 가지고 있지 않고 Java의 컬렉션을 사용한다고 했는데, last() 나 max()는 코틀린에서 편의를 위해 추가한 확장 함수 입니다.

확장함수 (Extension function)은 어떻게 만드는지 보도록 하겠습니다.


3.2 확장함수 만들기

자바에는 toString()이 기본으로 구현되어 있습니다.
만약 원하는 형태로 출력을 하고자 한다면 toString()를 override해서 재정의하면 됩니다.

list의 toString()은 [x,y,z,....] 형태로 출력되도록 구현되어 있습니다.
이를 코틀린에서 시작, 끝, 구분자를 바꿔서 출력할 수 있는 joinToString()이란 함수로 만들어 보겠습니다.
fun <T> joinToString(
        collection: Collection<T>,
        separator: String,
        prefix: String,
        postfix: String): String {

    val result = StringBuilder(prefix)

    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator) // 첫번째 인자에는 seperator가 붙지 않도록.
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

fun main(args: Array) {
    val list = listOf(1, 2, 3)
    println(joinToString(list, "; ", "(", ")"))
}

어려운 함수가 아닙니다.

사실 이렇게 짠다면 collection에서 withIndex를 썼다는것 외에는 java와 kotlin의 장점이 드러나지 않습니다.

이제 코틀린다운 사용법을 알아보겠습니다.


3.2.1 인자의 이름. (parameter name)

joinToString()은 네개의 파라미터를 받습니다.
근데 직접 함수의 구성을 보지 않고서야 각 파라미터의 위치가 어떤값을 의미하는지를 알수가 없지요.
(요센 Tool이 좋아서 함수 사용시 개발 Tool에서 그 함수 시그니처에 대한 정보를 주긴 합니다.)

자바에서는 파라미터의 의미를 정확하게 하고, 다른 개발자의 가독성을 높여주도록 아래와 같이 사용합니다.
joinToString(collection, /* separator */ "", /* prefix */ "", /*postfix */ ".");

하지만 코틀린에서는 아예 명시적으로 파라미터 이름을 인자에 같이 넣어줄 수 있습니다.

joinToString(collection, separator = "", prefix = "", postfix = ".")

파라미터 이름이 있다면 순서를 바꿔도 가능합니다!!


3.2.2 기본 파라미터 값 (Default Parameter Value)

자바에서는 같은 이름의 함수에 다양한 파라미터를 전달받기 위해 Overloading을 사용합니다.
근데 사용하다보니, Overloading 함수가 계속 생겨나고 누가 누굴 호출하는건지 복잡도도 같이 올라가며, 코드도 중복되는 단점들이 존재합니다.

코틀린에서는 이런 단점을 극복하기 위해 함수선언시 파라미터에 기본값을 넣어줄 수 있습니다.
즉, 파라미터가 들어오지 않는다면 기본값을 사용하겠다는 의미인거죠.
fun <T> joinToString(
        collection: Collection<T>,
        separator: String = ", ",
        prefix: String = "",
        postfix: String = ""): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

fun main(args: Array) {
    val list = listOf(1, 2, 3)
    println(joinToString(list, separator = "; ", prefix = "(", postfix = ")"))
    println(joinToString(list, separator = "; ", prefix = "("))
    println(joinToString(list, separator = "; ")
    println(joinToString(list)
}

main 함수에서 첫번째 println 처럼 모든 parameter를 명시적으로 정해줄 수도 있고, 하나를 생각해도 되며, 필수요소인 한개만 넣어도 됩니다.
생략된 파라미터들은 기본값으로 사용됩니다.

만약 이 함수를 자바에서도 사용한다면 문제가 생깁니다.
따라서 joinToString()에 @JvmOverloads를 붙이며, compiler가 자동으로 자바를 위한 overloading 함수를 생성합니다.

3.2.3 Top level function 과 property

자바는 모든 함수를 class 내부에서 선언하고 구현해야 합니다.

하지만 코틀린은 클래스 외부에 선언함으로써 어디서든 접근할 수 있는 함수를 만들 수 있습니다.

즉 여러곳에서 호출해서 쓰려고 만드느 자바의 util 클래스가 사라질수 있습니다.


// 파일명: Join.kt
package strings
fun joinToString(...): String {...}


위와 같이 생성한다면, 다른곳에서 해당 package만 import한다면 joinToString을 호출할 수 있습니다.

위 코드를 컴파일하면 컴파일러는 사실 클래스를 하나 만들고 그안에 함수를 집어 넣습니다.


이 코드르 자바로 표현하여 아래와 같습니다.

package strings;

public class JoinKt {
    public static String joinToString(...) {...}
}
즉 Top-level function은 static 함수라고 볼수 있습니다.


자바에서 이 함수를 호출하려면, 아래처럼 호출하면 됩니다.

import strings.JointKt;
...
    JoinKt.joinToStirng(list,",","","");

만약 JoinKt라는 이름 대신 대응하는 클래스 이름을 바꾸고 싶다면 @file:JvmName 어노테이션을 사용하면 됩니다.

@file:JvmName("StringFunction")
package strings

fun joinToString(...); String{...}

단!! @file은 package 선언보다 위에 있어야 합니다.


추가적으로 최상위에 property도 위치시킬수 있습니다.


var totalCount = 0
const val CONNECTION_TIME_OUT = 1000

fun addOperation() {
    totalCount++;
}
최상위에 정의된 totalCount는 addOperation()이 호출될때마다 값이 증가됩니다.

CONNECTION_TIME_OUT은 const라는 키워드를 이용해 상수값이 됩니다.

(이는 자바에서 public static final 과 같습니다.)

만약 const를 붙이지 않는다면 getter를 같는 property가 됩니다.


반응형