Rewrite Java code in Kotlin using Function Referen

2019-05-04 09:56发布

问题:

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.