用“超级”关键字绑定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不是Integersuper但是ObjectIntegersuper类,而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,为什么我们不应该使用它?
    • 介绍原始typesListList<Object>不同的List<?>不同之处

关于使用superextends

  • Java Generics: What is PECS?
    • 有效的Java第2版 :“生产者extends消费者super
  • Javagenerics中的superextends什么区别?
  • <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),而是限制调用者对我们返回的对象的期望。 作为一个简单的规则,您使用的extendstypes是您所需要的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型变成了命名!

有趣的讨论:)