用“super”限制泛型关键词

发布于 2024-08-31 21:42:48 字数 224 浏览 5 评论 0原文

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

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

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

Why can I use super only with wildcards and not with type parameters?

For example, in the Collection interface, why is the toArray method not written like this

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

把回忆走一遍 2024-09-07 21:42:48

super 绑定命名类型参数(例如 ),而不是通配符(例如 ) 是非法,只是因为即使允许,它也不会执行您希望它执行的操作,因为 Object 是最终的super 所有引用类型,并且一切都是一个 Object实际上没有绑定

在您的具体示例中,由于任何引用类型的数组都是Object[](通过Java数组协方差),因此它可以用作<的参数;S超级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 不是 Integersuper但是ObjectIntegersuper,而 String[] 是一个 Object[],因此编译器仍然会让上面的代码编译,即使假设你可以

因此,以下仍然会编译(就像它们现在的样子一样),并且运行时的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

您使用 (如果合法)的意图是它应该允许add(anInteger)add(aNumber),当然还有 add(anObject),但不是 add(aString)< /代码>。好吧,String 是一个 Object,因此 add(aString) 无论如何仍然可以编译。


另请参阅

相关问题

关于泛型输入规则:

在使用 < code>super 和 extends

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

In your specific example, since any array 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 ArrayStoreException at run-time.

What you're trying to propose is that given:

List<Integer> integerList;

and given this hypothetical super bound on 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 Integer only has those 3 types as super). That is, you're trying to prevent this from compiling:

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

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

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

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, String is an Object, so add(aString) would still compile anyway.


See also

Related questions

On generics typing rules:

On using super and extends:

机场等船 2024-09-07 21:42:48

由于没有人提供满意的答案,正确的答案似乎是“没有充分的理由”。

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 与您提供的类型一起使用。

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:

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 super in the named type parameter:

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 B altogether, so B is indeed needed.

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

// 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 withDefault to be virtual.

Now, to correlate with what polygenelubricants said, we use B here 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 extends with the types you demand and super with the types you provide.

老街孤人 2024-09-07 21:42:48

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

BT2:评估

查看

http://lampwww.epfl.ch/~odersky/ftp/本地ti.ps

特别是第 3 节和第 9 页的最后一段。
子类型约束两侧的类型变量可能会导致
没有单一最佳解的类型方程组;最后,
无法使用任何现有标准来完成类型推断
算法。这就是为什么类型变量只有“扩展”界限。

另一方面,通配符不必进行推断,因此
不需要这个约束。

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

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

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

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

列表 s;
布尔值 b;
...
s = b ? s:s;

目前,我们推断 List其中 X 扩展了 Object 作为
条件表达式,表示赋值非法。

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

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

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

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

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.

—━☆沉默づ 2024-09-07 21:42:48

唯一的原因是在类级别定义时使用 super 关键字声明类型参数是没有意义的。
Java 唯一合乎逻辑的类型擦除策略是回退到所有对象的超类型,即 Object 类。

可以在这里找到一个很好的示例和解释:
http: //www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#为什么%20is%20there%20no%20lower%20bound%20for%20type%20parameters

类型擦除规则的简单示例可以在这里找到:
https://www.tutorialspoint.com/java_generics/java_generics_type_erasure.htm#:~:text=Type%20erasure%20is%20a%20process,there%20is%20no%20runtime%20overhead

The only reason is it makes no sense when declaring a type parameter with a super keyword when defining at a class level.
The only logical type-erasure strategy for Java would have been to fallback to the supertype of all objects, which is the Object class.

A great example and explanation can be found here:
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#Why%20is%20there%20no%20lower%20bound%20for%20type%20parameters?

A simple example for rules of type-erasure can be found here:
https://www.tutorialspoint.com/java_generics/java_generics_type_erasure.htm#:~:text=Type%20erasure%20is%20a%20process,there%20is%20no%20runtime%20overhead.

