(Kotlin) 함수형 인터페이스(SAM)에 람다 사용하기
주로 리스너 계열이 SAM으로 되어 있기 때문에, 리스너에 많이 사용한다. 자바 8 이전에는 무명 객체를 사용했다.
1
2
3
4
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) { . . . }
});
코틀린(또는 자바8)에서는 SAM에 람다를 사용할 수 있다.
1
button.setOnClickLIstener({ v -> . . . })
이런 코드가 동작하는 이유는 단일 추상 메소드만 가지고 있기 때문에 람다가 어느 메소드를 대체해야 할지를 찾을 수 있기 때문이다.
Note ) 코틀린 함수를 호출 할 때는 람다를 넘겨도 코틀린 컴파일러가 람다를 무명 클래스 객체로 변환하지 않는다. 코틀린에는 제대로 된 함수 타입이 존재하기 때문에 함수 타입을 인자로 넘길 수 있기 때문이다.
코틀린은 따로 옵션 안주면 Java 6 대응이므로 SAM에 넘기는 람다의 경우 무명 객체로 변환되기는 하지만, 코틀린에서 명시적으로 객체 식을 이용해 무명 객체를 생성해 넘기는 것과 람다를 넘기는 것은 다르다.
1
2
3
4
// 함수가 호출될 때 마다 새로운 객체를 생성
postponeComputation(1000, object : Runnable {
override fun run() { println(42) }
});
1
2
// 전역 무명 객체를 하나 만들고, 호출 시마다 같은 객체를 사용.
postponeComputation(1000, { println(42) })
단, 람다가 주변 변수를 포획한다면 매 호출마다 같은 인스턴스를 사용할 수 없기 때문에, 매번 변수를 포획한 새로운 인스턴스를 생성한다. * 향후 무명 객체를 만들거나 별도의 클래스를 만들지 않고 자바 8의 람다 기능을 활용할 예정이라고 한다.
람다가 무명 객체로 변환되기는 하지만, 컴파일러 입장에서는 그냥 코드 블록일 뿐이므로 람다 안에서 this
를 써도 변환된 자기 자신 무명 객체를 가리키는게 아니라, 그 람다를 둘러싼 인스턴스를 가리킨다. 그래서 이벤트 리스너가 자기 자신의 리스너 등록을 해제해야 하는 경우 람다를 사용하면 안되고, SAM 생성자를 사용해야 한다.
SAM 생성자
람다를 함수형 인터페이스의 인스턴스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수로, SAM 생성자의 이름은 사용하려는 함수형 인터페이스의 이름과 같다.
함수형 인터페이스의 인스턴스를 리턴하고 싶은 경우, 람다를 그냥 리턴하게 되면 인터페이스에 대한 정보가 없기 때문에 SAM 생성자를 사용해야 한다. ( 람다를 인자로 넘길 때는 수신 함수 측 인수 타입을 보고 인터페이스를 추측하지만, 리턴 시에는 그런 정보가 없다. )
함수형 인터페이스의 인스턴스를 변수에 저장해놓고 재사용 하고 싶은 경우 에도 사용할 수 있다. 람다를 저장해놓는 패턴과 비슷해보인다. 람다에 타입을 지정해준다는 느낌으로 받아들여도 좋으나, 원래의 의미를 생각해 보면 람다가 들어갈 수 있는 것은 SAM 인스턴스로 자동 변환해주기 때문이다. 이건 SAM 인스턴스를 직접 만들어 두는 방법.
1
2
3
4
5
6
7
8
9
10
11
12
13
val listener = OnClickListener { view ->
val text = when (view.id) {
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
* SAM 생성자를 사용하면 무명 객체가 생성되므로 this
가 자기 자신 무명 객체를 가리킨다.