原文点此链接

使用通配符的原因:Java中的数组是协变的,但是泛型不支持协变。

数组的协变

首先了解下什么是数组的协变,看下面的例子:

Number[] nums = new Integer[10]; // OK

因为Integer是Number的子类,一个Integer对象也是一个Number对象,所以一个Integer的数组也是一个Number的数组,这就是数组的协变。

Java把数组设计成协变的,在一定程度上是有缺陷的。因为尽管把Integer[]赋值给Number[],Integer[]可以向上转型为Number[],但是数据元素的实际类型是Integer,只能向数组中放入Integer或者Integer的子类。如果向数组中放入Number对象或者Number其他子类的对象,对于编译器来说也是可以通过编译的。但是运行时JVM能够知道数组元素的实际类型是Integer,当其它对象加入数组是就会抛出异常(java.lang.ArrayStoreException)。

泛型的设计目的之一就是保证了类型安全,让这种运行时期的错误在编译期就能发现,所以泛型是不支持协变的。例如:

List<Number> nums = new ArrayList<Integer>(); // incompatible types

当确实需要建立这种向上转型的类型关系的时候,就需要用到泛型的通配符特性了。例如:

List<? extends Number> nums = new ArrayList<Integer>(); // OK

无边界通配符(Unbounded Wildcards)

语法:

class-name<?> var-name

例子:

public static void print(List<?> list) {
for (Object obj : list) {
System.out.println(o);
}
}

List<?> list和List list的区别:

  • List<?> list是表示持有某种特定类型对象的List,但是不知道是哪种类型;List list是表示持有Object类型对象的List。
  • List<?> list因为不知道持有的实际类型,所以不能add任何类型的对象,但是List list因为持有的是Object类型对象,所以可以add任何类型的对象。
    注意:List<?> list可以add(null),因为null是任何引用数据类型都具有的元素。

Pair<?> 和 Pair 的区别

  • Pair<?>的  ? getFirst()方法,返回值只能赋值给一个Object对象,它的void setFirst(? )方法不能被调用,甚至不能用Object调用。
  • 为什么要使用这样脆弱的类型?它对于许多简单的操作非常有用。例如 ,下面这个方法将用来测试一个 pair 是否包含一个 mill 引用,它不需要实际的类型。
    public static boolean hasNulls (Pair<?> p)
    {
    return p.getFirstO = null | | p.getSecondO = null ;
    }
    通过将 hasNulls 转换成泛型方法,可以避免使用通配符类型:
    public static <T> boolean hasNulls (Pair<T> p)
    但是,带有通配符的版本可读性更强。

上边界限定的通配符(Upper Bounded Wildcards)

语法:

class-name<? extends superclass> var-name

例子:

public static double sum(List<? extends Number> list) {
double s = 0.0;
for (Number num : list) {
s += num.doubleValue();
} return s;
} List<? extends Number> list = new ArrayList<Integer>(); // OK
List<? extends Number> list = new ArrayList<Object>(); // error

特性:

  • List<? extends Number> list表示某种特定类型(Number或者Number的子类)对象的List。跟无边界通配符一样,因为无法确定持有的实际类型,所以这个List也不能add除null外的任何类型的对象
list.add(new Integer(1)); // error
list.add(null); // OK
  • 从list中获取对象是是可以的(比如get(0)),因为在这个List中,不管实际类型是什么,但肯定都能转型为Number。
Number n = list.get(0); // OK
Integer i = list.get(0); // error
  • 事实上,只要是形式参数有使用类型参数的方法,在使用无边界或者上边界限定的通配符的情况下,都不能调用。比如以java.util.ArrayList为例:
public E get(int index) // 可以调用
public int indexOf(Object o) // 可以调用
public boolean add(E e) // 不能调用

下边界限定的通配符(Lower Bounded wildcards)

语法:

class-name<? super subclass> var-name

例子:

public static void writeTo(List<? super Integer> list) {
// ...
} List<? super Number> list = new ArrayList<Number>(); // OK
List<? super Number> list = new ArrayList<Object>(); // OK
List<? super Number> list = new ArrayList<Integer>(); // error

特性:

  • List<? super Integer> list表示某种特定类型(Integer或者Integer的父类)对象的List。可以确定这个List持有的对象类型肯定是Integer或者其父类,所以往list里面add一个Integer或者其子类的对象是安全的,因为Integer或者其子类的对象都可以向上转型为Integer的父类对象。但是因为无法确定实际类型,所以往list里面add一个Integer的父类对象是不安全的。
list.add(new Integer(1)); // OK
list.add(new Object()); // error
  • 当从List<? super Integer> list获取具体的数据的时候,JVM在编译的时候知道实际类型可以是任何Integer的父类,所以为了安全起见,要用一个最顶层的父类对象来指向取出的数据,这样就可以避免发生强制类型转换异常了。
Object obj = list.get(0); // OK
Integer i = list.get(0); // error

PECS原则(Producer Extends Consumer Super)

从上面上边界限定的通配符和下边界限定的通配符的特性,可以知道:

  • 对于上边界限定的通配符,无法向其中加入任何对象,但是可以从中正常取出对象。
  • 对于下边界限定的通配符,可以存入subclass对象或者subclass的子类对象,但是取出时只能用Object类型变量指向取出的对象。

