我对通配符边界的规则感到困惑。似乎有时可以声明一个方法参数,其边界不满足类声明的边界。在下面的代码中,方法foo(...)编译很好,但bar(...)没有编译。我不明白为什么要允许这两个人。
public class TestSomething {
private static class A<T extends String> {}
public static void foo(A<? extends Comparable<?>> a) {
}
public static void bar(A<? extends Comparable<Double>> a) {
}
}
答案 0 :(得分:6)
我们首先考虑方法@class MySwiftClass
。 void foo(A<? extends Comparable<?>> a)
与A<? extends Comparable<?>>
“兼容”,因为存在通配符类型A<T extends String>
和可比较的通配符类型P
以满足以下条件:
Q
由于P <: Comparable<Q> && P <: String
,String <: Comparable<String>
必须为Q
,String
可能是P
的任何子类型(因为声明了字符串{{1} },你的选择是有限的)
现在让我们考虑方法String
。没有可以满足
final
void bar(A<? extends Comparable<Double>> a)
因为String已经实现了P
而不是P <: Comparable<Double> && P <: String
,Comparable<String>
的任何子类都无法实现Comparable<Double>
。
仅仅因为你写了一个签名String
并不意味着你可以传递任何Comparable<Double>
方法。您可以更改声明以接受任何A<? extends Comparable<?>> a
并且它也将编译,但您只能实例化A<? extends Comparable<?>>
,因此它不是必须使用String或其子类的漏洞。
有趣的是,我的Eclipse IDE甚至没有在上面声明的bar中找到编译错误,但是如果bar接受A<? extends Object>
就行了。
有关完整的理解,请参阅Java规范的this part。
如果满足下列条件之一,则可以证明两个类型参数是不同的:
类型参数T1被认为包含另一个类型参数T2,写为T2&lt; = T1,如果由T2表示的类型集合可证明是由T1表示的类型集合的子集,在反射和传递闭包下以下规则(其中&lt;:表示子类型(§4.10)):
A<T extends String>
答案 1 :(得分:2)
这是一个参数化类型何时“格式良好”的问题,即允许哪种类型的参数。关于这个主题的JLS写得不是很好,而且编译器正在按规范做事。 以下是我的理解。 (根据JLS8,oracle javac 8)
一般来说,我们讨论泛型类/接口声明G<T extends B1>
;作为一个例子
class Foo<T extends Number> { .. }
泛型声明可以看作是一组具体类型的声明;例如Foo
声明类型Foo<Number>, Foo<Integer>, Foo<Float>, ...
具体类型G<X>
(其中X是一种类型)格式正确,如果X<:B1
,即X
是B1
的子类型。
Foo<Integer>
格式正确,因为Integer<:Number
。 Foo<String>
格式不正确;它在类型系统中不存在。严格执行此约束,例如,这不会编译
<T> void m1(Foo<T> foo) // Error, it's not the case that T<:Number
给定G<? super B2>
类型,我们会期望B2<:B1
。这是因为我们通常需要在其上应用捕获转换,从而产生G<X> where B2<:X<:B1
,暗示B2<:B1
。如果B2<:B1
为假,我们会在类型系统中引入矛盾,导致眩晕行为。
事实上,Foo<? super String>
被javac拒绝,这很好,因为该类型显然是程序员的错误。
有趣的是,我们在JLS中找不到这种约束;或者至少在JLS中没有明确说明。实验表明javac
并不总是强制执行此约束,例如
<T> Foo<? super T> m2() // compiles, even though T<:Number is false
<String>m2(); // compiles! returns Foo<? super String> !
目前还不清楚他们为何被允许。我不知道这会在实践中引起任何问题。
给定G<? extends B2>
,捕获转化产生G<X> where X<:B1&B2
。
问题是当交叉点类型B1&B2
格式正确时。最自由的方法是允许任何交叉点;即使交集为空,即B1&B2
等同于null类型,它也不会在类型系统中引起问题。
但实际上,我们希望编译器拒绝由Number&String
引入的Foo<? extends String>
之类的内容,因为它很可能是程序员的错误。
更具体的原因是javac
需要构建一个“名义类”,它是B1&B2
的子类型,因此javac可以推断出可以在类型上调用哪些方法。为此,Number&String
,Number&Integer
,Number&Object
等都不允许Number&Runnable
。此部分在JLS#4.9
String & Comparable<Double>
是不允许的,因为名义类会同时实现Comparable<String>
和Comparable<Double>
,这在Java中是非法的。
B1
和B2
可以有多种形式,导致更复杂的情况。这是规范没有经过深思熟虑的地方。例如,从规范文本中不清楚,如果其中一个是类型变量,该怎么办; javac
的行为对我们来说似乎是合理的
<T extends Runnable> Foo<? extends T> m3() // error
<T extends Object > Foo<? extends T> m4() // error
<T extends Number > Foo<? extends T> m5() // ok
<T extends Integer > Foo<? extends T> m6() // ok
另一个例子,应该允许Number & Callable<?>
吗?如果是,那么概念类的超级接口应该是什么?请记住,Callable<?>
不能是超级界面
class Bar extends Number implements Callable<?> // illegal
在更复杂的case中,我们有类似Foo<Number> & Foo<CAP#1>
的内容,其中CAP#1
是捕获转换引入的类型变量。该规范明确禁止它,但用例表明它应该是合法的。
javac
比JLS更自由地处理这些案件。请参阅Maurizio和Dan
那么,作为一名程序员,我们该怎么做呢? - 遵循你的直觉并构建对你有意义的类型。最有可能javac
会接受它。如果没有,那可能是你的错误。在极少数情况下,类型是有道理的,但spec / javac不允许它;你运气不好:)你必须找到解决方法。