第 26 条:请不要使用原生态类型

声明中具有一个或多个类型参数的类或者接口,就是泛型(generic)。

例如List接口只有单个类型参数E, 表示列表的元素类型。这个接口全称List<E>,泛型类和接口统称为泛型(generic type)。

每一种泛型都定义一个原生态类型(raw type),即不带任何实际类型参数的泛型名称。

它的存在主要是为了兼容泛型之前的代码。

 

如果使用原生态类型,就失去了泛型在安全性和描述性方面的优势。

如果使用像List这样的原生态类型,就会失掉类型安全性,但是如果使用像List<Object>这样的参数化类型,则不会。

如何使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。

例如,泛型Set<E>的无限制通配符类型为Set<?>。这是最普通的参数化Set类型,可以持有任何集合。

通配符是类型安全的,原生态类型不安全。

不要使用原生类型,有2个例外。

1. 必须在类文字中使用原生态类型。

例如:List.class, String[].class, int.class是合法,但是List<String.class>和List<?>.class是不合法。

2. 由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof操作符是非法的。

利用泛型来使用instanceof操作符的首选方法,例如:

if (o instanceof Set) {
Set<?> s = (Set<?>) o;
}

总之,使用原生态类型会在运行时导致异常。原生态类型只是为了与遗留代码兼容。

第 27 条:消除非受检的警告

用泛型编程会遇到很多编译器警告:

非受检转换警告(unchecked cast warning)、非受检方法调用警告、非受检参数化可变参数类型警告(unchecked parameterized vararg type warning)

以及非受检转换警告(unchecked conversion warning)。

例如:

Set<Lark> exaltation = new HashSet();

编译器会提醒出错地方:warning:[unchecked] unchecked conversion

Java7中开始引入菱形操作符(<>),编译器可以推断出正确的实际参数类型.

Set<Lark> exaltation = new HashSet<>(); 

尽可能地消除每一个非受检警告。

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,才可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。

SuppressWarnings可以用在任何颗粒度,应该尽量小的范围使用SuppressWarnings注解。

每当使用SuppressWarnings(“unchecked”)注解时,都要添加一条注解,说明为什么这么做是安全的。

总之,每一条警告都表示可能在运行时抛出ClassCastException,要尽量消除这些警告。

第 28 条:列表优于数组

数组与泛型相比有两个重要不同点。

第一,数组是协变的(covariant),泛型是可变的(invariant)

表示如果Sub为Super的子类型,那么数组Sub[]就是Super[]的子类型。

对于泛型,不同类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是超类型。

例如,不能将String放入Long容器中,但是数组只会在运行时发现,用列表,则可以在编译时发现错误。

第二,数组是具体化的,泛型是通过擦除来实现的。

数组在运行时知道和强化他们的元素类型,如果将String放入Long数组中,会得到ArrayStoreException.

泛型在编译时强化他们的类型信息,在运行时丢弃(擦除)他们的元素信息。擦除使泛型可以与没有泛型的代码互用。

创建泛型、参数化类型或者类型参数的数组是非法的。

例如:new List<E>[]、new List<String>[]和new E[]。

创建泛型数组不是类型安全的。

优先使用集合类型List<E>,而不是数组类型E[],可能会损失一些性能,但换回是更高的类型安全性和互动性。

总之,数组和泛型有完全不同的类型规则。数组是协变且可以具体化的,泛型是不可变且可以被擦除的。

因此,数组提供了运行时的类型安全,但没有编译时的类型安全,反之,泛型也一样。数组和泛型不能很好地混合使用,优先用列表代替数组。

第 29 条: 优先考虑泛型

第28条鼓励优先使用列表而非数组。实际上不可能总是在泛型中使用列表。

Java并不是生来就支持列表,因此有些泛型如ArrayList必须在数组上实现。为了提升性能,其他泛型如HashMap也在数组上实现。

总之,使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更容易。

第 30 条: 优先考虑泛型方法

静态工具方法尤其适合于泛型化。Collections中所有“算法”方法(例如binarySearch和sort)都泛型化了。

public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}

这个方法可以编译,但有2条警告。

为了修正警告,使方法变成类型安全的,将方法声明修改为一个类型参数(type parameter),表示这三个集合元素类型(两个参数和一个返回值),并在方法中使用类型参数。

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

声明类型参数的类型参数列表,处在方法的修饰符及其返回值之间。

总之,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并且返回值的方法更安全,也更容易。

就像类型一样,应该确保方法不用转换就能使用,还应该将现有方法泛型化,使新用户使用起来更轻松,且不会破坏现有的客户端。

第31 条:利用有限制通配符来提升API的灵活性

Java提供了一种特殊的参数化类型,称为有限制的通知符类型(bounded wildcard type)。

例如,E的某个子类型的Iterable接口,通配符类型Iterable<? extends E>。

public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}

另一种,E的某种超类的集合,通用符Collection<? super E>。

public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}

为了获取最大限度的灵活性,要在表示生产者或消费者的输入参数上使用通配符类型。

PESC表示producer – extends, consumer – super.

如果参数化类型表示一个生产者T,就用<? extends T>; 如果它表示一个消费者T,就用<? super T>。

例如第30条的union方法:

public static <E> Set<E> union(Set<E> s1, Set<E> s2)

用PESC原则:

public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)

注意:返回类型仍然是Set<E>,不要用通配符类型作为返回类型。

修改声明后:

Set<Integer> ints = Set.of(1, 3, 5);
Set<Double> dob = Set.of(2.0, 4.0, 6.0);
Set<Number> num = union(ints, dob);

第30条的max方法,初始声明:

