Kotlin – Functions in Java

Functions in Java

어느 날, 코틀린으로 구성된 유틸 클래스에 아래와 같은 메소드를 작성했다.

/**
 * run code inside of Background Thread after given delay
 *
 * @param[delayMillis] delay in ms
 * @param[action] code to execute
 */
fun runDelayed(delayMillis: Long, action: () -> Unit) = Handler().postDelayed(Runnable(action), delayMillis)

이걸 코틀린 액티비티에서 사용할려고 할때, 어떻게 하면 될까.

간단하다.

runDelayed(delayMillis = 1000, action = {
            
})

그럼.. 자바에서는 어떻게 해야할까. 내키는대로 써보자.

Function0<T> 라는 제너릭 클래스인 것 같다.

Utils.runDelayed(1000, new Function0<Unit>() {
            @Override
            public Unit invoke() {
                getLastestRelease();
                return null;
            }
 });

즉, 이렇게 된다. 안에 행동을 넣고 돌리면? 강제종료된다. 코틀린 코드에서 action 변수가 Nullable가 아닌데 null을 넘기니 NullPointerException가 발생하는 것이다.

해결방법은 Unit 클래스를 넘겨주면 된다.

Utils.runDelayed(1000, new Function0<Unit>() {
            @Override
            public Unit invoke() {
                getLastestRelease();
                return Unit.INSTANCE;
            }
});

참고로 유닛 클래스는 아래와 같다.

package kotlin

/**
 * The type with only one value: the Unit object. This type corresponds to the `void` type in Java.
 */
public object Unit {
    override fun toString() = "kotlin.Unit"
}

자바의 Void에 해당한다고 작성되어있다.

제법 간단한 해결방법 이었지만, 처음에 적잖이 당황한것도 사실이다;-;

Kotlin – SAM Conversions

SAM Conversions

안드로이드 앱의 주 개발 언어를 Java에서 Kotlin으로 바꾸려 할때, 가장 헷갈리는 부분이 리스너(listener) 부분이라고 생각한다.

가령 버튼을 클릭한다고 가정해보자.

btnReboot.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

    }
});

이런 식이거나, retrolambda를 사용한다면

btnReboot.setOnClickListener(v -> {

});

일 것이다.

문제는 아직까지 (2017년 6월) SDK들은 Java로 제공되기 때문에 코틀린에서 자바를 호출해야 하는데, 다행히 Kotlin은 Java 8처럼 SAM Conversions 란 기능을 제공한다. 간단히 설명하자면 인터페이스의 매개변수 유형이 코틀린에서의 인터페이스 유형과 일치할 때, Java 인터페이스의 구현으로 자동 변환해주는 것이다.

btnReboot.setOnClickListener {

}

이런 식으로 구현이 가능하다.

다만 SAM Conversions은 자바 코드에서만 작동한다는 것이다. 코틀린에선 () -> Unit 등의 적절한 함수 유형이 있어 불필요하다고 판단된다고  코틀린 문서에선 말한다.

문제는 개발하다 보면 코틀린으로 작성한 코드라고 해도 자바에서도 실행될 수 있게도 해야될 때가 발생한다는 것이다. 즉 함수 유형만 믿고 지원하기엔 역부족이라고 생각한다.

예를 들어 인터페이스 하나를 코틀린으로 구현해보자.

companion object {
    interface OnResultListener {
        fun onResult(resultCode: Int, list: ArrayList<String>)
    }
}

이 인터페이스를 코틀린으로 짜여진 액티비티에서 쓰기 위해서는

class Callback : CustomClass.OnResultListener {
   override fun onResult(resultCode: Int, list: ArrayList<String>) {

   }
}

btnSubmit.setOnClickListener {
   customClass.checkVaild(Callback())
}

이런 식으로 클래스를 만들어 구현하거나,

customClass.checkVaild(object : CustomClass.OnResultListener {
    override fun onResult(resultCode: Int, list: ArrayList<String>) {
                    
    }
})

이렇게 하는 방법이 있다. 두번째 방법이 가장 자바와 비슷하지만 복잡한 감을 지울 수 없다.

그나마 액티비티 단에서 코드를 줄이려면, 이런 방식도 있다.

fun setOnResultListener(callback: (Int, ArrayList<String>) -> Unit) {
        object : OnResultListener {
            override fun onResult(resultCode: Int, list: ArrayList<String>) {
                callback(resultCode, list)
            }
        }
}
customClass.setOnResultListener { i, arrayList ->

}

만약 OnResultListener의 onResult가 하나의 파라미터만 제공한다면,

customClass.setOnResultListener {

}

이런식으로 최종적으로 축약이 가능하다.

다만 이 방식은 어디까지나 가능한 방법으로 복잡하고, 오히려 코드 자체는 더 길어진다. 커스텀 뷰 등에 사용하면 활용할 수 있지만 억지 느낌을 지울 수 없다는게 흠이긴 하다.

앞으로 더 간편한 해결법이 나오길 기대하지만 아직 신경 쓸 부분이 많은 것 같다.

— 추가 2016.06.26..

인터페이스를 유지하는 것 보다 간단한 인터페이스라면 오히려 functions 으로 바꾸는 것이 더 좋다고 판단하게 되어 후속 글을 작성하게 되었습니다.

Java to Kotlin – Improve Interface to functions