原文点此链接

使用通配符的原因: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. 原生js的常用方法总结

    =============== 通知: 博主已迁至<掘金>码字,博客园可能以后不再更新,掘金地址:https://juejin.im/post/5a1a6a6551882534af25a8 ...

  2. 前端性能----CDN

    Content Distribute Network(内容分发网络)是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡.内容分发.调度等功能模块,使用户就近获取所需内容 ...

  3. React Virtual DOM Explained in Simple English

    If you are using React or learning React, you must have heard of the term “Virtual DOM”. Now what is ...

  4. Vue的数据双向绑定原理——Object-defineProperty

    一.定义 ①方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. ②vue.js的双向数据绑定就是通过Object.defineProperty方法实现的,俗称属性拦截 ...

  5. 鸿蒙OS与谷歌Fuchsia

    鸿蒙,意在“开天辟地”,它的征程是物联网.跨终端,是一款战略性产品.它真正对标的不是安卓,而是谷歌最新研发的操作系统Fuchsia. 根据Fuchsia中文社区的介绍,在安卓和 Chrome OS 两 ...

  6. 细说PHP-fpm

    在理解php-fpm之前,我们要先搞清楚几个关键词以及他们之间的关系:CGI FastCGI:(Fast Common Gateway Interface),即快速通用网关接口,是一种让交互程序与We ...

  7. C++中list的用法总结

    list简介 list 也是顺序容器的一种.只是list 是一个双向链表.使用 list 需要包含头文件 list.双向链表的每个元素中都有一个指针指向后一个元素,也有一个指针指向前一个元素,如下图所 ...

  8. unity:坐标变换 - 两个函数

    在cocos中,我们知道有如下的坐标变换函数: CCPoint convertToNodeSpace(const CCPoint& worldPoint);CCPoint convertToW ...

  9. 深度学习最全优化方法总结比较及在tensorflow实现

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u010899985/article/d ...

  10. java wait方法

    wait方法是让当前线程等待,这里的当前线程不是指t,而是主线程. wait会释放锁,等到其他线程调用notify方法时再继续运行. 可以看下面的例子. 1 package com.citi.test ...