Skip to content

함수형 (SAM\

하나의 추상 멤버 함수만 있는 인터페이스를 함수형 인터페이스(functional interface) 또는 단일 추상 메서드(Single Abstract Method, SAM) 인터페이스라고 합니다. 함수형 인터페이스는 여러 개의 비추상(non-abstract) 멤버 함수를 가질 수 있지만, 추상 멤버 함수는 오직 하나만 가질 수 있습니다.

Kotlin에서 함수형 인터페이스를 선언하려면 fun 수식어를 사용하세요.

kotlin
fun interface KRunnable {
    fun invoke()
}

SAM 변환

함수형 인터페이스의 경우, 람다 식을 사용하여 코드를 더 간결하고 읽기 좋게 만드는 SAM 변환을 사용할 수 있습니다.

함수형 인터페이스를 수동으로 구현하는 클래스를 만드는 대신, 람다 식을 사용할 수 있습니다. SAM 변환을 사용하면 Kotlin은 인터페이스의 단일 메서드 시그니처와 일치하는 모든 람다 식을, 인터페이스 구현을 동적으로 인스턴스화하는 코드로 변환할 수 있습니다.

예를 들어, 다음과 같은 Kotlin 함수형 인터페이스가 있다고 가정해 보겠습니다.

kotlin
fun interface IntPredicate {
    fun accept(i: Int): Boolean
}

SAM 변환을 사용하지 않는다면 다음과 같이 코드를 작성해야 합니다.

kotlin
// 클래스의 인스턴스 생성
val isEven = object : IntPredicate {
    override fun accept(i: Int): Boolean {
        return i % 2 == 0
    }
}

Kotlin의 SAM 변환을 활용하면 다음과 같은 동등한 코드를 대신 작성할 수 있습니다.

kotlin
// 람다를 사용하여 인스턴스 생성
val isEven = IntPredicate { it % 2 == 0 }

짧은 람다 식이 불필요한 모든 코드를 대체합니다.

kotlin
fun interface IntPredicate {
    fun accept(i: Int): Boolean
}

val isEven = IntPredicate { it % 2 == 0 }

fun main() {
    println("Is 7 even? - ${isEven.accept(7)}")
}

Java 인터페이스에 대한 SAM 변환도 사용할 수 있습니다.

생성자 함수가 있는 인터페이스에서 함수형 인터페이스로 마이그레이션

1.6.20부터 Kotlin은 함수형 인터페이스 생성자에 대한 호출 가능 참조(callable references)를 지원합니다. 이를 통해 생성자 함수가 있는 인터페이스에서 함수형 인터페이스로 소스 호환성을 유지하며 마이그레이션하는 방법이 추가되었습니다. 다음 코드를 살펴보세요.

kotlin
interface Printer { 
    fun print() 
}

fun Printer(block: () -> Unit): Printer = object : Printer {
    override fun print() = block()
}

함수형 인터페이스 생성자에 대한 호출 가능 참조를 사용하면, 이 코드를 단순히 함수형 인터페이스 선언으로 대체할 수 있습니다.

kotlin
fun interface Printer { 
    fun print()
}

생성자는 암시적으로 생성되며, ::Printer 함수 참조를 사용하는 모든 코드가 컴파일됩니다. 예시는 다음과 같습니다.

kotlin
documentsStorage.addPrinter(::Printer)

레거시 함수 PrinterDeprecationLevel.HIDDEN@Deprecated 어노테이션을 추가하여 바이너리 호환성을 유지하세요.

kotlin
@Deprecated(message = "Your message about the deprecation", level = DeprecationLevel.HIDDEN)
fun Printer(...) {...}

함수형 인터페이스 vs. 타입 별칭

위의 내용을 함수형 타입에 대한 타입 별칭(type alias)을 사용하여 간단히 다시 작성할 수도 있습니다.

kotlin
typealias IntPredicate = (i: Int) -> Boolean

val isEven: IntPredicate = { it % 2 == 0 }

fun main() {
    println("Is 7 even? - ${isEven(7)}")
}

하지만 함수형 인터페이스와 타입 별칭은 서로 다른 용도로 사용됩니다. 타입 별칭은 단순히 기존 타입에 대한 이름일 뿐이며 새로운 타입을 생성하지 않지만, 함수형 인터페이스는 새로운 타입을 생성합니다. 특정 함수형 인터페이스에 특화된 확장을 제공하여 일반 함수나 해당 타입 별칭에는 적용되지 않도록 할 수 있습니다.

타입 별칭은 하나의 멤버만 가질 수 있는 반면, 함수형 인터페이스는 여러 개의 비추상 멤버 함수와 하나의 추상 멤버 함수를 가질 수 있습니다. 또한 함수형 인터페이스는 다른 인터페이스를 구현하거나 확장할 수도 있습니다.

함수형 인터페이스는 타입 별칭보다 더 유연하고 더 많은 기능을 제공하지만, 특정 인터페이스로의 변환이 필요할 수 있기 때문에 구문상으로나 런타임 시에 더 많은 비용이 들 수 있습니다. 코드에서 어떤 것을 사용할지 선택할 때 다음 요구 사항을 고려하세요.

  • API가 특정 파라미터 및 반환 타입을 가진 함수(모든 함수)를 받아야 하는 경우 – 단순한 함수형 타입을 사용하거나 타입 별칭을 정의하여 해당 함수형 타입에 더 짧은 이름을 부여하세요.
  • API가 함수보다 더 복잡한 엔티티를 받는 경우 – 예를 들어, 함수형 타입의 시그니처로 표현할 수 없는 복잡한 계약(contract) 및/또는 작업이 있는 경우 – 해당 엔티티를 위해 별도의 함수형 인터페이스를 선언하세요.