用“超级”关键字绑定generics
为什么我只能使用super
通配符而不是types参数?
例如,在Collection
接口中,为什么toArray
方法不是这样写的
interface Collection<T>{ <S super T> S[] toArray(S[] a); }
super
绑定一个命名的types参数(例如<S super T>
)而不是通配符(例如<? super T>
)是非法的,因为即使允许,它也不会做你想做的事情,因为由于Object
是所有引用types的终极super
,而且一切都是Object
, 实际上没有界限 。
在你的具体例子中,因为任何引用types的数组都是一个Object[]
(通过Java数组协variables),所以它可以用作<S super T> S[] toArray(S[] a)
绑定是合法的)在编译时,它不会阻止在运行时的ArrayStoreException
。
你试图build议的是:
List<Integer> integerList;
并给出了这个假设 super
绑定到toArray
:
<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java
编译器应该只允许编译以下内容:
integerList.toArray(new Integer[0]) // works fine! integerList.toArray(new Number[0]) // works fine! integerList.toArray(new Object[0]) // works fine!
并没有其他数组types的参数(因为Integer
只有3种typessuper
)。 也就是说,你正试图阻止编译:
integerList.toArray(new String[0]) // trying to prevent this from compiling
因为根据你的论点, String
不是Integer
的super
。 但是 , Object
是Integer
的super
类,而String[]
是Object[]
,所以编译器仍然会让上面的代码编译,即使假设你可以做<S super T>
!
所以下面的代码仍然可以编译 (就像现在这样),并且在运行时ArrayStoreException
不能被任何使用genericstypes边界的编译时检查阻止:
integerList.toArray(new String[0]) // compiles fine! // throws ArrayStoreException at run-time
generics和数组不混合,这是它显示的许多地方之一。
一个非数组的例子
再次说,你有这个generics方法声明:
<T super Integer> void add(T number) // hypothetical! currently illegal in Java
你有这些variables声明:
Integer anInteger Number aNumber Object anObject String aString
你用<T super Integer>
(如果它是合法的)的意图是它应该允许add(anInteger)
和add(aNumber)
,当然也add(anObject)
,但不能add(aString)
。 那么, String
是一个Object
,所以add(aString)
仍然可以编译。
也可以看看
- Java教程/generics
- 分型
- 通配符更有趣
相关问题
关于genericsinput规则:
- 任何简单的方法来解释为什么我不能做的
List<Animal> animals = new ArrayList<Dog>()
? - javagenerics(不)协方差
- 什么是原始types,为什么我们不应该使用它?
- 介绍原始types
List
与List<Object>
不同的List<?>
不同之处
- 介绍原始types
关于使用super
和extends
:
-
Java Generics: What is PECS?
- 从有效的Java第2版 :“生产者
extends
消费者super
”
- 从有效的Java第2版 :“生产者
- Javagenerics中的
super
和extends
什么区别? -
<E extends Number>
和<Number>
之间有什么区别? - 我如何添加到
List<? extends Number>
List<? extends Number>
数据结构? (你不能!)
由于没有人提供满意的答案,正确的答案似乎是“由于Java语言不足”。
polygenelubricants提供了有关java数组协变的坏事情的好概述,这本身就是一个可怕的特性。 考虑下面的代码片段:
String[] strings = new String[1]; Object[] objects = strings; objects[0] = 0;
这显然是错误的代码编译而不诉诸任何“超”构造,所以数组协变不应该被用作一个参数。
现在,在这里我有一个非常有效的代码需要super
命名的types参数的例子:
class Nullable<A> { private A value; // Does not compile!! public <B super A> B withDefault(B defaultValue) { return value == null ? defaultValue : value; } }
可能支持一些不错的用法:
Nullable<Integer> intOrNull = ...; Integer i = intOrNull.withDefault(8); Number n = intOrNull.withDefault(3.5); Object o = intOrNull.withDefault("What's so bad about a String here?");
后面的代码片段不能编译,如果我完全删除B
,所以B
确实是需要的。
请注意,我试图实现的function很容易获得,如果我颠倒types参数声明的顺序,从而更改super
约束extends
。 但是,只有将该方法重写为静态方法才有可能:
// This one actually works and I use it. public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... }
关键是,这种Java语言的限制确实限制了一些可能的有用function,并可能需要丑陋的解决方法。 我想知道如果我们需要withDefault
为虚拟会发生什么。
现在,为了和polygenelubricants所说的相关联,我们在这里使用B
而不是限制作为defaultValue
传递的对象的types(请参阅示例中使用的String),而是限制调用者对我们返回的对象的期望。 作为一个简单的规则,您使用的extends
types是您所需要的types,并且super
您提供的types。
您的问题的“官方”答案可以在Sun / Oracle错误报告中find 。
BT2:评估
看到
http://lampwww.epfl.ch/~odersky/ftp/local-ti.ps
特别是第3节和第9页的最后一段。在子types约束的两侧都接受typesvariables可以产生一组没有单一最佳解的types方程; 因此,types推断不能使用任何现有的标准algorithm来完成。 这就是为什么typesvariables只有“扩展”范围。
另一方面,通配符不必被推断,所以不需要这个约束。
@ ###。### 2004-05-25
是; 关键是通配符,即使被捕获,也只是用作推理过程的input; 没有什么(只)下限需要推断作为结果。
@ ###。### 2004-05-26
我看到了这个问题。 但是我不认为与推理期间通配符的下限有什么不同,例如:
名单<? 超级号码>;
布尔b;
…
s = b? s:s;目前,我们推断List <X>其中X扩展了Object作为条件expression式的types,这意味着该赋值是非法的。
@ ###。### 2004-05-26
可悲的是,谈话结束了。 (现在是死的)链接用来指向的文件是GJ的推断types实例 。 从最后一页看一眼,可以归结为:如果允许下限,则types推断可能产生多种解决scheme,但都不是主要的 。
假设我们有:
-
基本classA> B> C和D
class A{ void methodA(){} }; class B extends A{ void methodB(){} } class C extends B{ void methodC(){} } class D { void methodD(){} }
-
工作包装类
interface Job<T> { void exec(T t); } class JobOnA implements Job<A>{ @Override public void exec(A a) { a.methodA(); } } class JobOnB implements Job<B>{ @Override public void exec(B b) { b.methodB(); } } class JobOnC implements Job<C>{ @Override public void exec(C c) { c.methodC(); } } class JobOnD implements Job<D>{ @Override public void exec(D d) { d.methodD(); } }
-
和一个经理类与4种不同的方法来执行对象的工作
class Manager<T>{ final T t; Manager(T t){ this.t=t; } public void execute1(Job<T> job){ job.exec(t); } public <U> void execute2(Job<U> job){ U u= (U) t; //not safe job.exec(u); } public <U extends T> void execute3(Job<U> job){ U u= (U) t; //not safe job.exec(u); } //desired feature, not compiled for now public <U super T> void execute4(Job<U> job){ U u= (U) t; //safe job.exec(u); } }
-
与使用
void usage(){ B b = new B(); Manager<B> managerB = new Manager<>(b); //TOO STRICT managerB.execute1(new JobOnA()); managerB.execute1(new JobOnB()); //compiled managerB.execute1(new JobOnC()); managerB.execute1(new JobOnD()); //TOO MUCH FREEDOM managerB.execute2(new JobOnA()); //compiled managerB.execute2(new JobOnB()); //compiled managerB.execute2(new JobOnC()); //compiled !! managerB.execute2(new JobOnD()); //compiled !! //NOT ADEQUATE RESTRICTIONS managerB.execute3(new JobOnA()); managerB.execute3(new JobOnB()); //compiled managerB.execute3(new JobOnC()); //compiled !! managerB.execute3(new JobOnD()); //SHOULD BE managerB.execute4(new JobOnA()); //compiled managerB.execute4(new JobOnB()); //compiled managerB.execute4(new JobOnC()); managerB.execute4(new JobOnD()); }
现在有什么build议如何实现execute4?
==========编辑=======
public void execute4(Job<? super T> job){ job.exec( t); }
谢谢大家 :)
==========编辑==========
private <U> void execute2(Job<U> job){ U u= (U) t; //now it's safe job.exec(u); } public void execute4(Job<? super T> job){ execute2(job); }
更好,任何代码与U内的execute2
超级U型变成了命名!
有趣的讨论:)