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

半世苍凉 提交于 2019-12-22 08:35:57

问题


I have an example Java code using method reference that I want to rewrite to Kotlin. Java version is using method reference, solution is short and clear. But on the other hand I cannot use method reference in Kotlin. The only version that I've managed to write is one presented below. It seems like Function3 { s: String, b: Boolean, i: Int -> combine(s, b, i) } could be written in cleaner way (if possible method reference would be perfect).

I'm new to Kotlin so I'll be grateful for any clues.

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
    }
}

EDIT

Following method references in Kotlin gives an error.

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

EDIT 2

RxKotlin solves issue mentioned in answer.

import io.reactivex.rxkotlin.Observables

class TestKotlin {

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

}

回答1:


You can also using Function Reference Expression in Kotlin just like as Method Reference Expression in Java. for example, the combine function reference as KFunction3 in Kotlin as below:

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

In java, Method Reference Expression can assign to any compatible Functional Interface, but you can't assign a Function Reference Expression to any Function types even if they are compatible, for example:

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

Indeed, Kotlin using SAM Conversions to convert lambda/Function Reference Expression into an implementation of Java Functional Interface, which means Kotlin does something like as below:

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();
   }
};

Why did Method Reference Expression can works fine in Java, but using Function Reference Expression is failed in Kotlin?

Base on the above, and you can see there are many overloaded combineLatest methods in rx-java2. for example, the following two can't make Kotlin using Function Reference Expression correctly:

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>
)

As I said Kotlin using SAM Conversions to convert a lambda/Function Reference Expression to a Java Functional Interface, but it can't assign to a Java Functional Interface directly.

The 4th parameter of the combineLatest method in Kotlin, the compiler can't infer its type at all, since the 4th parameter type can be io.reactivex.functions.Function3 or ObservableSource. because ObservableSource is also a Java Functional Interface.

When you meet SAM Conversion conflict like as this, you can using an adapter function that converts a lambda to a specific SAM type, for example:

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}

So you must using an adaption before using lambda/Function Reference Expression, for example:

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) }
}

Then you can using Function Reference Expression as below, for example:

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



回答2:


@holi-java Answer is great, it explains why this problem occurs, here I'm providing a quick fix for those who just want to get rid of the problem:

In rxJava2 use io.reactivex.rxkotlin.Observables instead of io.reactivex.Observable to combine your observables:

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

This should work out of the box.



来源:https://stackoverflow.com/questions/45120698/rewrite-java-code-in-kotlin-using-function-reference-occurs-sam-types-conflict

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!