23.不要在新代码中使用原生态类型
简介
Java泛型从1.5
引入,为了保持兼容性,实现的是伪泛型,类型参数信息在编译完成之后都会被擦除,其在运行时的类型都是raw type
,类型参数保存的都是Object
类型,List<E>
的raw type
就是List
- 编译器在编译期通过类型参数,为读操作自动进行了类型强制转换,同时在写操作时自动进行了类型检查
- 如果使用
raw type
,那编译器就不会在写操作时进行类型检查了,写入错误的类型也不会报编译错误,那么在后续读操作进行强制类型转换时,将会导致转换失败,抛出异常
List
与List<Object>
- 前者不具备类型安全性,后者具备
- 可以将
List<String>
传递给List
的参数,但不能传递给List<Object>
的参数 - 泛型有子类化(
subtyping
)规则,List<String>
是List
的子类型,但不是List<Object>
的子类型
Set<?>
与Set
区别
当不确定类型参数,或者说类型参数不重要时,也不应该使用
raw type
,而应该使用List<?>
- 任何参数化的
List
均是List<?>
的子类 - 通配符是类型安全的,原生态是不安全的/
- 被引用的
List<?>
,并不能向其中加入任何非null
元素,读取出来的元素也是Object
类型,而不会被自动强转为任何类型 instanceof
不支持泛型
术语:
24.消除非受检警告
原则
- 要尽可能的消除每一个 非受检警告
- 清除警告能够确保不会出现
ClassCastException
- 如果无法消除警告,且确信是安全的,可添加注解
@SuppressWarnings("unchecked")
- 应该尽量
减小注解作用的范围
,通常是应该为一个赋值语句添加注解
- 非受检警告很重要,不要忽视他们,每一条警告都预示着这里会在运行时出现一些问题.
25.列表优于数组
简介
- 数则是协变的,而泛型是不可变的,如下示例:
// Fails at runtime
Object[] objects = new Long[1];
objects[0] = "Throws ArrayStoreException";
// Won`t compile
List<Object> objectList = new ArrayList<Long>();
objectList.add("Incompatible types");
- 数组是具体化的,只有在
运行时
才能知道并检查他们的元素类型约束
. - 泛型则是通过
擦除
来实现,在编译时就知道元素的类型 - 不能返回
泛型元素
的数组,必须是具体化类型(即E[]
是类型不安全的) - 在结合使用
可变参数方法
和泛型
时会出现令人费解的警告
总结
- 数组是协变且可以具体化的.泛型是不可变且可以被擦除的.
- 数组提供了
运行时
类型安全,泛型提供了编译时
类型安全
26.优先考虑泛型
简介
第25 条 说明不能创建
不可具体化
类型的数组,如E[]
.如下面这种情况:
elements = new E[DEFAULT_CAPACITY];
解决这种问题,有两种方式
创建一个
Object
数组,并转换成泛型数组类型
将
elements
域的 类型 从E
改为Object[]
这两种方式编译器都无法再运行时类型检验,因此需要自己添加注解.
@SuppressWarning("unchecked")
elements = (E[])new Object[DEFAULT_CAPACITY];
总结
- 有时看似与
item 25
矛盾,实属无奈,Java
原生没有List
,原生只支持数组,ArrayList
不得不基于数组实现,HashMap
也是基于数组实现的 - 泛型不支持原生类型,比如
List<int>
会报编译时错误,使用包装类型即可解决.List<Integer>
- 泛型比使用者进行
cast
更加安全,而且由于Java泛型的擦除
实现,也可以和未做泛型的老代码无缝兼容
27.优先考虑泛型方法
简介
静态工具方法尤其
适合
用泛型
化,编写泛型方法
与编写泛型类型
类似.
// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
- 泛型方法无需指定
类型参数
的值.利用有限制的通配符
可以使方法变得更加灵活. - 类型安全如
<T extends Comparable<T>>
,可以读作”针对可以与自身进行比较每个类型T”.
总结
- 泛型方法像
泛型类
一样,需要传入泛型参数
- 为了消除
泛型声明冗余
,可以使用静态工厂方法,如下:
Map<String,List<String>> map = new HashMap<String,List<String>>();
public static <K, V> HashMap<K, V> newHashMap() {
return new HashMap<K, V>();
}
- 方法使用者就不用类型转换即可使用
28.利用有限制通配符限制API的灵活性
语义:
使用
<? extends T>
: 表示T生产者
或者<? super E>
: 表示E消费者
不要用通配符
作为返回值 类型.
示例
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
- 这样的化, 这样就可以将
E
和E的子类
放入 集合中.popAll
同理- 因为泛型是不可变的,就好像
List<String>
不是List<Object>
的子类一样.但可以通过这种方式来解决.
- 下面是一个通过
使用通配符
修正的类型声明
//遵循了开头的三个规则
public static <T extends Comparable<? super T>> T max(List<? extends T> list{}
显示的类型参数
如果编译器不能推断
类型.我们也可以显示的指定类型
,如:
Set<Number> numbers = Union.<Number>(integers,doubles);
建议
public static <E> void swap(List<E> list,int i,int j);
public static void swap(List<?> list,int i,int j);//使用通配符
- 如上所示,如果类型参数
只在方法声明中出现
一次,应该用通配符
替代 - 但是上面的
通配符
方法会编译错误.不能把null之外的任何值
放进List<?>
(参考第23
条),通常通过helper
的方式解决 - 所有的
comparable
和comparator
都是消费者.
// 不导出,对用户不可见
private static <E> void swapHelper(List<E> list,int i,int j){
list.set(i,list.set(j,list.get(i));
}
public static void swap(List<?> list,int i,int j){
swapHelper(list,i,j);
};
29.优先考虑类型安全的异构容器
介绍
使用泛型限制了
每个容器只能有
固定数目的类型参数
,有时候我们需要跟过的灵活性,比如 如下示例:
示例: Favorites.java
public <T> void putFavorite(Class<T> type, T instance) {}
public <T> T getFavorite(Class<T> type) {}
这里,
存入和取出
的是不同类型 也可以满足,而且保证了类型安全
,而不是像Map<K,V>
在声明时就将类型固定
了,这种 被称为类型安全的异构容器
.
TypeToken
自 1.5
开始 java
支持 Class<T>
,即 :
String.class 属于 Class<String> 类型
Integer.class 属于 Class<Integer> 类型
这种 当一个类 的字面文字 被用在方法中,来传达 编译时和运行时的类型信息时.被称作
type token
解惑
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
如上,虽然
Class<?>
使用了 通配符,却 不涉及到使用通配符容器不能存入任何非null的元素
[参考 23 条], 因为它属于键key
,而不是map 容器
局限
Favorites
类并不是运行时安全
的.如原生态类型的
类,HashSet等,可如下实现安全的运行时类型.
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, type.cast(instance));
}
如上这种技巧,普遍的用在了
checkedList
,checkedMap
中.
Favorites
类不能用在不可具体化的
类型中.就是说,可以保存String
,String[]
,但是不能保存List<String>
,因为List<String>.class
是不合法的.尽管可以有TypeToken
总结
泛型限制了 容器
使用固定数目的 类型参数,我们可以将类型参数
放在 键上
,而不是容器上,
来避开这一限制,
这种方式叫做 异构容器