java 使用 'super' 关键字限定泛型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2800369/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 22:54:51  来源:igfitidea点击:

Bounding generics with 'super' keyword

javagenericslanguage-designsuper

提问by mohsenof

Why can I use superonly with wildcards and not with type parameters?

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

For example, in the Collectioninterface, why is the toArraymethod not written like this

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

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

采纳答案by polygenelubricants

superto bound a named type parameter (e.g. <S super T>) as opposed to a wildcard (e.g. <? super T>) is ILLEGALsimply because even if it's allowed, it wouldn't do what you'd hoped it would do, because since Objectis the ultimate superof all reference types, and everything is an Object, in effect there is no bound.

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

In your specific example, since anyarray of reference type is an Object[](by Java array covariance), it can therefore be used as an argument to <S super T> S[] toArray(S[] a)(if such bound is legal) at compile-time, and it wouldn't prevent ArrayStoreExceptionat run-time.

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

What you're trying to propose is that given:

你试图提出的是给定的:

List<Integer> integerList;

and given this hypotheticalsuperbound on toArray:

并给出这个假设的super界限toArray

<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java

the compiler should only allow the following to compile:

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

integerList.toArray(new Integer[0]) // works fine!
integerList.toArray(new Number[0])  // works fine!
integerList.toArray(new Object[0])  // works fine!

and no other array type arguments (since Integeronly has those 3 types as super). That is, you're trying to prevent this from compiling:

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

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

because, by your argument, Stringis not a superof Integer. However, Objectis a superof Integer, and a String[]is an Object[], so the compiler stillwould let the above compile, even if hypothetically you can do <S super T>!

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

So the following would still compile(just as the way they are right now), and ArrayStoreExceptionat run-time could not be prevented by any compile-time checking using generic type bounds:

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

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

Generics and arrays don't mix, and this is one of the many places where it shows.

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



A non-array example

非数组示例

Again, let's say that you have this generic method declaration:

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

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

And you have these variable declarations:

你有这些变量声明:

Integer anInteger
Number aNumber
Object anObject
String aString

Your intention with <T super Integer>(if it's legal) is that it should allow add(anInteger), and add(aNumber), and of course add(anObject), but NOT add(aString). Well, Stringis an Object, so add(aString)would still compile anyway.

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



See also

也可以看看

Related questions

相关问题

On generics typing rules:

关于泛型类型规则:

On using superand extends:

关于使用superextends

回答by Rotsor

As no one has provided a satisfactory answer, the correct answer seems to be "for no good reason".

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

polygenelubricants provided a good overview of bad things happening with the java array covariance, which is a terrible feature by itself. Consider the following code fragment:

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

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

This obviously wrong code compiles without resorting to any "super" construct, so array covariance should not be used as an argument.

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

Now, here I have a perfectly valid example of code requiring superin the named type parameter:

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

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

Potentially supporting some nice usage:

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

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?");

The latter code fragment does not compile if I remove the Baltogether, so Bis indeed needed.

如果我B完全删除后一个代码片段,则不会编译,因此B确实需要。

Note that the feature I'm trying to implement is easily obtained if I invert the order of type parameter declarations, thus changing the superconstraint to extends. However, this is only possible if I rewrite the method as a static one:

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

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

The point is that this Java language restriction is indeed restricting some otherwise possible useful features and may require ugly workarounds. I wonder what would happen if we needed withDefaultto be virtual.

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

Now, to correlate with what polygenelubricants said, we use Bhere not to restrict the type of object passed as defaultValue(see the String used in the example), but rather to restrict the caller expectations about the object we return. As a simple rule, you use extendswith the types you demand and superwith the types you provide.

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

回答by Ben Schulz

The "official" answer to your question can be found in a Sun/Oracle bug report.

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

BT2:EVALUATION

See

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

particularly section 3 and the last paragraph on page 9. Admitting type variables on both sides of subtype constraints can result in a set of type equations with no single best solution; consequently, type inference cannot be done using any of the existing standard algorithms. That is why type variables have only "extends" bounds.

Wildcards, on the other hand, do not have to be inferred, so there is no need for this constraint.

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

Yes; the key point is that wildcards, even when captured, are only used as inputs of the inference process; nothing with (only) a lower bound needs to be inferred as a result.

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

I see the problem. But I do not see how it is different from the problems we have with lower bounds on wildcards during inference, e.g.:

List<? super Number> s;
boolean b;
...
s = b ? s : s;

Currently, we infer List<X> where X extends Object as the type of the conditional expression, meaning that the assignment is illegal.

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

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

Sadly, the conversation ends there. The paper to which the (now dead) link used to point is Inferred Type Instantiation for GJ. From glancing at the last page, it boils down to: If lower bounds are admitted, type inference may yield multiple solutions, none of which is principal.

遗憾的是,谈话就此结束。(现已失效)链接所指向的论文是GJ 的推断类型实例化。从最后一页看,它归结为:如果允许下界,类型推断可能会产生多个解决方案,其中没有一个是principal

回答by yuranos

I really like the accepted answer, but I would like to put a slightly different perspective on it.

我真的很喜欢接受的答案,但我想对此提出稍微不同的观点。

superis supported in a typed parameter only to allow contravariancecapabilities. When it comes to covarianceand contravarianceit's important to understand that Java only supports use-site variance. Unlike Kotlin or Scala, which allow declaration-site variance. Kotlin documentation explains it very well here. Or if you're more into Scala, here's one for you.

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

It basically means that in Java, you can not limit the way you're gonna use your class when you declare it in terms of PECS. The class can both consume and produce, and some of its methods can do it at the same time, like toArray([]), by the way.

这基本上意味着在 Java 中,当您根据 PECS 声明类时,您无法限制使用类的方式。类既可以消费也可以生产,并且它的一些方法可以同时进行,比如toArray([]),顺便说一下。

Now, the reason extendsis allowed in classes and methods declarations is because it's more about polymorphismthan it is about variance. And polymorphismis an intrinsic part of Java and OOP in general: If a method can accept some supertype, a subtype can always safely be passed to it. And if a method, at declaration site as it's "contract", should return some supertype, it's totally fine if it returns a subtype instead in its implementations

现在,extends在类和方法声明中允许的原因是因为它更多的是关于多态而不是关于方差。而多态性是Java和OOP的一般固有的一部分:如果一个方法可以接受一些父,子类型可以随时安全地传递给它。如果一个方法,在声明站点,因为它是“合同”,应该返回一些超类型,如果它在它的实现中返回一个子类型,那完全没问题

回答by nxdrvr

Suppose we have:

假设我们有:

  • basic classes A > B > C and D

    class A{
        void methodA(){}
    };
    class B extends  A{
        void methodB(){}
    }
    
    class C extends  B{
        void methodC(){}
    }
    
    class D {
        void methodD(){}
    }
    
  • job wrapper classes

    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();
        }
    }
    
  • and one manager class with 4 different approaches to execute job on object

    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);
        }
    }
    
  • with usage

    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());
    }
    
  • 基本类 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());
    }
    

Any suggestions how to implement execute4 now ?

现在有什么建议如何实施 execute4 吗?

==========edited =======

==========编辑======

    public void execute4(Job<? super  T> job){
        job.exec( t);
    }

Thanks to all :)

谢谢大家 :)

========== edited ==========

==========编辑==========

    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);
    }

much better, any code with U inside execute2

更好,任何在 execute2 中带有 U 的代码

super type U becomes named !

超级类型 U 被命名!

interesting discussion :)

有趣的讨论:)