13

Java泛型中<?> 和 <? extends Object>的异同分析

 3 years ago
source link: http://www.cnblogs.com/liululee/p/13999179.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

相信很多人和我一样,接触Java多年,却仍旧搞不清楚 Java 泛型中 <?><? extends Object> 的相似和不同。但是,这应该是一个比较高端大气上档次的Question, 在我们进行深入的探讨之前,有必要对Java泛型有一个基础的了解。详细请看上一篇文章!

重温Java泛型,带你更深入地理解它,更好的使用它!

1. 泛型产生的背景

在 JDK5 中引入了泛型来消除编译时错误和加强类型安全性。这种额外的类型安全性消除了某些用例中的强制转换,并使程序员能够编写泛型算法,这两种方法都可以生成更具可读性的代码。

例如,在 JDK5 之前,我们必须使用强制转换来处理列表的元素。这反过来又产生了一类特定的运行时错误:

List aList = new ArrayList();
aList.add(new Integer(1));
aList.add("a_string");
        
for (int i = 0; i < aList.size(); i++) {
    Integer x = (Integer) aList.get(i);
}

现在,我们想解决两个问题:

  • 我们需要一个显式转换来从 aList 中提取值——类型取决于左侧的变量类型(在本例中为 Integer
  • 当我们试图将 a_string 转换为 Integer 时,在第二次迭代中会出现运行时错误。

泛型填补了这个空白,代码如下:

List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add("a_string"); // compile time error
 
for (int i = 0; i < iList.size(); i++) {
    int x = iList.get(i);
}

执行上述代码,编译器会告诉我们,无法将 a_string 添加到 Integer 类型的 List 中,这比起在运行时才发现异常要好很多。

而且,不需要显式转换,因为编译器已经知道 iList 包含 Integer 类型的数据。另外,由于自动拆箱的关系,我们甚至不需要使用 Integer 类型,它的原始类型就足够了。

2. 泛型中的通配符

问号或通配符在泛型中用来表示未知类型。它可以有三种形式:

  • 无界通配符 : List<?> 表示未知类型的列表
  • 上界通配符 :List<? extends Number> 表示 Number 或其子类型(如 IntegerDouble)的列表
  • 下界通配符 :List<? super Integer> 表示 Integer 或其超类型 NumberObject的列表

由于 Object 是 Java 中所有类型的固有超类,所以我们会认为它也可以表示未知类型。换句话说, List<?>List<Object> 可以达到相同的目的。但事实并非如此。

来看看这两个方法:

public static void printListObject(List<Object> list) {    
    for (Object element : list) {        
        System.out.print(element + " ");    
    }        
}    
 
public static void printListWildCard(List<?> list) {    
    for (Object element: list) {        
        System.out.print(element + " ");    
    }     
}

给出一个 整数 的列表,比如:

List<Integer> li = Arrays.asList(1, 2, 3);

执行 printListObject(li) 不会编译,并且我们将得到以下错误:

The method printListObject(List<Object>) is not applicable for the arguments (List<Integer>)

而执行 printListWildCard(li) 将通过编译,并将 1 2 3 输出到控制台。

3. <?>和<? extends Object>的相同之处

在上面的示例中,如果我们将 printListWildCard 方法更改为:

public static void printListWildCard(List<? extends Object> list)

它的工作方式与 printListWildCard(List<?>) 相同。这是因为 Object 是 Java 所有对象的超类,基本上所有的东西都扩展了 Object 。因此,这个方法也会处理一个 Integer 类型的List。

也就是说, <?> 和 <? extends Object> 在这个例子中是同一个意思。

虽然在大多数情况下,这是正确的, 但也有一些区别 。接下来我们就来看看它们之间的差异。

4. <?>和<? extends Object>的不同之处

可重构类型是指那些在编译时未被擦除的类型。换句话说,一个不可重构类型,运行时将比编译时表达的信息更少,因为其中一些信息会被擦除。

一般来说,参数化类型是不可重新定义的。比如 List<String>Map<Integer,String> 就不可重新定义。编译器会擦除它们的类型,并将它们分别视为列表和映射。

这个准则的唯一例外是无界通配符类型。也就是说, List<?> 以及 Map<?, ?> 是可重写的。

另外, List<? extends Object> 不可重写。虽然微妙,但这是一个显著的区别。

不可重构的类型在某些情况下不能使用,例如在 instanceof 运算符或作为数组的元素。

所以,如果我们的代码写成这样:

List someList = new ArrayList<>();
boolean instanceTest = someList instanceof List<?>

代码编译后, instanceTesttrue

但是,如果我们在 List<? extends Object> 上使用 instanceof 运算符:

List anotherList = new ArrayList<>();
boolean instanceTest = anotherList instanceof List<? extends Object>;

那么第2行不编译。

类似地,在下面的代码片段中,第1行编译,但第2行不编译:

List<?>[] arrayOfList = new List<?>[1];
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]

5.结语

好了,文章到此就划上句号了,在本文中,我们主要讨论了<?> 和 <? extends Object>的异同,虽然基本上是相似的,但两者在可变与否方面存在细微差异。

如果你觉得文章还不错,记得关注公众号: 锅外的大佬

刘一手的博客

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK