combineLatest/zip array overload to be typesafe
dmstocking opened this issue · comments
I wanted to use the version of combineLatest
that takes an array/iterable. The only problem is that it gives you a Array<Any>
which wasn't very useful. I thought it was kind of weird that RxJava doesn't have a version that gives you Array<T>
. Was this because of the Java compiler couldn't figure out the two overloads? Anyway, would we want to provide a version in this project that correctly has the type T
for the array? Maybe there is a good reason not to, but I found it really useful for what I was working on.
I made one myself by using both Kotlin and Java together to do what I think is the most efficient version while still being useful.
inline fun <reified T: Any,R> Observables.combineLatest(observables: Array<Observable<T>>,
crossinline combiner: (Array<T>) -> R)
: Observable<R> =
Observable.combineLatest(
observables,
{ items -> combiner.invoke(ArrayUtils.cast(items, arrayOfNulls<T>(items.size))) },
Observable.bufferSize())
@NotNull
public static <T> T[] cast(@NotNull Object[] items,
@NotNull T[] ts) {
return Arrays.asList(items).toArray(ts);
}
This uses Kotlin to actually create the array we are copying to. We can do this because the type is reified. The java then uses the Arrays.asList
to create a simple list wrapper for the array and then copies it into the array we made. So we make one copy of the array and a list wrapper for the array.
In practice this will be the exact same thing as the generic parameter will be widened to Any
. For example, combineLatest(listOf(just(1), just("hey")), values -> /* it's Array<Any> here */)
. Reification doesn't offer anything here.
It is explained in the JavaDoc:
Note on method signature: since Java doesn't allow creating a generic array with
new T[]
, the implementation of this operator has to create anObject[]
instead. Unfortunately, aFunction<Integer[], R>
passed to the method would trigger aClassCastException
.
As the apply
method first tries to cast the Object[]
into Integer[]
which is not possible. I'd guess Kotlin has the same restrictions.
Man you guys are fast lol.
@JakeWharton I will admit it doesn't help when T is widened. My specific use case was using it like this. (I changed the name of some types)
combineLatest(viewModelRequests) { requests ->
val loading = request.any { it is Request.Loading }
val items = request.mapNotNull { it.successDataOrNull() }
.flatmap{ it } // the type of "it" is List<ListItemViewModel>
return@combineLatest ViewModel(loading, items)
}
I have an android recycler view that is populated from many sources and I combine them all as a list. It was easier then having the overload that took five observables. I basically had a list that I would unpack for combineLatest then immediately repack it back into a list.
@akarnokd Kotlin does have the same restrictions for non inline methods. For inline methods, you can make the type reified
this is basically like the compiler automatically adding a parameter Class<T> clazz
to your method. This let me make the array, new T[]
, in Kotlin. This is how I sort of cheated around it in Kotlin. I then pass it to Java to do the array copy. I could see that the RxJava project didn't want to add a overload that used Class<T> clazz
if it wasn't going to be used very often.
This does the trick:
inline fun <reified T> Iterable<Single<T>>.zip(): Single<List<T>> {
return Single.zip(this) {
it.filterIsInstance<T>()
}
}