简而言之,上边界限定(extends)的通配符适合于内容的获取,而下边界限定(super)的通配符更适合于内容的存入。所以就有了一个PECS原则来很好的解释这两种通配符的使用原则。

  • 当一个数据结构作为producer对外提供数据的时候,应该只能取数据而不能存数据,所以适合使用上边界限定(extends)的通配符。
  • 当一个数据结构作为consumer获取并存入数据的时候,应该只能存数据而不能取数据,所以适合使用下边界限定(super)的通配符。
  • 如果既需要取数据也需要存数据,就不适合使用泛型的通配符。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}

Java泛型之通配符的更多相关文章

  1. Java泛型和通配符那点事

    泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法 ...

  2. java 泛型的通配符和限定

    package cn.sasa.demo1; import java.util.ArrayList; import java.util.Collection; import java.util.Ite ...

  3. Java 泛型、通配符? 解惑

    Java 泛型通配符?解惑 分类: JAVA 2014-05-05 15:53 2799人阅读 评论(4) 收藏 举报 泛型通配符上界下界无界 目录(?)[+] 转自:http://www.linux ...

  4. JAVA 泛型与通配符的使用

    泛型的本质是参数化类型.即所操作的数据类型被指定为一个参数. 1.jdk 1.5/1.6 必须显式的写出泛型的类型. 2.jdk 1.7/1.8 不必显式的写出泛型的类型. 一.泛型声明 可以用< ...

  5. Java 泛型和通配符解惑

    转自:http://www.linuxidc.com/Linux/2013-10/90928.htm T  有类型 ?  未知类型 一.通配符的上界 既然知道List<Cat>并不是Lis ...

  6. java 泛型与通配符(?)

    泛型应用于泛型类或泛型方法的声明. 如类GenericTest public class GenericTest<T> { private T item; public void set( ...

  7. java泛型之通配符?

    一.在说泛型通配符" ?" 之前先讲几个概念 1.里氏替换原则(Liskov Substitution Principle, LSP): 定义:所有引用基类(父类)的地方必须能透明 ...

  8. java 泛型: 通配符? 和 指定类型 T

    1. T通常用于类后面和 方法修饰符(返回值前面)后面 ,所以在使用之前必须确定类型,即新建实例时要制定具体类型, 而?通配符通常用于变量 ,在使用时给定即可 ? extends A  :  通配符上 ...

  9. [转]JAVA泛型通配符T,E,K,V区别,T以及Class<T>,Class<?>的区别

    原文地址:https://www.jianshu.com/p/95f349258afb 1. 先解释下泛型概念 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被 ...

随机推荐

  1. Fiddler实现篡改接口请求和返回数据

    步骤如下: 点击rules->Automatic Breakpoints,在这个选项下,我们可以看到三个可选项: Before Requests:在请求发出前拦截请求: After Reques ...

  2. WHAT IS GAME-BASED LEARNING?

    https://www.growthengineering.co.uk/what-is-game-based-learning/ Gamification and game-based learnin ...

  3. HTML5游戏引擎深度测评

    https://zhuanlan.zhihu.com/p/20768495 最近看到网上一篇文章,标题叫做<2016年 最火的 15 款 HTML5 游戏引擎>.目前针对HTML5游戏的解 ...

  4. 编程语言的类型修饰符modifiers

    编程语言修饰符,代表语言要素与常规表达不同的语义: 这些语义的不同需要编译器和运行时作出不同的解释: 作用域.访问: 生命周期: 同步异步: 多态: 纯函数: 注解: 懒加载: 编译器合成:

  5. Docker for Windows 使用命令报错

    could not read CA certificate "C:\Users\username\.docker\machine\machines\default\ca.pem": ...

  6. vim文本编辑器——替换、保存退出

    1.替换: (1)全文替换: 利用查询命令查询: (2)指定替换的字符串的范围: 2.保存.退出命令: (1)在命令行模式下保存(:w) (2)另存为(:w+要保存的文件的路径) (3)保存退出(:w ...

  7. luogu_3645: 雅加达的摩天楼

    雅加达的摩天楼 题意描述: 有\(N\)座摩天楼,从左到右依次编号为\(0\)到\(N-1\). 有\(M\)个信息传递员,编号依次为\(0\)到\(M-1\).编号为i的传递员最初在编号为\(B_i ...

  8. 第03组 Alpha冲刺(2/4)

    队名:不等式方程组 组长博客 作业博客 团队项目进度 组员一:张逸杰(组长) 过去两天完成的任务: 文字/口头描述: 制定了初步的项目计划,并开始学习一些推荐.搜索类算法 GitHub签入纪录: 暂无 ...

  9. [golang]数据库字典生成器-dataDictionary

    dataDictionary 介绍 生成数据字典 应用场景 根据mysql数据库生成html格式数据字典,目前仅支持macos/linux/windows 使用步骤 运行程序 windows: dat ...

  10. modis数据处理的坑(MOD02,mod03,mod04,MCD19A2)

    一直以来处理modis产品都是用的 modis conversion toolkit(MCTK),用 IDL 来跑感觉好像也没什么问题,好像并没有去验证结果到底对不对,因为用的气溶胶数据 MOD04_ ...