呆萌少年 2024-09-07 21:42:48

假设我们有:

  • 基本类 A > B> C和D

    <前><代码>A类{
    无效方法A(){}
    };
    B 类扩展 A{
    无效方法B(){}
    }

    C 类扩展 B{
    void 方法C(){}
    }

    D 类{
    无效方法D(){}
    }

  • 作业包装类

    接口Job; {
        无效执行(T t);
    }
    
    JobOnA 类实现 Job{
        @覆盖
        公共无效执行(A a){
            a.methodA();
        }
    }
    类 JobOnB 实现 Job{
        @覆盖
        公共无效执行(B b){
            b.methodB();
        }
    }
    
    JobOnC 类实现 Job{
        @覆盖
        公共无效执行(CC){
            c.methodC();
        }
    }
    
    JobOnD 类实现 Job{
        @覆盖
        公共无效执行(D d){
            d.methodD();
        }
    }
    
  • 和一个管理器类,具有 4 种不同方法在对象上执行作业

    类管理器{
        最终T t;
        经理(T t){
            这个.t=t;
        }
        公共无效执行1(作业作业){
            作业.exec(t);
        }
    
        公共无效执行2(作业作业){
            Uu=(U)t; //不安全
            作业.exec(u);
        }
    
        公共 voidexecute3(Job job){
            Uu=(U)t; //不安全
            作业.exec(u);
        }
    
        //所需的功能,暂时未编译
        公共无效execute4(作业作业){
            Uu=(U)t; //安全的
            作业.exec(u);
        }
    }
    
  • 和用法

    无效用法(){
        B b = 新 B();
        经理 managerB = 新的 Manager(b);
    
        //太严格了
        managerB.execute1(new JobOnA());
        managerB.execute1(new JobOnB()); //编译完成
        managerB.execute1(new JobOnC());
        managerB.execute1(new JobOnD());
    
        //太多的自由
        managerB.execute2(new JobOnA()); //编译完成
        managerB.execute2(new JobOnB()); //编译完成
        managerB.execute2(new JobOnC()); //编译完成!!
        managerB.execute2(new JobOnD()); //编译完成!!
    
        //限制不够     
        managerB.execute3(new JobOnA());
        managerB.execute3(new JobOnB()); //编译完成
        managerB.execute3(new JobOnC()); //编译完成!!
        managerB.execute3(new JobOnD());
    
        //应该是
        managerB.execute4(new JobOnA()); //编译完成
        managerB.execute4(new JobOnB()); //编译完成
        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 被命名!

有趣的讨论:)

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

Any suggestions how to implement execute4 now ?

==========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

super type U becomes named !

interesting discussion :)

生活了然无味 2024-09-07 21:42:48

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

类型化参数中支持 super 仅是为了允许逆变功能。当谈到协方差逆变时,了解Java仅支持使用站点方差非常重要。与 Kotlin 或 Scala 不同,它们允许声明站点差异。 Kotlin 文档此处对此进行了很好的解释。或者,如果您更喜欢 Scala,这里给你一份。

它基本上意味着在 Java 中,当您根据 PECS 声明类时,您不能限制类的使用方式。该类既可以消费又可以生产,并且它的一些方法可以同时执行此操作,例如 toArray([]) 顺便说一句。

现在,在类和方法声明中允许使用 extends 的原因是,它更多的是关于多态性,而不是变异。一般来说,多态性是 Java 和 OOP 的固有部分:如果方法可以接受某些超类型,则始终可以安全地将子类型传递给它。如果一个方法,在声明站点因为它是“契约”,应该返回一些超类型,那么如果它在其实现中返回一个子类型,那就完全没问题了

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

super is supported in a typed parameter only to allow contravariance capabilities. When it comes to covariance and contravariance it'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.

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.

Now, the reason extends is allowed in classes and methods declarations is because it's more about polymorphism than it is about variance. And polymorphism is 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文