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的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被 ...
随机推荐
- php长连接和短连接区别
短连接 连接->传输数据->关闭连接(推荐学习:PHP编程从入门到精通) 比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接. 具体 ...
- 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 ...
- DoubleArrayTrie
/** * DoubleArrayTrie: Java implementation of Darts (Double-ARray Trie System) * * <p> * Copyr ...
- 探索Windows 10的CFG机制
转载https://www.anquanke.com/post/id/85493 0x00 前言 随着操作系统开发人员一直在增强漏洞利用的缓解措施,微软在Windows 10和Windows 8.1 ...
- ent 基本使用六 Mixin
ent 的Mixin 可以让我们服用已有的schema Mixin 接口说明 type Mixin interface { Fields() []ent.Field } 一个demo 代码 // -- ...
- 5.学习springmvc响应json数据
一.配置不过滤静态资源文件:用来访问webapp中js,css,images文件 1.修改springmvc.xml <!--配置前端控制器,用于设置哪些资源不拦截--> <mvc: ...
- Hawq架构
Hawq采用分层架构,将MPP shared-nothing的计算层架在HDFS之上. Hawq集群中有三种角色:master, namenode和segment hosts. 1.Master负责认 ...
- maven 无法引入包 报错 处理方式
<!--orderquery thrift client定义--> <dependency> <groupId>com.sankuai.qcs</groupI ...
- 如何将 普通代码变成 java lamband表达式
public static void main(String[] args) { Map<String, String> map = new HashMap<>(); List ...
- Spring的注解收集
@Scope("prototype")spring 默认scope 是单例模式scope="prototype" 可以保证 当有请求的时候 都创建一个Actio ...