Scala类型边界和Java通用互操作



我正在尝试包装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都不太熟悉(以及所有的类型边界),但据我所知:otherJavathisJava都属于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,使得thisJavarx.Observable[A])。thisJava: rx.Observable[A]timeout方法需要一个类型为rx.Observable[_ <: A]的参数,而您给它一个类型rx.Observable[_ <: U]的参数:编译器无法知道这两种类型是如何关联的。他们可能根本没有关系!

另一方面,如果AU,那么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]的意思是"TT的某个子类型的可观测对象",并且这种描述与类型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适配器中进行强制转换。

最新更新