使用'super'关键字绑定泛型

moh*_*nof 55 java generics language-design super

为什么我super只能使用通配符而不使用类型参数?

例如,在Collection界面中,为什么toArray方法不是这样写的

interface Collection<T>{
    <S super T> S[] toArray(S[] a);
}
Run Code Online (Sandbox Code Playgroud)

pol*_*nts 47

super绑定一个命名类型参数(例如<S super T>)而不是通配符(例如<? super T>)是ILLEGAL,因为即使它被允许,它也不会做你希望它会做的事情,因为因为它Objectsuper所有引用类型的终极,而且一切都是Object,实际上没有约束力.

在您的具体示例中,由于任何引用类型数组都是Object[](通过Java数组协方差),因此它可以<S super T> S[] toArray(S[] a)在编译时用作参数(如果此类绑定是合法的),并且它不会ArrayStoreException在运行时阻止 - 时间.

你要提出的建议是:

List<Integer> integerList;
Run Code Online (Sandbox Code Playgroud)

鉴于这个假设的 super界限toArray:

<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java
Run Code Online (Sandbox Code Playgroud)

编译器应该只允许以下编译:

integerList.toArray(new Integer[0]) // works fine!
integerList.toArray(new Number[0])  // works fine!
integerList.toArray(new Object[0])  // works fine!
Run Code Online (Sandbox Code Playgroud)

并且没有其他数组类型参数(因为Integer只有这3种类型super).也就是说,您正在尝试阻止编译:

integerList.toArray(new String[0])  // trying to prevent this from compiling
Run Code Online (Sandbox Code Playgroud)

因为,你的说法,String是没有superInteger.但是,ObjectsuperInteger,并且String[]是一个Object[],所以编译器仍然将让上面的编译,即使假设你能做到<S super T>!

因此,以下内容仍将编译(就像它们现在的方式一样),并且ArrayStoreException在运行时无法通过使用泛型类型边界的任何编译时检查来阻止:

integerList.toArray(new String[0])  // compiles fine!
// throws ArrayStoreException at run-time
Run Code Online (Sandbox Code Playgroud)

泛型和数组不混合,这是它显示的许多地方之一.


非数组示例

再说一次,假设你有这个泛型方法声明:

<T super Integer> void add(T number) // hypothetical! currently illegal in Java
Run Code Online (Sandbox Code Playgroud)

你有这些变量声明:

Integer anInteger
Number aNumber
Object anObject
String aString
Run Code Online (Sandbox Code Playgroud)

你的意图<T super Integer>(如果它是合法的)是它应该允许add(anInteger),add(aNumber)当然add(anObject),但不是add(aString).嗯,String是一个Object,所以add(aString)仍然会编译.


也可以看看

相关问题

关于泛型类型规则:

