67

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

比如在Collection接口中,为什么toArray方法不是这样写的

interface Collection<T>{
    <S super T> S[] toArray(S[] a);
}
4

5 回答 5

54

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

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

您要提出的建议是:

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!

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

integerList.toArray(new String[0])  // trying to prevent this from compiling

因为,根据您的论点,String不是super. 但是,is a of和 a is an ,所以编译器仍然会让上面的代码编译,即使假设你可以这样做!IntegerObjectsuperIntegerString[]Object[]<S super T>

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

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

泛型和数组不能混用,这是它展示的众多地方之一。


非数组示例

同样,假设您有这个通用方法声明:

<T super Integer> void add(T number) // hypothetical! currently illegal in Java

你有这些变量声明:

Integer anInteger
Number aNumber
Object anObject
String aString

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


也可以看看

相关问题

关于泛型类型规则:

关于使用superand extends

于 2010-05-10T05:04:05.123 回答
41

由于没有人提供令人满意的答案,正确的答案似乎是“无缘无故”。

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

String[] strings = new String[1];
Object[] objects = strings;
objects[0] = 0;

这个明显错误的代码在编译时没有使用任何“超级”构造,因此不应将数组协方差用作参数。

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

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确实需要。

请注意,如果我反转类型参数声明的顺序,从而将super约束更改为extends. 但是,这只有在我将方法重写为静态方法时才有可能:

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

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

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

于 2011-04-15T00:06:56.393 回答
20

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

BT2:评估

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

尤其是第 3 节和第 9 页的最后一段。在子类型约束的两侧承认类型变量可能会导致一组类型方程没有单一的最佳解;因此,类型推断不能使用任何现有的标准算法来完成。这就是类型变量只有“扩展”边界的原因。

另一方面,通配符不必被推断,因此不需要此约束。

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

是的; 关键是通配符,即使被捕获,也仅用作推理过程的输入;因此,不需要推断(仅)具有下限的任何内容。

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

我看到了问题。但我看不出它与推理期间通配符下限的问题有何不同,例如:

列表<? 超号>s;
布尔值 b;
...
s = b ?小号:小号;

目前,我们推断 List<X> where X extends Object 作为条件表达式的类型,这意味着赋值是非法的。

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

可悲的是,谈话到此结束。(现已失效的)链接用于指向的论文是Inferred Type Instantiation for GJ。从最后一页看,它归结为:如果允许下限,则类型推断可能会产生多个解决方案,其中没有一个是主要的。

于 2015-11-24T13:53:35.263 回答
-1

假设我们有:

  • 基本等级 A > 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());
    }
    

有什么建议现在如何实施 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型变成了名字!

有趣的讨论:)

于 2014-10-03T15:54:06.657 回答
-1

我真的很喜欢被接受的答案,但我想对它提出一个稍微不同的看法。

super在类型化参数中受支持只是为了允许逆变功能。当谈到协变逆变时,重要的是要了解 Java 仅支持使用点变化。与 Kotlin 或 Scala 不同,它们允许声明站点变化Kotlin 文档在这里很好地解释了它。或者,如果您更喜欢 Scala,这里有一款适合您。

这基本上意味着在 Java 中,当你根据 PECS 声明它时,你不能限制你将使用你的类的方式。该类既可以消费也可以生产,并且它的一些方法可以同时做到这一点,toArray([])顺便说一下。

现在,extends允许在类和方法声明中的原因是因为它更多的是关于多态性而不是关于方差多态性通常是 Java和OOP 的内在组成部分:如果一个方法可以接受某个超类型,则始终可以安全地将子类型传递给它。如果一个方法,在声明站点作为它的“合同”,应该返回一些超类型,如果它在它的实现中返回一个子类型而不是完全没问题

于 2019-10-11T07:12:43.637 回答