본문 바로가기
2023년 이전/kotlin

Kotlin - 함수를 호출하기 쉽게 만들기

by JeongUPark 2020. 2. 19.
반응형

[출처 -  Kotlin In Action] [아래 내용들은 Kotlin In Action을 공부하면서 스스로 정리한 내용입니다] 

이번 장에서는 함수를 호출하기 쉽게 만들기 지만 kotlin에서 함수 활용 방법 알아보는것이 더 맞는 것 같습니다.

 

책 내용에 따르면 

val list = listOf(1, 2, 3)
println(list)

을 실행하면

[1, 2, 3]

이라는 결과가 나오는데 이 결과를 (1; 2; 3)으로 만들려면 자바에서는 Guava나 Apache Commons 같은 서드파티 프로젝트를 추가하거나 직접 로직을 만들어야 합니다. 그런데 코틀린에는 이런 요구 사항을 처리할 수 있는 함수가 표준 라이브러리에 존재한다고 합니다.

 

우선 (1; 2; 3) 를 직접 만드는 함수를 구현하면

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()
}

 

인자로 컬렉션, 구분자, 접두사, 접미사를 받고 결과를 String으로 반환합니다. 이 함수는 제너릭 함수로 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있습니다. 위 함수를 사용하여 다음의 code를 실행하면

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

의도한 결과를 받을 수 있습니다.

 

그런데 위 joinToString 코드를 보면 인자가 4개인데 사실 그냥 위의 println 구문 처럼 쓰면 각 인자가 어떤 역할을 하는지 알 수 가 없습니다.( 인자가 더 많다면 더 헤깔리겠죠?) 함수 원형을 보면서 인자를 작성해야 할 것입니다.

이를 해결 방법은 함수의 인자들을 외우거나 주석을 작성하는 등의 방법이 있겠지만 kotlin에서는 다음과 같이 처리할 수 있습니다.

joinToString(collection = list, separator = "; ",prefix =  "(", postfix = ")")

이렇게 함수에 전달하는 인자 중 일부(또는 전부)의 이름을 명시할 수 있습니다. 명시 할 때 어느 하나라도 이름을 명시하고 나면 그 뒤에 오는 모든 인자는 이름을 꼭 명시해야합니다.  또한 Android Stuido나 InteliJ에서 함수의 인자 이름을 바꾼때 에디터에서 직접 변경하지 말고 Refactor 메뉴의 Rename이나 ChangeSignature 로 변경해야 합니다.

하지만 이 방법은 자바로 작성한 코드(함수)에서는 사용할 수 없습니다.

그리고 AndroidStudio나 InteliJ에서는 

위와 같이 함수 작성시나 완료 후 인자의 이름을 나타내 줍니다. 

 

그리고 kotlin에서는 함수 선언에서 파라미터의 디폴트 값을 지정 할 수 있습니다. 이 디폴트 값을 지정할 수 있는게 왜 필요하냐? 그 이유는 오버로딩 메소드가 많아 질 때 그 오버로딩 일부분을 피할 수 있다는 점입니다.  

예를 들어 

java의 Thread를 보면

Thread()

