使用函数引用在 Kotlin 中重写 Java 代码发生 SAM 类型冲突

Rewrite Java code in Kotlin using Function Reference occurs SAM types conflict

我有一个示例 Java 使用方法引用的代码,我想将其重写为 Kotlin。 Java 版本是使用方法参考,解决方案简短明了。但另一方面,我不能在 Kotlin 中使用方法引用。我设法编写的唯一版本如下所示。似乎 Function3 { s: String, b: Boolean, i: Int -> combine(s, b, i) } 可以用更简洁的方式编写(如果可能的话,方法参考将是完美的)。

我是 Kotlin 的新手,所以如果能提供任何线索,我将不胜感激。

Java

import io.reactivex.Observable;

public class TestJava {

    Observable<String> strings() {
        return Observable.just("test");
    }

    Observable<Boolean> booleans() {
        return Observable.just(true);
    }

    Observable<Integer> integers() {
        return Observable.just(1);
    }

    void test() {
        Observable.combineLatest(strings(), booleans(), integers(),
                this::combine);
    }

    double combine(String s, boolean b, int i) {
        return 1.0;
    }
}

Kotlin

import io.reactivex.Observable
import io.reactivex.functions.Function3

class TestKotlin {

    fun strings(): Observable<String> {
        return Observable.just("test")
    }

    fun booleans(): Observable<Boolean> {
        return Observable.just(true)
    }

    fun integers(): Observable<Int> {
        return Observable.just(1)
    }

    fun test() {
        Observable.combineLatest(strings(), booleans(), integers(),
                Function3 { s: String, b: Boolean, i: Int -> combine(s, b, i) })
    }

    fun combine(s: String, b: Boolean, i: Int): Double {
        return 1.0
    }
}

编辑

Kotlin 中的以下方法引用出错。

fun test() {
    Observable.combineLatest(strings(), booleans(), integers(), this::combine)
}

fun test() {
    Observable.combineLatest(strings(), booleans(), integers(), TestKotlin::combine)
}

None of the following functions can be called with the arguments supplied: @CheckReturnValue @SchedulerSupport public final fun combineLatest(p0: ((Observer) -> Unit)!, p1: ((Observer) -> Unit)!, p2: ((Observer) -> Unit)!, p3: ((???, ???, ???) -> ???)!): Observable<(???..???)>! defined in io.reactivex.Observable @CheckReturnValue @SchedulerSupport public open fun combineLatest(p0: ObservableSource!, p1: ObservableSource!, p2: ObservableSource!, p3: io.reactivex.functions.Function3!): Observable<(???..???)>! defined in io.reactivex.Observable @CheckReturnValue @SchedulerSupport public open fun combineLatest(p0: Function!, out (???..???)>!, p1: Int, vararg p2: ObservableSource!): Observable<(???..???)>! defined in io.reactivex.Observable

编辑 2

RxKotlin 解决了答案中提到的问题。

import io.reactivex.rxkotlin.Observables

class TestKotlin {

    fun test() {
        Observables.combineLatest(strings(), booleans(), integers(), this::combine)
    }

}

您也可以在 Kotlin 中使用 Function Reference Expression,就像 Java 中的方法引用表达式一样。例如,combine 函数在 Kotlin 中引用为 KFunction3,如下所示:

val f: kotlin.reflect.KFunction3<String, Boolean, Int, Double> = this::combine

在 java 中,方法引用表达式可以分配给任何 compatible Functional Interface,但不能将 Function Reference Expression 分配给任何 Function Reference Expression函数类型,即使它们 兼容 ,例如:

val f:io.reactivex.functions.Function3<String,Boolean,Int,Double> =this::combine
//                                                type mismatch error     ---^

确实,Kotlin 使用 SAM Conversions 将 lambda/Function Reference Expression 转换为 Java Functional Interface 的实现,这意味着 Kotlin 做了一些事情如下所示:

fun test() = TODO()

val exector:Executor = TODO()

exector.execute(::test)

//::test compile to java code as below:
Runnable task = new Runnable(){
   public void run(){
      test();
   }
};

为什么方法引用表达式在Java中可以正常工作,但在Kotlin中使用Function Reference Expression却失败了?

根据上面,可以看到中有很多重载的combineLatest方法。例如,以下两个无法使 Kotlin 正确使用 Function Reference Expression

combineLatest(
       ObservableSource<out T1>,
       ObservableSource<out T2>,
       ObservableSource<out T3>, 
       Function3<T1, T2, T3, out R>
)

combineLatest(
       ObservableSource<out T1>,
       ObservableSource<out T2>,
       ObservableSource<out T3>, 
       ObservableSource<out T4>, 
       Function4<T1, T2, T3, T4, out R>
)

正如我所说,Kotlin 使用 SAM Conversions 将 lambda/Function Reference Expression 转换为 Java Functional Interface,但它不能直接分配给 Java Functional Interface

Kotlin中combineLatest方法的第4个参数,编译器根本无法推断出它的类型,因为第4个参数类型可以是io.reactivex.functions.Function3ObservableSource。因为 ObservableSource 也是一个 Java Functional Interface.

当你遇到像这样的SAM Conversion冲突时,你可以使用适配器函数将lambda转换为特定的SAM类型,例如:

typealias RxFunction3<T1, T2, T3, R> = io.reactivex.functions.Function3<T1,T2,T3,R>

val f: RxFunction3<String, Boolean, Int, Double> = RxFunction3{ s, b, i-> 1.0}

因此在使用 lambda/Function Reference Expression 之前必须使用适配,例如:

typealias RxFunction3<T1, T2, T3, R> = io.reactivex.functions.Function3<T1,T2,T3,R>

fun <T1,T2,T3,R> KFunction3<T1,T2,T3,R>.toFunction3(): RxFunction3<T1, T2,T3,R> {
    return RxFunction3 { t1, t2, t3 -> invoke(t1, t2, t3) }
}

然后你可以使用Function Reference Expression如下,例如:

Observable.combineLatest(
        strings(),
        booleans(), 
        integers(), 
        //             v--- convert KFunction3 to RxFunction3 explicitly
        this::combine.toFunction3()
)

@holi-java 答案很好,解释了为什么会出现这个问题,这里我为那些只想摆脱这个问题的人提供一个快速修复:

rxJava2 中使用 io.reactivex.rxkotlin.Observables 而不是 io.reactivex.Observable 来组合您的观察值:

Observables.combineLatest(src1, src2, ::yourFunction)

这应该开箱即用。