第五章_泛型_effective_java 第二十四条:消除非受检警告 第二十五条:列表优先于数组 第二十六条:优先考虑泛型 && 第二十七条:优先考虑泛型方法 第二十七条:利用有限制通配符来提升API灵活性 第二十九条:类型安全的异构容器

Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。之前set都是没有<>,可以接受任何类型的参数,而错误插入可以通过编译和运行,直到取出结果时才会报错。Java深度历险(五)——Java泛型

泛型与类型擦除有关,下面一段简单讲了下原理和特性

正确理解泛型概念的首要前提是理解类型擦除(type erasure)。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。
很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:

  • 泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class
  • 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
  • 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>MyException<Integer>的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。

因为泛型可以将错误提前在编译时发现,所有建议尽量使用泛型而不是原生态类型(即Set<Object> 而不是Set)。其中Set<Object>是参数化类型,表示可以包含任何对象类型的一个集合;Set<?>是一个通配符,表示只能包含某种位置对象类型的一个集合;Set是原生态类型。前两种是安全的,最后一种是不安全的

第二十四条:消除非受检警告

尽可能消除每一个非受检警告。如果确定警告本身没问题,可以用注解@SuppressWarnings("unchecked").

第二十五条:列表优先于数组

数组和泛型的不同有两点:

  • 数组是协变的,如果sub是super的子类,那么sub[]就是super[]的子类。但是对于不同类型T1和T2,List<T1>既不是List<T2>的超类,也不是子类。
  • 数组是具体化的,在运行时才检查元素类型约束;泛型是通过擦除来实现,编译时会强化类型信息。

不能创建泛型数组,因为泛型数组会带来语法问题。当得到泛型数组创建错误时(通常是混合使用泛型和数组),最好的办法是有限使用集合类型List<E>.

在基于性能的考虑下,也可优先用数组 - -

第二十六条:优先考虑泛型 && 第二十七条:优先考虑泛型方法

主要讲了泛型类和泛型方法的使用。其中重要的就在于你何时进行类型转换,转换时要保证安全性。同时注意<T extends T>的运用

第二十七条:利用有限制通配符来提升API灵活性

因为List<A>List<B>互不为子类,所以会出现以下情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package org.effectivejava.examples.chapter05.item28;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class <E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
("unchecked")
public () {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
// pushAll method without wildcard type - deficient!
// public void pushAll(Iterable<E> src) {
// for (E e : src)
// push(e);
// }
// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
// popAll method without wildcard type - deficient!
// public void popAll(Collection<E> dst) {
// while (!isEmpty())
// dst.add(pop());
// }
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
public static void main(String[] args) {
Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);
numberStack.pushAll(integers);
Collection<Object> objects = new ArrayList<Object>();
numberStack.popAll(objects);
}
}

如果使用注释里的push和pop,会因为Error:(31, 37) java: 不兼容的类型: java.lang.Iterable<java.lang.Integer>无法转换为java.lang.Iterable<java.lang.Number>这类不兼容问题而无法通过编译。因为虽然Integer是Number的子类,但是Iterable用了泛型,所以Iterable<Integer> 不是Iterable<Number>的子类,无法转换。处理这种问题就需要利用有限通配符。

有限通配符分为<? extends T>(表示T的某个子类)和 <? super T>(表示T的某个父类)两种。一般再用通配符时遵守PECS原则:producer-extends,consumer-super。比如pushAll的参数src产生E的实例供stack使用,则用extends;popAll消费了Stack的E实例,则用super。同时,所有的comparablehe comparator都是消费者。

第二十九条:类型安全的异构容器

类型安全的异构容器主要是将Class<?>作为key,Object作为value。在插入的时候保证type!=null,取出的时候通过type.cast进行类型转换,从而实现类型检查。比如下面的favorite(重点关注12行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Favorites {
// Typesafe heterogeneous container pattern - implementation
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));
}
}

但是这里每个类都只能有一个value,感觉不是很实用,难道要value再是一个map?或者主要是这种Class<?>背后的类型令牌的思想吧。验证一个类的类型除了用instanceof还可以用c1.asSubclass(c)函数判断c1是不是c的子类,如果是的话该函数返回c1。具体可以看这篇文章.

另外他讲了下Collections的checkedList<E>等包装类,他就是对普通List<E>做了包装,操作时进行typeCheck保证插入的对象严格是E类。


本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证,欢迎转载,但转载请注明来自http://thousandhu.github.io,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://thousandhu.github.io/2016/10/09/第五章-泛型-effective-java/