在Java中使用通配符

时间:2014-10-23 17:38:23

标签: java generics wildcard

import java.util.ArrayList;
import java.util.List;

public class WildCardNumber {

    public static void main(String[] args) {
        List<EvenNumber> le = new ArrayList<>();
        List<? extends NaturalNumber> ln = le;
        ln.add(new NaturalNumber(50));//  *   Compile time error
        ln.add(new EvenNumber(46));   // **   Compile time error 
    }

}

class NaturalNumber {

    private int n;

    public NaturalNumber(int n) {
        this.n = n;
    }

}

class EvenNumber extends NaturalNumber {

    public EvenNumber(int n) {
        super(n);
    }

}

在Oracle文档中学习通配符时,我找到了上述代码。

根据来源,变量“ln”不能接受任何“NaturalNumber”,因为它是“EvenNumber”内容的列表。我尝试添加一个“EvenNumber”对象。这也是不被接受的。

似乎变量“ln”是文档中提到的只读对象。你们可以解释为什么这个对象是只读的吗?(我可以添加空值)如果我们不能添加“NaturalNumber”,为什么我们不能添加“EvenNumber”呢?因为根据我们已经指定的Wildcard,变量“ln”可以接受“NaturalNumber”的子类型,而“EvenNumber”是子类型吗?

3 个答案:

答案 0 :(得分:5)

这是一个经典问题。它之所以成功,是因为如果允许的话:

    List<EvenNumber> le = new ArrayList<>();
    List<? extends NaturalNumber> ln = le;
    ln.add(new NaturalNumber(50));
    ln.add(new EvenNumber(46));  
    EvenNumber even = le.get(0); // ClassCastException

我们保证le声明所有数字必须是偶数。但是如果允许你在那里添加一个NaturalNumber,那么这个保证会中断。

答案 1 :(得分:1)

类型安全检查基于所涉及对象的声明的类型。这个声明

List<? extends NaturalNumber> ln = le;

描述了一个(引用)对象ln,其类型参数在编译时不是精确已知的,并且实际上在程序执行期间的不同点可能不同。为该变量分配对其类型更精确已知的对象的引用并不重要,因为(1)不会更改ln的声明类型,以及(2)因为不同 - 参数化列表可以在以后重新分配。 (在给定代码中实际上不会发生这种情况是无关紧要的。)

实际上,您无法向列表ln添加任何元素,因为您(明确地)不知道它接受的元素类型。但是,您可以从中检索元素:

le.add(new EvenNumber(0));
NaturalNumber n = ln.get(0);

如果指定下限,则情况相反:

List<? super EvenNumber> lq = le;
NaturalNumber n = lq.get(0);      /* type safety error */

情况正好相反:您无法确定列表lq中的对象类型,因为它可能是对List<Object>的引用(尽管您可以始终执行此操作:

Object o = lq.get(0);

。)但是,您可以确定无论实际的类型参数如何,它都与添加 EvenNumber s一致:

lq.add(new EvenNumber(12));

答案 2 :(得分:-1)

您无法添加到ln,因为这可能会导致运行时错误。 C#和Scala更好地适应所谓的协方差/逆变,其中泛型参数始终用作输出/输入参数。 C#/ Scala会让你安全地向下/向上投射。集合和列表在输入和输出容量中都使用泛型参数,因此您无法安全地允许转换,因此即使在C#/ Scala中,这些类型也是不变的。