[Kotlin] 6. 함수 정의와 호출

2023. 3. 24. 23:35Programming Languages/Kotlin

1. Kotlin에서 Collection 만들기

// java.util.HashSet
val set = hashSetOf(1, 8, 53)

// java.util.ArrayList
val list = arrayListOf(1, 7, 53)

// java.util.HashMap
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
💡Kotlin은 Java와의 호환을 위해 자체 컬렉션을 제공하지 않고 Java의 컬렉션을 차용함으로써 Java Collection과 Kotlin Collection을 변환할 필요가 없게 설계되었다.

2. 함수를 호출하기 쉽게 만들기

예제 함수

fun <T> joinToString (
    collection: Collection<T>,
    separator: String,
    prefix: String,
    suffix: String
): String {
    val result = StringBuilder(prefix)
    for ((index, elem) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(elem)
    }
    result.append(suffix)
    return result.toString()
}

val list = listOf(1, 2, 3)
println(joinToString(list, ", ", "{", "}")

1. named parameter

함수의 인자 이름을 지정하여 값을 지정할 수 있다. 이로써 가독성이 향상되고 Java에서 객체 생성 시 많이 사용되는 Builder 패턴을 대체할 수 있다.

fun <T> joinToString (
    collection: Collection<T>,
    separator: String,
    prefix: String,
    suffix: String
): String {
    val result = StringBuilder(prefix)
    for ((index, elem) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(elem)
    }
    result.append(suffix)
    return result.toString()
}

val list = listOf(1, 2, 3)
println(joinToString(
    collection = list,
    separator = ", ",
    prefix = "{",
    suffix = "}"
)

2. default parameter

인자값이 없는 경우 기본적으로 사용되는 값을 지정할 수 있다.

fun <T> joinToString (
    collection: Collection<T>,
    separator: String = ", ",
    prefix: String = "[",
    suffix: String = "]"
): String {
    val result = StringBuilder(prefix)
    for ((index, elem) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(elem)
    }
    result.append(suffix)
    return result.toString()
}

val list = listOf(1, 2, 3)
println(joinToString(
    collection = list,
    separator = ", ",
    prefix = "{",
    suffix = "}"
)
⚠️Java에는 default parameter라는 개념이 없어서 Kotlin 함수를 Java에서 호출하는 경우 그 Kotlin 함수가 default parameter를 제공하더라도 모든 인자를 명시해야 한다. Java에서 Kotlin 함수를 자주 호출한다면 이 부분이 귀찮을 것이다. 이럴 때 @JvmOverloads 어노테이션을 추가할 수 있다. @JvmOverloads를 함수에 추가하면 Kotlin 컴파일러가 자동으로 맨 마지막 파라미터로부터 파라미터를 하나씩 생략한 오버로딩한 Java 메서드를 추가해준다. 예를 들어 위 함수의 경우는 다음과 같이 생성된다.
String joinToString(Collection<T> collection, String separator, String prefix, String postfix);
String joinToString(Collection<T> collection, String separator, String prefix);
String joinToString(Collection<T> collection, String separator);
String joinToString(Collection<T> collection);

3. 정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티

1. 최상위 함수

Java에서는 모든 코드가 클래스 안에 들어가야 하므로 정적 메서드만 모아두는 유틸리티 클래스를 따로 만들어두는 경우가 많았다. Kotlin에서는 이런 무의미한 클래스 없이 함수를 만들 수 있다.

package strings

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

이 경우 컴파일러는 이 파일을 컴파일할 때 새로운 클래스를 정의해준다. 예를 들어 위 파일의 이름이 join.kt 라고 한다면 컴파일한 결과가 같은 Java 코드는 다음과 같다.

package strings;

public class JoinKt {
    public static String joinToString(...) { ... }
}
⚠️Kotlin 최상위 함수가 포함되는 클래스의 이름을 바꾸렬면 파일에 @JvmName 어노테이션을 추가하면 된다.@JvmName은 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다.
@file:JvmName("StringFunction")

package strings

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

위 함수는 다음과 같이 호출할 수 있다.

import string.StringFunction;
StringFunction.joinToString(list, ", ", "", "");

2. 최상위 프로퍼티

프로퍼티도 파일의 최상위 수준에 놓을 수 있다.

// Kotlin Code
var opCount = 0

fun performOperation() {
    opCount++
}

최상위 프로퍼티의 값은 정적(static) 필드에 저장된다.

//Decompile한 Java Code
public final class StringsFunctionKt {
    private static int opCount;
    
    public static final int getOpCount() { return opCount; }
    
    public static final int setOpCount() { opCount = var0; }
}

최상위 프로퍼티를 통해 상수를 지정할 수도 있다. val의 경우 getter만, var의 경우 getter, setter가 생긴다.

val opCount = 0
public final class StringsFunctionKt {
    private static final int opCount;
    
    public static final int getOpCount() { return opCount; }
}

기본적으로 최상위 프로퍼티도 다른 프로퍼티처럼 getter와 setter로 접근한다. 겉으로는 상수처럼 보이지만 getter와 setter로 접근하는 방법은 자연스럽지 못하다. 따라서 public static final로 정의하는 편이 더 자연스럽다. 이렇게 선언하려면 const 키워드를 함께 사용하면 된다.

const val opCount = 0
public final class StringsFunctionKt {
    public static final int opCount;
}