结合<! - ?扩展ClassE - >和<! - ?超级B - >

时间:2017-07-23 13:53:56

标签: java generics extends super

我懂得怎么样?延伸..和?超级..自己工作,哪些泛型类型是可能的,但我不能理解这个层次结构如何可能:

- &GT;意味着扩展

类是X(最低),A到E(最高)

界面是F

X - &gt; A(实现F) - &gt; B - &gt; C - &gt; E(实施F)

也是D - &gt; ë

public class Node<T extends ClassE> {
    private T info;

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }
}

public static void main (String [] args){

    Node<? super ClassB> n2 = new Node<ClassC>(); 
// this makes sense, since Node accepts below E and above B

    InterfaceF i2 = n2.getInfo();
// how? Not only outside of <? extends E> but also getting value even though 
// <? super B> is defined above, what's up with PECS?

    n2.setInfo(new ClassX());
// also.. how? I'm setting a class that's out of the allowed range + 
// seemingly violating the PECS for <? extends E>
}

正如您所看到的,在组合它们时我完全感到困惑,因为这些声明通过编译器没有问题,这让我感到非常惊讶。 我在某处读过Java中不可能将这两个边界组合起来,但那又如何呢?

3 个答案:

答案 0 :(得分:2)

第一行编译InterfaceF i2 = n2.getInfo();,因为下限通配符仍然保留了类型变量本身的边界。由于类型变量的上限为ClassEgetInfo()仍会返回ClassE。由于ClassE实现InterfaceF,因此分配编译。

换句话说,我们可以想象当你Node<? super ClassB>时,你隐含地实际上做了类似(伪造语法)Node<? extends ClassE & super ClassB>的事情。 Node的类型参数既是ClassB 的超类型,也是的子类型ClassE

这类似于Node<?>隐含与Node<? extends ClassE>相同的方式。

实际指定的方式有点复杂,但它位于capture conversion。捕获转换是编译器采用带通配符的类型并将其视为没有通配符的类型的过程,以确定子类型。

  

G使用 n 类型参数A1,...,An命名通用类型声明,并使用相应的边界U1,...,Un

     

从参数化类型G<T1,...,Tn>到参数化类型G<S1,...,Sn存在捕获转换,其中,对于1≤i≤n

     
      
  • [...]

  •   
  • 如果Ti? super Bi形式的通配符类型参数,则Si是一个新的类型变量,其上限为Ui[A1:=S1,...,An:=Sn]且其下限是Bi

  •   

换句话说,Si(捕获转换后对应于? super ClassB的类型参数)从通配符的边界获得其下限,并从类型变量声明的边界获得其上限。

第二行n2.setInfo(new ClassX());编译,因为ClassXClassB的子类,所以它可以隐式转换为它。我们可以想象n2是一个Node<ClassB>,这条线编译的原因可能更明显:

Node<ClassB> n2 = ...;
n2.setInfo(new ClassX());

setInfo接受ClassB以及ClassB的任何子类型。

另外,就此而言:

  

我在某处读过Java中不可能两个边界的组合,但是那怎么工作呢?

编译器中的类型系统做了很多我们自己无法明确做的事情。另一个很好的例子(尽管不相关)是使用匿名类的类型推断:

int num = Objects.requireNonNull(new Object() {int num = 42;}).num;
System.out.println(num); // 42

它编译是因为允许类型推断推断T的{​​{1}}的类型参数是匿名对象类型,即使我们自己也永远不能将该类型作为显式类型参数提供。 / p>

答案 1 :(得分:0)

public class Test {
    public interface F {}
    public static class E implements F {}
    public static class D extends E {}
    public static class C extends E {}
    public static class B extends C {}
    public static class A extends B implements F {}
    public static class X extends A {}

    public static class Node<T extends E> {
        private T info;
        public T getInfo() {return info;}
        public void setInfo(T info) {this.info = info;}
    }

    public static void main(String[] args) {
        Node<? super B> n = new Node<C>(); // C is superclass of B = OK
        F i = n.getInfo(); // node type = B|C|E all these types implements F (since E implements F) = OK
        n.setInfo(new X()); // X has supertypes A,B,C,E = can be casted to B and so satisfy <? super B>
    }
}

答案 2 :(得分:0)

免责声明:我不知道具体的类型推理规则,但我会以最好的理解解释。

关于InterfaceF i2 = n2.getInfo() - 从Node<T extends E>开始,Node.getInfo()确保extends E返回E implements F。根据您的说明T extends E。因此,确保implement FNode.getInfo() = T extends E implements F。因此n2.getInfo() implements F。所以n2.setInfo(new ClassX())没问题。

关于Node<? super ClassB> - 我没有像上面这样的正式解释,但让我们试着考虑一下:基本上你ClassB告诉所有人期望< / em> 最多 NodeClassX内容的最低值。但是,由于ClassB传递性地继承? super ClassB,因此它完全有效,因为它将满足{{1}}提出的所有接口保证。

希望这有帮助!