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

时间:2014-01-03 20:27:31

标签: scala

我正在尝试包装rxjava的timeout method以使其可用for scala

many other methods类似,我试过这个:

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]类型那么他们为什么不排队?

2 个答案:

答案 0 :(得分:4)

嗯,你正在踩踏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]为{{1} }})。 timeout thisJava: rx.Observable[A]方法需要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))
}

在一个完美的世界里,上述情况会起作用。 但是,java rx.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会使所有这些变得更容易,但也许有充分理由说明原因并非如此......

[*]更像是“但我怀疑”

答案 1 :(得分:3)

[这应该留在@ gourlaysama的答案的评论中,但我没有足够的声誉来评论]

@Aralo语句“MyType[_ <: T]MyType[T]相同”仅在编译器知道 MyType是协变的情况下才成立。这是List的情况,因为它被定义为List[+A],但rx.Observable不是这种情况,因为它是Java类型,因此它的类型参数不能有方差注释,所以编译器无法知道它是否应该是协变的。

@gourlaysama使asJavaObservable返回rx.Observable[T]而非_ <: T不是解决方案,因为类型rx.lang.scala.Observable[T]表示“T或{属于T“子类型的东西,此描述完全对应于类型rx.Observable[_ <: T](与rx.Observable<? extends T>相同)。

我们必须使用Scala进行强制转换的原因是Java中timeout的签名是“错误的”:严格来说,它使Java Observable不变,因为T出现在逆变位置。 “正确”的方法是使用另一个类型参数U,其下限为T,如在Scala中,但Java不支持下限,因此最好的解决方案是保持“错误的“解决方案reduce(请参阅this comment),onErrorReturn和其他一些运算符也会出现此问题。

一般而言,所有这些应该具有较低有界类型参数(但不具有)的运算符只能在Observable<T>上使用,而不能在Observable<? extends T>上使用(非常严重的不便之处) Java用户),因此,他们需要在Scala适配器中进行强制转换。