考虑以下层次结构:
class C1
class C2 extends C1
class C3 extends C2
class C4 extends C3
我想编写一个只接受C2
和C3
类型的函数。为此我想到了以下内容:
def f [C >: C3 <: C2](c :C) = 0
我期待以下行为
f(new C1) //doesn't compile, ok
f(new C2) //compiles, ok
f(new C3) //compiles, ok
f(new C4) // !!! Compiles, and it shouldn't
问题是用C4
调用它时,我不想允许,但编译器接受。我了解C4 <: C2
是正确的,C4
可以被视为C3
。但是在指定绑定[C >: C3 <: C2]
时,我希望编译器找到同时尊重两个边界的C
,而不是一个接一个。
问题是:有没有办法实现我想要的,如果没有,编译器是否试图避免与此有些不一致?
编辑:从答案中我意识到我的推定是错误的。 C4
始终满足C >: C3
,因此两个边界确实得到尊重。我的用例的方法是C3 <:< C
。
答案 0 :(得分:11)
静态地,是的。施加这种约束非常简单:
def f[C <: C2](c: C)(implicit ev: C3 <:< C) = 0
f(new C4)
现在不会编译。
问题是,在编译时可能无法禁止以下行为:
val c: C3 = new C4
f(c)
此处变量c
具有静态类型C3
,它通过编译器传递任何类型的类型检查,但它在运行时实际上是C4
。
在运行时,您当然可以使用反射或多态检查类型并抛出错误或返回Failure(...)
或None
答案 1 :(得分:5)
我发现another stackoverflow question的解释非常有用:
S&gt;:T只是意味着如果传入的类型S等于T或其父类,则使用S。如果你将一个sublevel类型传递给T,那么将使用T.
所以在你的例子中所有,但首先应该编译。 以下示例说明了其含义: 让我们重新定义f:
def f[U >: C3 <: C2](c: U) = c
然后:
val a2 = f(new C2)
val a3 = f(new C3)
val a4 = f(new C4)
List[C2](a2, a3, a4) //compiles
List[C3](a3, a4) //compiles
List[C4](a4) //does not cause a4 is C3
希望有所帮助。