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

Clo*_*tly 5 scala

我正在尝试包装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))
}
Run Code Online (Sandbox Code Playgroud)

但是我收到以下错误:

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))
Run Code Online (Sandbox Code Playgroud)

原始的java方法:

public Observable<T> timeout(long timeout, TimeUnit timeUnit, Observable<? extends T> other) {
   return create(OperationTimeout.timeout(this, timeout, timeUnit, other));
}
Run Code Online (Sandbox Code Playgroud)

我不是很熟悉,没有Java和斯卡拉(和所有类型的边界),但据我的理解:无论otherJava以及thisJava类型为rx.Observable[U],那么他们为什么不排队?

gou*_*ama 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))
}
Run Code Online (Sandbox Code Playgroud)

要理解为什么这行不通,让我们A在声明中调用未命名类型thisJava(A <: U例如thisJavaa rx.Observable[A])。的timeout方法thisJava: rx.Observable[A]需要一个类型为 的参数rx.Observable[_ <: A],而你给它一个类型为 的参数rx.Observable[_ <: U]:编译器无法知道这两种类型是如何相关的。他们可能根本没有关系!

另一方面,如果Awere U,那么thisJava将是 a rx.Observable[U],并且它的timeout方法将期望 a 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))
}
Run Code Online (Sandbox Code Playgroud)

在完美的世界中,上述内容是可行的。然而,javarx.Observable没有定义为协变,因为 java 中没有定义站点方差注释。所以Scala认为它是不变的。

因此,就 Scala 而言, arx.Observable[_ <: U]不是a ,并且遗憾地返回a 。rx.Observable[U]this.asJavaObservablerx.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))
}
Run Code Online (Sandbox Code Playgroud)

这个故事的寓意是,混合 Scala 的方差和 Java 的方差总是会让你到处付出一些代价,这一点必须仔细考虑。

另外,用asJavaObservablereturn arx.Observable[T]代替_ <: T会让这一切变得更容易,但也许有充分的理由为什么情况并非如此......

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