Java 将泛型与'绑定;超级';关键词

Java 将泛型与'绑定;超级';关键词,java,generics,language-design,super,Java,Generics,Language Design,Super,为什么我只能将super与通配符一起使用,而不能与类型参数一起使用 例如,在Collection界面中,为什么toArray方法不是这样编写的 interface Collection<T>{ <S super T> S[] toArray(S[] a); } 接口集合{ S[]到阵列(S[]a); } super绑定命名类型参数(例如)而不是通配符(例如),因为没有人提供满意的答案,正确的答案似乎是“没有充分的理由” PultEnguluBrimeCutes

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

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

interface Collection<T>{
    <S super T> S[] toArray(S[] a);
}
接口集合{
S[]到阵列(S[]a);
}

super
绑定命名类型参数(例如
)而不是通配符(例如
),因为没有人提供满意的答案,正确的答案似乎是“没有充分的理由”

PultEnguluBrimeCutes提供了一个很好的综述,它是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;
    }
}
关键是,这种Java语言限制确实限制了一些其他可能有用的功能,可能需要难看的解决方法。我想知道如果我们需要
withDefault
虚拟化会发生什么

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

假设我们有:

  • 基本等级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();
        }
    }
    
    接口作业{
    无效执行官(T);
    }
    类JobOnA实现作业{
    @凌驾
    公共行政主任(A){
    a、 方法a();
    }
    }
    类JobOnB实现作业{
    @凌驾
    公开作废执行官(B){
    b、 方法b();
    }
    }
    类JobOnC实现作业{
    @凌驾
    公开作废执行官(C){
    c、 方法c();
    }
    }
    类JobOnD实现作业{
    @凌驾
    公共行政主任(D){
    d、 方法d();
    }
    }
    
  • 和一个管理器类,具有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);
        }
    }
    
    类管理器{
    最终T;
    经理(T){
    t=t;
    }
    public void execute1(作业作业){
    执行主任(t);
    }
    public void execute2(作业作业){
    U=(U)t;//不安全
    行政总裁(u);
    }
    public void execute3(作业){
    U=(U)t;//不安全
    行政总裁(u);
    }
    //所需功能,目前未编译
    public void execute4(作业作业){
    U=(U)t;//安全
    行政总裁(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());
    }
    
    void用法(){
    B=新的B();
    经理b=新经理(b);
    //太严格
    managerB.execute1(newjobona());
    managerB.execute1(new JobOnB());//已编译
    managerB.execute1(newjobonc());
    managerB.execute1(newjobond());
    //太多的自由
    managerB.execute2(new JobOnA());//已编译
    managerB.execute2(new JobOnB());//已编译
    managerB.execute2(new JobOnC());//已编译!!
    managerB.execute2(new JobOnD());//已编译!!
    //限制不充分
    managerB.execute3(newjobona());
    managerB.execute3(new JobOnB());//已编译
    managerB.execute3(newjobonc());//已编译!!
    managerB.execute3(newjobond());
    //应该是
    managerB.execute4(new JobOnA());//已编译
    managerB.execute4(new JobOnB());//已编译
    managerB.execute4(newjobonc());
    managerB.execute4(newjobond());
    }
    
现在有没有关于如何实现execute4的建议

=============已编辑=======

    public void execute4(Job<? super  T> job){
        job.exec( t);
    }
public void execute4(Job您的问题的“官方”答案可以在中找到

BT2:评估

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

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

@###.### 2004-05-25 是的;关键是,即使捕获到通配符,也只使用通配符 作为推理过程的输入;没有(只有)下限需求 作为结果被推断

@###.### 2004-05-26 我明白问题所在,但我不明白它与问题有什么不同 在推理过程中,我们有通配符的下界,例如:


列表我真的很喜欢被接受的答案,但我想对它提出一个稍微不同的观点

super
仅在类型化参数中支持逆变功能。当涉及到协方差逆变时,必须了解Java仅支持使用站点方差。与Kotlin或Scala不同,后者允许声明站点方差riance。Kotlin文档对此进行了很好的解释。或者,如果您更喜欢Scala,那么这是一个适合您的文档

这基本上意味着在Java中,当你用PEC声明类时,你不能限制你使用它的方式。这个类既可以使用也可以生成,它的一些方法可以同时使用,顺便说一下,比如
toArray([])

现在,类和方法声明中允许使用
扩展
的原因是因为它更多地是关于多态性而不是关于差异。而多态性通常是Java和OOP的固有部分:如果一个方法可以接受某个超类型,那么子类型总是可以安全地传递给如果是一个方法,在声明站点
String[] strings = new String[1];
Object[] objects = strings;
objects[0] = 0;
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?");
// This one actually works and I use it.
public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... }
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();
    }
}
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());
}
    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);
    }