0%

Effective Java 第三版 (4) 泛型

泛型

§ 不要使用原始类型

原始类型是指 List 而不是 List<E>,在 Java 1.5 之后应该没有使用原始类型的理由了。这样会失掉泛型在安全性和表述性方面的优势。

使用参数化的类型允许插入任意对象,如 List<Object> 还是可以的。

List 和 参数化类型 List<Object> 区别是,前者逃避了 泛型检查,后者明确告知编译器,能够执有任意类型的对象。

如:List<String> 可以传递给 List 的参数,但是不能将它传给类型 List<Object> 的参数。泛型有子类型化的规则,List<String> 是原始类型 List 的一个子类型,而不是参数化类型 List<Object> 的子类型。

应该通过 无限制通配符类型Set<?> 取代原始类型。

泛型信息可以在运行时被擦除,所以当使用 instanceof 操作符时,应当使用无限制通配符类型:

1
2
3
if (o instanceof Set) {
Set<?> m = (Set<?>) o;
}

这是使用泛型类型的 instanceof 运算符的首选方法。

  • List<String> 参数化的类型
  • String 实际类型参数
  • List<E> 泛型
  • E 形式类型参数
  • List<?> 无限制通配符类型
  • List 原始类型
  • <E extends Number> 有限制类型参数
  • <T extends Comparable<T>> 递归类型限制
  • List<? extends Number> 有限制通配符类型
  • static <E> List<E> asList(E[] a) 泛型方法
  • String.class 类型令牌

§ 消除非受检警告

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有这种情况下才可以用一个 @SuppressWarngings("unchecked") 注解来禁止这条警告。

SupressWarning 注解可以用在任何粒度的级别中,从单独局部变量声明到整个类都可以,应该始终在尽可能小的范围中使用 SuppressWarnings。如 ArrayList 类中的 toArray 方法。

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

§ 列表优于数组

数组是协变的,意思是如果 Sub 为 Super 的子类型,那么数组类型 Sub[] 就是 Super[] 子类型。

泛型则是不可变的。对于任意两个不同的类型 Type1 和 Type2,List<Type1>List<Type2> 既不是子类型也不是父类型。

下面代码是合法的:

1
2
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";

但下面的代码不合法:

1
2
List<Object> ol = new ArrayList<Long>();
ol.add("I don't fit in");

使用列表可以在 编译时 发现错误,而数组只能在 运行时 发现错误。

数组是被具体化的,数组在运行时才能检查它们的元素类型约束。

§ 优先考虑泛型

当我们编写泛型类时,每当编写一个由数组支持的泛型时,你不能创建一个不可具体化类型的数组,示例代码:

1
2
3
4
5
6
7
private E[] elements;
private static final int DEFAULT_INITIAL_CAPACITY = 16
...


// 报错
elements = new E[DEFAULT_INITIAL_CAPACITY]

有两种方法来解决,一般第一种方法更易读一些,也更简洁,只需要搞一次。

第一种:

1
2
3
// 这种做法会有一个警告,通过添加注解来抑制警告。
@SuppressWarning("unchecked")
elements = (E[]) new Ojbect[DEFAULT_INITIAL_CAPACITY];

第二种:

1
2
// 将属性字段修改为 Object[]
private Object[] elements;

但是这样的话,我们需要在返回泛型的方法,对其进行转换或进行警告抑制。

1
2
3
4
5
6
public E pop() {
...
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
...
}

§ 优先考虑泛型方法

集合中的所有“算法”方法都是泛型的。如 binarySearchsort

下面的代码可以编译但是有两个警告:

1
2
3
4
5
// 抛出警告
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1); result.addAll(s2);
return result;
}

修复警告的方法:

1
2
3
4
5
public static <E> Set<E> union(Set<E> s1, Set<E> s2) { 
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}

§ 利用有限制通配符提升API的灵活性

Iterable<? extends E>

§ 合理的结合泛型和可变参数

可变参数的目的是允许客户端将一个可变数量的参数传递给一个方法,但这是一个脆弱的抽象。

当你调用一个可变参数方法时,会创建一个 数组 来保存可变参数,那个应该是实现细节的数组是可见的。 因此,当可变参数具有泛型或参数化类型时,会导致编译器警告混淆。

§ 优先考虑类型安全的异构容器

泛型最常用于集合,如 SetMap,它们都充当了被参数化了的容器,这样限制你每个容器只能有固定数目的类型参数。一个 Set 只有一个类型参数,表示它的元素类型;一个 Map 有两个类型参数,表示它的键和值得类型。

将键进行参数化而不是将容器参数化,然后将参数化的键提交给容器,来插入或者获取值。用泛型系统来确保值得类型与它的键相符。

有时有你需要更多的灵活性,比如,数据库一行记录可以具有任意多的列,能够以类型安全的方式访问它们是很好的。

Java 1.5 之后类 Class 被泛型化了,类的类型不再是简单的 Class,而是 Class<T>。例如 String.class 属于 Class<String> 类型。

1
2
3
4
public class Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
}

Favorites 类被参数化以后,就可以存储任意实例了:

1
2
3
4
5
6
7
8
9
10
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 123);
f.putFavorite(Class.class, Favorites.class);

String favoriteString = f.getFavorite(String.class);
int FavoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);

System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName());

Favorites 实例是类型安全的,并且是异构的。它所有的键都是不同类型的。所以它是类型安全的异构容器。

注意 Java 中的换行符可以使用 "%n",而不是 "\n",因为 "%n" 可以与平台无关。

以下是完整实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Favorites {

// Map 不保证键和值的之间的类型关系
private Map<Class<?>, Object> favorites = new HashMap<>();

public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}

public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}

注意,这里使用了 Classcase 方法,case 的签名充分利用了 Class 类是泛型的事实。它的返回类型是 Class 对象的类型参数。