public static <T extends Comparable<T>> T max(List<T> list)

修改使用通配符后:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

这是将通配符运用到类型参数。参数化类型Comparable<T>被有限制通配符类型Comparable<? super T>取代。 comparable是消费者,因此使用时始终应该是Comparable<? super T>优先于Comparable<T>。对于comparator接口也一样,使用时始终应该是Comparator<? super T>优先于Comparator<T>

还有一点,类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明。如下:

public static <E> void swap(List<E> list, int i, int j); // 使用无限制的类型参数

public static void swap(List<?> list, int i, int j); // 使用无限制的通配符

在公共API中,第二种更好些,因为它更简洁。

如果类型参数只在一个方法声明中出现一次,就可以使用通配符取代它。

总之,在API中使用通配符需要技巧,会是API变得很灵活。如果编写是将被广泛使用的类库,则一定要适当地利用通配符类型。基本原则:producer – extends, consumer – super.(PECS)。还有所有的comparable和comparator都是消费者。

第 32 条:谨慎并用泛型和可变参数

可变参数(vararg)方法和泛型都是Java5出现的,但他们不能良好地相互作用。

可变参数作用在于让客户端能够将可变数量的参数传给方法,当调用一个可变参数方法时,会创建一个数组用来存放可变参数。这个数组是一个实现细节,可见的。因此,当可变参数有泛型或者参数化类型时,编译警告信息就会产生。

当一个参数化类型的变量指向一个不是该类型对象时,会产生堆污染(heap pollution)。它导致编译器的自动生成转换失败,破坏了泛型系统的基本保证。

例如,第28条代码改编:

static void deangerous(List<String>… stringLists){
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
object[0] = intList; // heap pollution
String s = stringLists[0].get(0); // ClassCastException
}

上述最后一行代码中有一个不可见转换,由编译器生成。这个转换失败证明类型不安全。

因此将值保存在泛型可变参数数组参数中是不安全的。

在Java7中,增加了SafeVarargs注解,它让带泛型vararg参数的方法的设计者能够自动禁止客户端的警告。通过方法的设计者承诺声明这是类型安全的。

确定何时使用SafeVarargs注解的规则:对于每一个带有泛型可变参数或者参数化类型的方法,都要用@SafeVarargs进行注解。

安全使用泛型可变参数的例子:

@SafeVarargs
static <T> List<T> flatten(List<? extends T>… lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}

如不想使用@SafeVarargs,也可用一个List参数代替可变参数:

static <T> List<T> flatten(List<List<? extends T>> lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}

总之,可变参数和泛型不能良好地合作。如果选择编写带泛型(或参数化)可变参数的方法,首先要确保该方法时类型安全的,然后用@SafeVarargs对它进行注解。

第 33 条:优先考虑类型安全的异构容器

泛型最常用于集合,如Set<E>和Map<K,V>,以及单个元素的容器,如ThreadLocal<T>和AtomicReference<T>。限制每个容器只能有固定数目的类型参数。

有时候需要更多灵活性,将键(key)进行参数化而不是将容器(container)参数化,然后将参数化的键提交给容器来插入或者获取值。用泛型来确保值得类型与它的键相符。

例如:

public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorites(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
} public <T> T getFavorites(Class<T> type) {
return type.cast(favorites.get(type));
}
}

每个Favorites实例得到一个favorites的私有Map<Class<?>, Object>的支持。每个键都可以有一个不同的参数化类型:一个是Class<String>, 接下来是Class<Integer>,异构就是从这里来的。

getFavorites的cast方法时Java转换操作的动态模拟。它只检验它的参数是否为Class对象所表示的类型的实例。如果是,就返回参数,否则抛出ClassCastException异常。

总之,集合API说明了泛型的一般用法,限制每个容器只有固定数目的类型参数。你可以通过将类型参数放在键上而不是容器上来避开这一限制。对于类型安全的异构容器,可以用Class对象作为键。

最新文章

  1. 《python核心编程》读书笔记——列表解析
  2. MVC————扩展方法MvcHtmlString
  3. Interface/接口
  4. 如此低价的ZBrush,你能想象?
  5. hiho一下 第九十六周 数论五&#183;欧拉函数
  6. Codeforces Round #189 (Div. 1) B. Psychos in a Line 单调队列
  7. .net Signalr 使用笔记
  8. content-type 组件
  9. JavaScript中Object.keys用法
  10. Linux编程 23 shell编程(结构化条件判断 命令if -then , if-then ... elif-then ...else,if test)
  11. net core体系-API-1Ocelot-(2)继续深入
  12. goLand工程结构管理
  13. 关于pythoh面向过程开发人员三步转面向对象的补充,再加一步,四步走战略。转面向对象也可以有固定公式。
  14. 64位Ubuntu下配置CP-ABE环境
  15. win10 安装IIS说明操作
  16. Easyui1.3.4+IIS6.0+IE8兼容问题解决
  17. 工程优化暨babel升级小记
  18. 笔记-scrapy-selector
  19. 关于Tomcat服务器接收到的请求参数乱码的问题
  20. 尽管以C++为基础,但 Java 是一种更纯粹的面向对象程序设计语言

热门文章

  1. Android Studio 证书问题
  2. springboot 部署到tomcat中,项目总是重新部署
  3. go语言学习(基本数据类型)
  4. 【Day5】2.反爬策略之代理IP
  5. asp.net中数据库事务管理
  6. java之高并发与多线程
  7. oracle 12c 安装补丁报错
  8. Java集合--TreeMap
  9. jaxb生成pojo类返回类型为布尔值的问题
  10. JVM系列-001-JVM监控工具