함수형 인터페이스와 SAM 변환
함수형 인터페이스와 SAM 변환
코틀린의 함수형 인터페이스(functional interface)는 SAM(Single Abstract Method) 인터페이스라고도 불리며, 추상 메서드가 정확히 하나만 존재하는 인터페이스를 람다 표현식으로 인스턴스화할 수 있게 해 줍니다. 이 메커니즘 덕분에 콜백, 리스너, 전략 패턴 등을 정의할 때 보일러플레이트 코드를 크게 줄일 수 있습니다. 또한 SAM 변환(SAM conversion)은 코틀린 코드에서 단일 메서드 인터페이스를 기대하는 Java API에 람다를 직접 전달할 수 있도록 연결해 주는 다리 역할도 합니다. 면접에서도 자주 등장하는 주제이므로 핵심 원리를 정확히 이해해 두시는 것이 좋습니다.
이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- 코틀린에서
fun interface선언을 사용하여 함수형 인터페이스를 정의하는 방법 - SAM 변환이 람다를 인터페이스 구현체로 변환하는 과정
- 코틀린
fun interface와 추상 메서드가 하나인 일반 인터페이스의 차이점 - Java 인터페이스를 코틀린에서 사용할 때 SAM 변환이 적용되는 조건
- SAM 변환과 명시적
object표현식 간의 바이트코드 수준 차이점
함수형 인터페이스 선언하기
코틀린에서 함수형 인터페이스는 fun interface 수정자를 사용하여 선언합니다. 이 키워드를 붙이면 컴파일러에게 해당 인터페이스가 SAM 변환 대상임을 알려주게 됩니다. 함수형 인터페이스에는 추상 메서드가 정확히 하나만 있어야 하지만, 디폴트 메서드나 프로퍼티 같은 비추상 멤버는 얼마든지 포함할 수 있습니다.
fun interface Transformer<T, R> {
fun transform(value: T): R
}
// 람다로 간결하게 인스턴스 생성 (SAM 변환)
val intToString = Transformer<Int, String> { value ->
"Number: $value"
}
println(intToString.transform(42)) // 출력: Number: 42
만약 fun 수정자가 없다면 동일한 인터페이스라 하더라도 명시적인 object 표현식을 사용해야만 인스턴스를 생성할 수 있습니다. 즉, fun 키워드가 컴파일러에게 "람다로 이 인터페이스를 구현해도 된다"는 신호를 보내는 핵심 역할을 합니다.
SAM 변환 동작 원리
컴파일러는 함수형 인터페이스 타입에 람다가 할당되는 것을 감지하면, 해당 인터페이스를 구현하는 익명 클래스를 생성하고 추상 메서드의 구현을 람다 본문에 위임합니다. 바로 이 과정이 SAM 변환이며, 컴파일 타임에 이루어집니다. 생성된 클래스는 바이트코드에서 확인할 수 있습니다.
fun interface ClickHandler {
fun onClick(id: Int)
}
fun registerHandler(handler: ClickHandler) {
handler.onClick(1)
}
// SAM 변환: 람다가 ClickHandler 인스턴스로 변환됨
registerHandler { id ->
println("Clicked item $id")
}
위 코드에서 컴파일러는 대략 다음과 동일한 코드를 생성합니다.
registerHandler(object : ClickHandler {
override fun onClick(id: Int) {
println("Clicked item $id")
}
})