본문 바로가기
Kotlin

[Kotlin in Action] 3.2 함수를 호출하기 쉽게 만들기

by Nhahan 2025. 3. 14.

다음 코드의 joinToString 함수는 컬렉션의 원소를 StringBuilder의 뒤에 덧붙인다. 이때 원소 사이에 구분자를 추가하고, StringBuilder의 맨 앞과 맨 뒤에는 접두사와 접미사를 추가한다.

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)
        result.append(element)
    }
    result.append(postfix)
    
    return result.toString()
}

이 함수는 제네릭하다. 즉, 이 함수는 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있다.

>>> val list = listOf(1, 2, 3)
>>> println(joinToString(list, "; ", "(", ")"))
(1; 2; 3)

 

이제 이 함수를 좀 더 개선해서 쉽게 사용하게 만들어보자.

 

3.2.1 이름 붙인 인자

해결하고픈 첫 번째 문제는 함수 호출 부분의 가독성이다.

joinToString(collection, " ", " ", ".")

이를 해결하기 위해, 코틀린은 함수에 전달하는 인자 중 일부(or 전부)의 이름을 명시할 수 있다.

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

 

3.2.2 디폴트 파라미터 값

코틀린에서는 함수 선언에서 파라미터의 디폴트 값을 지정할 수 있으므로 자바의 오버로딩이 과도해지는 문제를 피할 수 있다.

디폴트 값을 사용해 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)
        result.append(element)
    }
    result.append(postfix)
    
    return result.toString()
}

 

자바에는 디폴트 파라미터 값이라는 개념이 없는데, 코틀린에서 만든 함수를 자바에서 호출해야하는 경우는 어떻게 해야할까?
이 경우, @JvmOverloads를 함수에 추가하면 코틀린 컴파일러가 자동으로 모든 경우의 수를 커버하는 오버로딩한 자바 메소드를 추가해준다.
@JvmOverloads // 함수에 추가
fun greet(name: String = "세계", greeting: String = "안녕") {
    println("$greeting, $name!")
}​

 

 

 

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

아래는 joinToString() 함수를 최상위 함수로 선언한 예제이다.

// join.kt
package strings
fun joinToString(...): String { ... }

이를 컴파일한 결과와 같은 클래스를 자바 코드로 써보면 다음과 같다.

package strings;
public class JoinKt {
    public static String joinToString(...) { ... }
}

 

만약, 자바에서 이 함수를 호출해서 사용한다면 아래와 같다.

import strings.JoinKt;
...
JoinKt.joinToString(list, ", ", "", "");

 

코틀린 최상위 함수가 포함되는 클래스의 이름을 바꾸고 싶다면 파일에 @JvmName을 추가하면 된다. @JvmName은 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다.
@file:JvmName("StringFunctions")
package strings

fun joinToSTring(...): String { ... }​

 

이제 다음과 같이 joinToString 함수를 호출할 수 있다.


import strings.StringFunctions;

StringFunctions.joinToString(list, ", ", "", "");​

 

최상위 프로퍼티

함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다. 어떤 데이터를 클래스 밖에 위치시켜야 하는 경우는 흔하지는 않지만, 그래도 가끔 유용할 때가 있다.

var opCount = 0

fun performOperation() {
    opCount++
    // ...
}

이런 프로퍼티의 값은 정적 필드에 저장된다.

 

최상위 프로퍼티를 활용해 코드에 상수를 추가할 수 있다.

val UNIX_LINE_SEPARATOR = "\n"

기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메소드를 통해 자바 코드에 노출된다(val의 경우 getter, var의 경우 getter&setter). 겉으론 상수처럼 보이는데, 실제로는 getter/setter를 사용해야 한다면 자연스럽지 못하다. 더 자연스럽게 사용하려면 이 상수를 public static final 필드로 컴파일해야 한다. const 변경자를 추가하면 프로퍼티를 public static final 필드로 컴파일하게 만들 수 있다(단, 원시 타입과 String 타입의 프로퍼티만 const로 지정할 수 있다).

const val UNIX_LINE_SEPARATOR = "\n"

이 코드는 다음 자바 코드와 동등한 바이트코드를 만들어낸다.

public static final String UNIX_LINE_SEPARATOR = "\n";

 

댓글