SAM은 Single Abstract Method의 줄임말로 추상 메소드 한개를 가진 인터페이스를 뜻한다.
SAM의 대표인 setOnClickListener의 구현을 보자.
setOnClickListener는 인자로 OnClickListener를 받고있다. OnClickListener에 들어가보면
추상 메소드 onClick() 한개만 가지고 있다.
자바 8 이전의 자바에서는 setOnClickListener() 메소드에게 인자로 넘기기 위해 무명 클래스의 인스턴스를 만들어야만 했다.
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
...
}
}
코틀린에서는 무명 클래스 인스턴스 대신 람다를 넘길 수 있다.
button.setOnClickListener{view -> ...}
이러한 코드가 가능한 이유는 OnClickListener에 추상메소드가 단 하나 있기 때문이다.
자바에서의 코드와 코틀린에서 코드를 비교해보자.
void postponComputation(int delay, Runnable computation);
코틀린에서는 람다를 이 함수에 넘길 수 있다. 컴파일러가 자동으로 람다를 Runnable 인스턴스로 변환해준다.
postponeComputation(1000){ println(42) }
위의 "Runnable 인스턴스"란 실제로 "Runnable을 구현한 무명 클래스의 인스턴스"라는 뜻이다. 컴파일러는 자동으로 그런 무명클래스와 인스턴스를 만들어준다. 이때 그 무명클래스에 있는 "유일한" 추상 메소드를 구현할 때 람다 본문을 메소드 본문으로 사용한다. 여기서는 Runnable의 run()이 그 추상메소드이다.
물론 object를 이용해 명시적으로도 만들 수 있다.
postponeComputation(1000, object: Runnable {
override fun run(){
println(42)
}
})
그런데 이런식으로 명시적으로 선언할 경우 메소드를 호출할 때마다 새로운 객체가 생성된다. 하지만 람다를 이용하면 프로그램 전체에서 Runnable의 인스턴스는 하나만 만들어진다.
그래서 object 선언을 이용하면서, 람다와 동일하게 이용하고플 땐 아래와 같이 이용해야 한다.
val runnable = Runnable{ println(42) }
fun handleComputation() {
postponeComputation(1000, runnable)
}
그런데 이러한 SAM을 이용할 때 주의사항이 있다. 기존의 JAVA에서 하던 SAM을 코틀린으로 옮겨봤다.
인터페이스에 추상 메소드를 만들고,
클래스에서 구현한 뒤에 사용을 하려고 보니..
오류가 뜨게 된다. 왜그럴까? Kotlin에서는 적절한 함수 유형이 이미 있어 함수를 자동 변환할 필요가 없어 지원하지 않는다고 한다. 그렇다면 항상 JAVA로 코드를 만들어야 할까? 그것은 또 아니다.
기존 코틀린의 interface 앞에 fun을 붙여주게 되면 앞의 자바 코드와 동일하게 실행된다.
fun을 interface 앞에 붙여주니
기존의 클릭 리스너와 동일하게 수행됨을 볼 수 있다.
https://thdev.tech/kotlin/androiddev/2017/10/07/Kotlin-SAM/
https://leejaeho.dev/posts/kotlin-sam/
Dmitry Jemerov 『Kotlin in Action』, 에이콘, p229-231.