Effective Java 3nd 笔记-第五章 泛型

401 阅读5分钟
原文链接: mp.weixin.qq.com

第五章 泛型

第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);	}}