使用superextends:

  • "它不会做你所希望的" - 这是完全错误的.OP提供了一个很好的用例(以可怕的数组协方差为模),具有明显的语义:集合应该能够填充任何更通用类型的数组并返回所述数组.没有什么问题. (18认同)
  • polygenelubricants:这个答案的主要观点是,上部类型绑定没用,是不正确的.这在Rotsor的回答中得到了证明.我想鼓励你编辑这个答案来指出这一点,因为它目前的形式是误导人! (7认同)
  • 有一些(尽管很少)有效的下限用例. (4认同)
  • 那么为什么<S super T> List <S> addToList(List <S> list,T element){list.add(element); 返回清单; 没有意义? (3认同)
  • "因为任何引用类型的数组都是`Object []`,所以它可以用作`<S super T> S [] toArray(S [] a)`"True的参数.但是,如果`S`被推断为`Object`,那么这也会将方法的返回类型约束为`Object []`,这可能会导致编译错误,具体取决于调用的上下文(如果它期望一个`S []`).因此,将类型变量更改为"Object"不能替代`super`界限. (3认同)
  • -1这是错误的答案。真正的答案是因为“他们以这种方式设计了Java”。请参见Rotsor的答案,以了解仅由于Java规范而不是由于逻辑不正确而导致无效代码的有效(且极为有用)的用例。 (2认同)

Rot*_*sor 33

由于没有人提供令人满意的答案,正确答案似乎是"没有充分理由".

polygenelubricants很好地概述了java数组协方差所发生的坏事,这本身就是一个可怕的特征.请考虑以下代码片段:

String[] strings = new String[1];
Object[] objects = strings;
objects[0] = 0;
Run Code Online (Sandbox Code Playgroud)

这显然错误的代码编译而不诉诸任何"超级"构造,因此不应将数组协方差用作参数.

现在,我super在命名类型参数中有一个完全有效的代码示例:

class Nullable<A> {
    private A value;
    // Does not compile!!
    public <B super A> B withDefault(B defaultValue) {
        return value == null ? defaultValue : value;
    }
}
Run Code Online (Sandbox Code Playgroud)

可能支持一些不错的用法:

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

如果我B完全删除后面的代码片段就不会编译,所以B确实需要.

请注意,如果我反转类型参数声明的顺序,那么我正在尝试实现的功能很容易获得,从而将super约束更改为extends.但是,这只有在我将方法重写为静态方法时才有可能:

// This one actually works and I use it.
public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... }
Run Code Online (Sandbox Code Playgroud)

关键是这种Java语言限制确实限制了一些其他可能有用的功能,可能需要丑陋的解决方法.我想知道如果我们需要withDefault虚拟会发生什么.

现在,为了与polygenelubricants所说的相关联,我们B在这里使用不限制传递的对象的类型defaultValue(参见示例中使用的String),而是限制调用者对我们返回的对象的期望.作为一个简单的规则,您可以使用extends您需要super的类型以及您提供的类型.

  • +1您的示例与Guava的[`Optional.or(T)`]中的实际用例相匹配(http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/碱/ Optional.html#或\(T \)).从文档:"签名`public T或(T defaultValue)`过于严格.然而,理想的签名,`public <S super T> S或(S)`,不是合法的Java.因此,一些涉及子类型的合理操作是编译错误". (15认同)
  • @PaulBellora…现在,Java具有自己的`Optional`或Stream API,它吸引了所有人。那么,您能做的最好的事情就是实际上已经过时的.. &lt;SuperType&gt; map(t-&gt; t)或.map(Function。&lt;SuperType&gt; identity())`… (2认同)

Ben*_*ulz 11

可以在Sun/Oracle错误报告中找到您的问题的"官方"答案.

BT2:评估

看到

http://lampwww.epfl.ch/~odersky/ftp/local-ti.ps

特别是第3节和第9页的最后一段.允许子类型约束两侧的类型变量可以产生一组没有单一最佳解的类型方程; 因此,使用任何现有的标准算法都无法进行类型推断.这就是类型变量只有"扩展"边界的原因.

另一方面,通配符不必推断,因此不需要这种约束.

@ ###.### 2004-05-25

是; 关键点在于,即使被捕获,通配符也仅用作推理过程的输入; 没有(仅)下限需要作为结果推断出来.

@ ###.### 2004-05-26

我看到了问题.但我不明白它与我们在推理过程中遇到的通配符下限问题有什么不同,例如:

名单<?超级数> s;
布尔b;
......
s = b?s:s;

目前,我们推断List <X>,其中X扩展Object作为条件表达式的类型,这意味着赋值是非法的.

@ ###.### 2004-05-26

可悲的是,谈话在那里结束了.(现在死的)链接用于指向的论文是GJ的推断类型实例化.从浏览最后一页开始,它归结为:如果允许下限,则类型推断可能会产生多个解,其中没有一个是主要的.

  • 被低估的答案,应该被接受。方法类型参数的下界很有用(如Rotsor所示)并且可以实现(如该论文的作者在Scala中所做的那样)。他们很难明智地实现,这显然是Java中不允许使用它们的真正原因。 (2认同)

归档时间:

查看次数:

11837 次

最近记录:

6 年 前