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)
}
}
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()
)
@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.