我正在尝试包装rxjava的timeout
方法,使其可用于scala。
类似于我尝试过的许多其他方法:
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = {
val otherJava: rx.Observable[_ <: U] = other.asJavaObservable
val thisJava: rx.Observable[_ <: U] = this.asJavaObservable
toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava))
}
但我得到了以下错误:
Observable.scala:1631: error: overloaded method value timeout with alternatives:
($1: Long,x$2: java.util.concurrent.TimeUnit,x$3: rx.Scheduler)rx.Observable[_$85] <and>
($1: Long,x$2: java.util.concurrent.TimeUnit,x$3: rx.Observable[_ <: _$85])rx.Observable[_$85]
cannot be applied to (Long, scala.concurrent.duration.TimeUnit, rx.Observable[_$84])
toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava))
最初的java方法:
public Observable<T> timeout(long timeout, TimeUnit timeUnit, Observable<? extends T> other) {
return create(OperationTimeout.timeout(this, timeout, timeUnit, other));
}
我对Java和Scala都不太熟悉(以及所有的类型边界),但据我所知:otherJava
和thisJava
都属于rx.Observable[U]
类型,为什么它们不排成一行呢?
哼,您正着手解决Scala中使用的Java泛型的差异问题。让我们一步一步走。
让我们看看您的实现:
// does not compile (with your original error)
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = {
val otherJava: rx.Observable[_ <: U] = other.asJavaObservable
val thisJava: rx.Observable[_ <: U] = this.asJavaObservable
toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava))
}
为了理解为什么这不起作用,让我们将A
调用为thisJava
声明中的未命名类型(A <: U
,使得thisJava
是rx.Observable[A]
)。thisJava: rx.Observable[A]
的timeout
方法需要一个类型为rx.Observable[_ <: A]
的参数,而您给它一个类型rx.Observable[_ <: U]
的参数:编译器无法知道这两种类型是如何关联的。他们可能根本没有关系!
另一方面,如果A
是U
,那么thisJava
将是rx.Observable[U]
,并且其timeout
方法将期望rx.Observable[_ <: U]
,恰好是otherJava
的类型。让我们试试:
// still does not compile, sadly
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = {
val otherJava: rx.Observable[_ <: U] = other.asJavaObservable
val thisJava: rx.Observable[U] = this.asJavaObservable // variance error
toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava))
}
在一个完美的世界里,以上都是可行的然而,javarx.Observable
没有被定义为协变,因为java中没有定义站点方差注释。所以Scala认为它是不变的。
因此,就Scala而言,rx.Observable[_ <: U]
是而不是rx.Observable[U]
,不幸的是,this.asJavaObservable
返回rx.Observable[_ <: U]
。
但我们知道[*]rx.Observable<T>
应该是协变的,所以我们可以盲目地丢弃:
// this compiles and *should* work
def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = {
val otherJava: rx.Observable[_ <: U] = other.asJavaObservable
val thisJava = this.asJavaObservable.asInstanceOf[rx.Observable[U]]
toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava))
}
这个故事的寓意是,混合Scala的差异和Java的差异总是会让你在这里和那里付出一些代价,这必须仔细考虑。
此外,让asJavaObservable
返回rx.Observable[T]
而不是_ <: T
会让这一切变得更容易,但也许有充分的理由解释为什么不是这样。。。
[*]更像"但我怀疑">
[这应该进入@gourlysama的回答的评论中,但我没有足够的声誉来评论]
@Aralo只有当编译器知道MyType
是协变的时,语句"MyType[_ <: T]
与MyType[T]
相同"才成立。List
是这样的,因为它被定义为List[+A]
,但rx.Observable
不是这样,因为它是Java类型,所以它的类型参数不能有方差注释,所以编译器不能知道它是协变的。
@让asJavaObservable
返回rx.Observable[T]
而不是_ <: T
不是解决方案,因为类型rx.lang.scala.Observable[T]
的意思是"T
或T
的某个子类型的可观测对象",并且这种描述与类型rx.Observable[_ <: T]
(与rx.Observable<? extends T>
相同)完全对应。
我们必须在Scala中进行强制转换的原因是timeout
在Java中的签名是"错误的":严格来说,它使Java Observable保持不变,因为T
出现在相反的位置。"正确"的方法是使用另一个类型参数U
,它的下界是T
,就像在Scala中一样,但Java不支持下界,所以最好的解决方案是保留"错误"的解决方案。reduce
(请参阅此注释)、onErrorReturn
和其他一些运算符也会出现此问题。
通常,所有这些应该有下界类型参数(但没有)的运算符只能在Observable<T>
上使用,而不能在Observable<? extends T>
上使用(这对Java用户来说是一个非常严重的不便),因此,它们需要在Scala适配器中进行强制转换。