第五章 泛型
第26条 请不要使用原生态类型
-
原生态类型指的是没有类型参数的泛型,例如List是原生态类型,List<String>不是。
-
如果使用原生态类型,就失掉了泛型在安全性和描述性方面的所有优势。
-
不能将任何元素放到Collection<?>中。
-
使用原生态类型的几个例外:
-
必须在类文字中使用源生态类型,即List.class, String[].class合法,但是List<String.class>和List<?>.class不合法。
-
instanceof 操作符:
-
总而言之:Set<Object>是个参数化类型,表示可以包含任何对象类型的一个集合;Set<?>则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set是一个原生态类型,它脱离了泛型系统。前两种是安全的,最后一种不安全。
if(o instanceof Set) { Set<?> s = (Set<?>)o; }
第27条 消除非受检的警告
-
要尽可能地消除每一个非受检警告。
-
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有在这种情况下,才可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告。
-
应该始终在尽可能小的范围内使用SuppressWarnings注解。
-
永远不要在整个类上使用SuppressWarnings注解
-
每当使用SuppressWarnings("unchecked")注解时,都要添加一条注释,说明为什么这么做是安全的。
-
总而言之,非受检警告很重要,不要忽略它们。
第28条 列表优于数组
-
数组域泛型不同点:
-
数组是协变,泛型是可变的:Object[] a = new Long[1]; a[1] = "232"; 编译不错,运行出错。
-
数组是具体化的,泛型是通过擦除来实现的:数组会在运行时强化它们的元素类型,泛型只在编译时强化它们的类型信息,并在运行时丢弃它们的类型信息。
-
数组和泛型不能很好地混合使用
-
发生编译时错误或警告,第一反应应该是用列表代替数组。
第29条 优先考虑泛型
-
值得花些时间去学习如何编写自己的泛型
-
总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。
第30条 优先考虑泛型方法
-
静态工具方法尤其适合泛型化
-
泛型单例工厂
-
递归类型限制
-
总而言之,泛型方法就像泛型一样,使用起来比要求客户端输入参数并返回值的方法来得更加安全,也更加容易。就像类型一样,你应该确保方法不用转换就能使用。这通常意味着要将它们泛型化。
public static <E> Set<E> union(Set<E> s1, Set<E> s2) { Set<E> result = new HashSet<E>(s1); result.addAll(s2); return result; }
/*** 泛型单例工厂*/private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;@SuppressWarnings("unchecked") public static <T> UnaryOperator<T> identityFunction() { return (UnaryOperator<T>) IDENTITY_FN; }
/** * 递归类型限制 */ public static <E extends Comparable<E>> E max(Collection<E> c) { if (c.isEmpty()) { throw new IllegalArgumentException("Empty Collection"); } E result = null; for (E e : c) { if (e == null || e.compareTo(result) >0) { result = Objects.requireNonNull(e); } } return result; }
第31条 利用有限制通配符来提升API的灵活性
-
每个类型都是自身的子类型
-
为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。
-
PECS: producer-extends, consumer-super, Comparator与comparable都是消费者
-
不要用通配符类型作为返回类型
-
通配符对类的用户来说几乎是无形的。如果类的用户必须考虑通配符类型,类的API或许就会出错
-
如果类型参数只在方法声明中出现一次,就可以用通配符取代它,如果是无限制的类型参数,就用无限制的通配符取代它;如果是有限制的类型参数,就用有限制的通配符取代它
-
总而言之,在API中使用通配符比较需要技巧,但是会使API变得灵活得多。
Iterable<? extends E> d;Iterable<? super E> dd;
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) { Set<E> result = new HashSet<E>(s1); result.addAll(s2); return result; }
public static <E extends Comparable<? super E>> E max(Collection<? extends E> c) { if (c.isEmpty()) { throw new IllegalArgumentException("Empty Collection"); } E result = null; for (E e : c) { if (e == null || e.compareTo(result) >0) { result = Objects.requireNonNull(e); } } return result;}
/*** 第二种更好*/public static <E> void swap(List<E> list, int i, int j);public static void swap(List<?> list, int i, int j);
/*** 通配符方法可能需要编写辅助方法*/public static void swap(List<?> list, int i, int j) { swapHelp(list, i, j); } public static <E> void swapHelp(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); }
第32条 谨慎并用泛型和可变参数
-
允许另一个方法访问一个泛型可变参数数组是不安全的。两种例外情况
-
将数组传给另一个用@SafeVarags正确注解过的可变参数方法是安全的。
-
将数组传给只计算数组内容部分函数的非可变参数方法也是安全的。
-
@SafeVarrags 注解
-
总而言之,可变参数和泛型不能良好地合作,这是因为可变参数设施是构建在顶级数组之上的一个技术露底,泛型数组有不同的类型规则。
第33条 优先考虑类型安全的异构容器
-
C语言中使用\n的地方,在Java中应该使用%n。这个%n会产生适用于特定平台的行分隔符,在许多平台上是\n,但并非所有平台都是如此。
-
Favorites是类型安全的异构容器:异构在于favorites中键值中使用通配符Class<?>
import java.util.HashMap;import java.util.Map;import java.util.Objects;public class Favorites { private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>(); public <T> void putFavorite(Class<T> type, T instance) { favorites.put(Objects.requireNonNull(type), type.cast(instance)); } public <T> T getFavorite(Class<T> type) { return type.cast(favorites.get(type)); } public static void main(String[] args) { Favorites f = new Favorites(); f.putFavorite(String.class, "java"); f.putFavorite(Integer.class, 0xcafebabe); f.putFavorite(Class.class, Favorites.class); String favoriteString = f.getFavorite(String.class); int favoriteInt = f.getFavorite(Integer.class); Class<?> favoriteClass = f.getFavorite(Class.class); System.out.printf("%s %x %s %n", favoriteString, favoriteInt, favoriteClass); }}