Thread(Runnable target)
Thread(Runnable target, String name)
Thread(String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Thread(ThreadGroup group, String name)

이렇게 생성자가 많고 또 생성자마다 파라미터 타입들이 중복되거나 일부 파라미터가 생략된 함수를 호출 하면 어떤 함수가 불릴지 애매 모호해집니다.

그럴때 함수에 디폴트값을 지정해두면 위의 문제점을 어느정도 피할 수 있게 됩니다. 그럼 그 활용을 확인해 보겠습니다.

 

fun <T> joinToString(
        collection: Collection<T>,
        separator: String =",",
        prefix: String = "",
        postfix: String = ""
): String {

위와 같이 joinToString 함수의 파라미터에 디폴트 값을 지정하고 다음을 실행해보면

fun main(args: Array<String>) {
    val list = listOf(1, 2, 3)
    println(joinToString(list, "; ", "(", ")"))
    println(joinToString(list))
    println(joinToString(list,"| "))
    println(joinToString(list,prefix = "["))
    println(joinToString(list,prefix = "[", postfix = "]"))
}
(1; 2; 3)
1,2,3
1| 2| 3
[1,2,3
[1,2,3]

결과를 볼 수 있습니다. 딱히 모든 파라미터를 채우지 않거나, 필요한 파라미터만 채워도 함수가 동작을 하게됩니다.(디폴트 지정을 하지 않은 Collection은 반드시 너어주어야 합니다.)

그리고 여기서 알아야할 점은, 함수의 디폴트 파라미터 값은 함수를 호출하는 쪽이 아니라 함수 선언쪽에서 지정된다는 사실입니다. 그래서 디폴트 값을 바꾸고 재 컴파일 하면, 그 함수를 호출하는 코드 중에 값을 지정하지 않은 모든 인자가 자동으로 변경된 디폴트 값으로 적용됩니다.

 

그런데 디폴트 값을 가진 함수를 자바에서 호출 한다면 자바에는 디폴트 값이라는 개념이 없기 떄문에 코틀린 함수가 디폴트값을 제공하더라도 자바에서는 모든 인자를 명시해야 합니다.

 

또한 함수를 최상위로 만들어서 다음과 같이 사용할 수도  있습니다. (최상위로 만든다는 의미는 class 밖에 선언 또는 위치 시킨다는 의미 입니다.)

 

Join.kt라는 이름의 파일을 만들고 다음과 같이 code를 작성합니다.

package strings
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()
}

이 부분을 자바로 보면

pakage strings;
public class Joinkt{
	public static String joinToString(...) {...};
}

이렇게 처리되는 부분입니다. 그래서 위의 Join.kt를 Java나 kotlin에서 사용하려면 우선 pakage를 임포트 하고 사용하면 됩니다. 사용 방법을 code로 보면 다음과 같습니다.

Java

import strings.JoinKt;
import java.util.ArrayList;

public class Test_Join {

    public static void main(String[] args){

        ArrayList<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        System.out.println(JoinKt.joinToString(list,";","[","]"));
    }

}

Kotlin

import strings.joinToString
import java.util.ArrayList

fun main(args: Array<String>) {

    val list = ArrayList<String>()
    list.add("1")
    list.add("2")
    list.add("3")
    println(joinToString(list, ";", "[", "]"))
}

 그리고 결과는

[1;2;3]

를 확인할 수 있습니다.

 

또한 최상위 함수가 포함되는 클래스의 이름을 바꾸어 사용하고 싶다면 파일에 @JvmName 이노테션을 추가하여 사용 하면 됩니다. @JvmName은 pakage 이름 선언 전에 선언해야 합니다. 그래서 활용 방법을 보면 Join.kt 파일을 다음과 같이 수정하고

@file:JvmName("JoinTest")
package strings
fun <T> joinToString(
    collection: Collection<T>,
    separator: String =",",
    prefix: String = "",
    postfix: String = ""
): String {

사용 위치를 가면 JoinKt가 error가 되어 있고 이를 JoinTest로 바꾸면 똑같이 동작하는 것을 확인 할 수 있습니다.

 

뿐만 아니라 프로퍼티도 최상위로 사용할 수 있는데 최상위 프로터티로 상수를 다음과 같이 추가할 경우

val LINE_SEPARATOR = "\n"

Java에서는 JoinTest.getLINE_SEPARATOR();인 getter를 사용하여 접근합니다.(위에서 사용한 Join.kt를 이용하여 테스트를 하였습니다.) 이는 쫌 부자연스러운 느낌이 들기 때문에

const val LINE_SEPARATOR = "\n"

로 작성하면 Java에서는 JoinTest.LINE_SEPARATOR이렇게 접근 할 수 있습니다. 즉 위의 kotlin code를 자바에서 보면

public static final String LINE_SEPARATOR = "\n";

로  볼 수 있습니다.

 

반응형