본문 바로가기

코틀린

코틀린(Kotlin) - 함수형 인터페이스 활용

 

코틀린의 람다식을 기존 자바 API에도 사용할 수 있으며, 호환이 가능하기 때문에 이러한 코틀린 람다를 자바 API에 활용하는 방법에 대해서 살펴보겠습니다.

 

우리는 자바 8 이전까지는 클릭 이벤트 처리를 위해서 아래와 같이 setOnClickListener 메소드에 파라미터로 무명 클래스 인스턴스를 넘겨야만 했습니다.

 

button.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View view) {
                
     }
});

 

코틀린에서는 무명 클래스 인스턴스 대신 람다를 넘길 수 있습니다.

물론, 자바에서 처럼 무명 클래스를 사용해도 됩니다. (코틀린의 object 를 사용하면 되겠죠?)

 

/* 코틀린 람다 사용 */
button.setOnClickListener { view -> ... }


/* 무명 클래스 인스턴스 object 사용 */
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(view: View) {
        ...
    }
})

 

이런 코드가 작동하는 이유는 OnClickListener에 추상 메소드가 단 하나만 있기 때문입니다.

 

public interface OnClickListener {
    void onClick(View view); // {view -> ...} 대응
}

 

즉, 오직 하나의 추상 메소드를 가진 인터페이스함수형 인터페이스(functional interface) 또는 SAM 인터페이스라고 합니다. (SAM = Single Abstract Method)

 

자바 메소드에 파라미터로 람다 전달하기

함수형 인터페이스를 파라미터로 받는 자바의 메소드에 코틀린 람다를 전달하는 방식을 알아보겠습니다.

 

/* 자바 */
void post(int delay, Runnable run)


/* 코틀린 */
post(1000) { println("람다로 전달") }

 

위에서 Runnable 인스턴스를 람다로 사용하여 전달하면, 컴파일러는 자동으로 람다를 Runnable 인스턴스로 변환해줍니다.

당연히 object를 사용하여 무명 클래스로도 호출 할 수 있습니다.

 

/* 무명 클래스로 명시적 선언 */
post(1000, object : Runnable {
    override fun run() {
        print("object로 명시적 선언")
    }
})

 

하지만 람다와 무명 클래스 사이에는 큰 차이가 있습니다.

 

무명 클래스를 사용 하는 경우에는 메소드를 호출할 때마다 새로운 객체를 계속 생성하여 사용합니다.

반면에 람다는 단 한번만 객체를 생성한 후 재사용하기 때문에 훨씬 효율적입니다.

 

post(1000) { ... } //전역변수로 컴파일되므로 단 하나의 인스턴스만 생성됩니다.

 

SAM 생성자 : 람다를 함수형 인터페이스로 명시적 변경 

SAM 생성자는 람다를 함수형 인터페이스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수입니다.

컴파일러가 자동으로 람다를 함수형 인터페이스로 바꾸지 못하는 경우에는 직접 SAM 생성자를 사용할 수 있습니다.

 

/* SAM 생성자를 사용해 값 반환하기 */
fun createAllDoneRunnable(): Runnable {
    return Runnable { println("Done!") }
}

>>> createAllDoneRunnable().run()

Done!

 

createAllDoneRunnable 는 Runnable 객체 자체를 반환하는 메소드입니다.

 

SAM 생성자의 이름은 사용하려는 함수형 인터페이스의 이름과 같습니다.

 

함수형 인터페이스의 유일한 추상 메소드(위의 예제의 경우 run) 의 본문에 사용할 람다만을 인자로 받아서 인스턴스를 반환합니다.

 

람다로 생성한 함수형 인터페이스를 변수에 저장해야 하는 경우에도 SAM 생성자를 사용할 수 있습니다.

예를 들어, 여러 버튼에 같은 클릭 리스너를 적용하고 싶다면 아래처럼 SAM 생성자를 통해 람다를 함수형 인터페이스로 만들어서 변수에 저장해 활용할 수 있습니다.

 

/* SAM 생성자를 사용해 리스너 재사용 하기 */
val listener = View.OnClickListener { view ->
    val text = when (view.id) {
        R.id.button1 -> "First Button"
        R.id.button2 -> "Second Button"
        else -> "Unknown Button"
    }
    Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}

button1,setOnClickListener(listener)
button2,setOnClickListener(listener)

 

단! 람다식의 this는 자기 자신을 가리키지 않기 때문에, 자기 자신을 컨트롤해야 하는 경우에는 람다식이 아닌 무명 클래스를 사용해서 구현해야 합니다.