Java泛型之通配符
使用通配符的原因: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泛型之通配符的更多相关文章
- Java泛型和通配符那点事
泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法 ...
- java 泛型的通配符和限定
package cn.sasa.demo1; import java.util.ArrayList; import java.util.Collection; import java.util.Ite ...
- Java 泛型、通配符? 解惑
Java 泛型通配符?解惑 分类: JAVA 2014-05-05 15:53 2799人阅读 评论(4) 收藏 举报 泛型通配符上界下界无界 目录(?)[+] 转自:http://www.linux ...
- JAVA 泛型与通配符的使用
泛型的本质是参数化类型.即所操作的数据类型被指定为一个参数. 1.jdk 1.5/1.6 必须显式的写出泛型的类型. 2.jdk 1.7/1.8 不必显式的写出泛型的类型. 一.泛型声明 可以用< ...
- Java 泛型和通配符解惑
转自:http://www.linuxidc.com/Linux/2013-10/90928.htm T 有类型 ? 未知类型 一.通配符的上界 既然知道List<Cat>并不是Lis ...
- java 泛型与通配符(?)
泛型应用于泛型类或泛型方法的声明. 如类GenericTest public class GenericTest<T> { private T item; public void set( ...
- java泛型之通配符?
一.在说泛型通配符" ?" 之前先讲几个概念 1.里氏替换原则(Liskov Substitution Principle, LSP): 定义:所有引用基类(父类)的地方必须能透明 ...
- java 泛型: 通配符? 和 指定类型 T
1. T通常用于类后面和 方法修饰符(返回值前面)后面 ,所以在使用之前必须确定类型,即新建实例时要制定具体类型, 而?通配符通常用于变量 ,在使用时给定即可 ? extends A : 通配符上 ...
- [转]JAVA泛型通配符T,E,K,V区别,T以及Class<T>,Class<?>的区别
原文地址:https://www.jianshu.com/p/95f349258afb 1. 先解释下泛型概念 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被 ...
随机推荐
- Ubuntu 18.04 环境下安装 Matlab2018
由于实验环境要求,最近在 Ubuntu 18.04 上安装了 Matlab2018b , 这里简单记录过程. (1) 首先是获取对应的 Matlab2018b 的安装包,这里笔者是在一个外国的网站上获 ...
- log4j2 配置文件解读
1.日志相关介绍 日志接口(slf4j):slf4j是对所有日志框架制定的一种规范.标准.接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用,比如log4j.l ...
- 爬虫 -- UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe6 in position 301: unexpected end of data
errors参数有3个值:strict, ignore, replace html.decode("utf-8"),这种形式有时会报错,那么修改为下面形式,将decode函数 ...
- PipelineWise illustrates the power of Singer
转自:https://www.stitchdata.com/blog/pipelinewise-singer/ Stitch is based on Singer, an open source st ...
- 为什么vue组件中的data不是一个对象而是一个函数
如果两个实例引用同一个对象,当其中一个实例的属性发生改变时,另一个实例属性也随之改变,只有当两个实例拥有自己的作用域时,才不会相互干扰. 这是因为JavaScript的特性所导致,在component ...
- kernel 获取ntoskrnl.exe基址
标题: kernel shellcode之寻找ntoskrnl.exe基址 http://scz.617.cn:8/windows/201704171416.txt 以64-bits为例,这是Eter ...
- planning algorithms chapter 2
planning algorithms chapter 2 :Discrete Planning 离散可行规划导论 问题定义 在离散规划中,状态是"可数"的,有限的. 离散可行规划 ...
- PowerDesigner应用02 逆向工程之导出PDM文件前过滤元数据(表、视图、存储过程等)
在上一篇文章<PowerDesigner应用01 逆向工程之配置数据源并导出PDM文件>步骤二中导出了目标数据库对应的PDM文件, 该文件中展示出了所有表的信息与关系. 某些业务场景下只需 ...
- zabbix TCP 连接数监控
直接上配置: 1.修改配置 cat userparameter_tcp_connect.conf UserParameter=tcp_connect.established,/opt/app/zabb ...
- 论文阅读 | Recurrent Attentional Reinforcement Learning for Multi-label Image Recognition
源地址 arXiv:1712.07465: Recurrent Attentional Reinforcement Learning for Multi-label Image Recognition ...