Effective Java 4
Item 27 不要使用原始类型
1 // Raw collection type - don't do this! 2 // My stamp collection. Contains only Stamp instances. 3 private final Collection stamps = ... ;
1 // Erroneous insertion of coin into stamp collection 2 stamps.add(new Coin( ... )); // Emits "unchecked call" warning
// Raw iterator type - don't do this! for (Iterator i = stamps.iterator(); i.hasNext(); ) Stamp stamp = (Stamp) i.next(); // Throws ClassCastException stamp.cancel();
1、使用原始类型不会产生编译期错误,但会产生运行期错误,增加debug难度。
1 // Parameterized collection type - typesafe 2 private final Collection<Stamp> stamps = ... ;
2、虽然使用原始类型是合法的,但是不应该这样做,这会丧失类型安全以及泛型在表达方面的优势。
3、必须使传递含有参数类型的实例 给 被设计为原始类型的方法 合法,反之亦然。这就是所谓的移植性,为了兼容之前版本。
4、List<String> 是原始类型List 的子类 但不是 List<Object>的子类。
5、使用原始类型会失去类型安全但是 List<Object>不会。
6、当不确定方法的具体参数类型时,也不要使用原始类型。
1 // Use of raw type for unknown element type - don't do this! 2 static int numElementsInCommon(Set s1, Set s2) { 3 int result = 0; 4 for (Object o1 : s1) 5 if (s2.contains(o1)) 6 result++; 7 return result; 8 }
而应该使用<?>:
1 // Uses unbounded wildcard type - typesafe and flexible 2 static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }
7、在 class literals 中必须使用原始类型,例如List,class,String[].class。
8、在 instanceof 操作符中必须使用原始类型:
1 // Legitimate use of raw type - instanceof operator 2 if (o instanceof Set) { // Raw type 3 Set<?> s = (Set<?>) o; // Wildcard type 4 ... 5 }
注意第三行的类型转换是必须的。
9、<Object>是指能放入任何对象,<?>是指能放入 任何一种 目前未知的对象
Item 28 比起数组优先使用List
1、数组是协变的,即a是b的子类 则a[]是b[]的子类;List<a>则不是List<b>的子类。
2、List在编译期就会指出类型不匹配:
1 // Fails at runtime! 2 Object[] objectArray = new Long[1]; 3 objectArray[0] = "I don't fit in"; // Throws ArrayStoreException
1 // Won't compile! 2 List<Object> ol = new ArrayList<Long>(); // Incompatible types 3 ol.add("I don't fit in");
3、arrays的类型是具体的;而泛型是通过编译期确定类型然后在运行期擦除。
4、创建一个泛型数组或是参数化类型的数组是非法的。例如,new List<E>[], new List<String>[], new E[]。
5、一般来说不可能在一个泛型集合中返回元素的数组。
6:
1 // Chooser - a class badly in need of generics! 2 public class Chooser { 3 private final Object[] choiceArray; 4 public Chooser(Collection choices) { 5 choiceArray = choices.toArray(); 6 } 7 public Object choose() { 8 Random rnd = ThreadLocalRandom.current(); 9 return choiceArray[rnd.nextInt(choiceArray.length)]; 10 } 11 }
在调用其中的choose()的方法后,必须手动转化类型,否则会导致运行错误。
1 // A first cut at making Chooser generic - won't compile 2 public class Chooser<T> { 3 private final T[] choiceArray; 4 public Chooser(Collection<T> choices) { 5 choiceArray = choices.toArray(); //choiceArray = (T[]) choices.toArray(); 6 } 7 // choose method unchanged 8 }
使用注释后的语句会变成一个warning,泛型的类型信息将会在被运行时擦除,因此编译器无法保证类型转化的正确。
1 // List-based Chooser - typesafe 2 public class Chooser<T> { 3 private final List<T> choiceList; 4 public Chooser(Collection<T> choices) { 5 choiceList = new ArrayList<>(choices); 6 } 7 public T choose() { 8 Random rnd = ThreadLocalRandom.current(); 9 return choiceList.get(rnd.nextInt(choiceList.size())); 10 } 11 }
Item 29 支持泛型类型
1、泛型话一个类,首先是在类的声明中添加一个或多个参数类型,用于代表这个类中元素的类型。
2、然后将其中所有Object类型替换为 E的相关类型:
1 public class Stack<E> { 2 private E[] elements; 3 private int size = 0; 4 private static final int DEFAULT_INITIAL_CAPACITY = 16; 5 6 public Stack() { 7 elements = new E[DEFAULT_INITIAL_CAPACITY]; 8 } 9 10 public void push(E e) { 11 ensureCapacity(); 12 elements[size++] = e; 13 } 14 15 public E pop() { 16 if (size == 0) 17 throw new EmptyStackException(); 18 E result = elements[--size]; 19 elements[size] = null; // Eliminate obsolete reference 20 return result; 21 } 22 ... // no changes in isEmpty or ensureCapacity 23 }
3、值得注意是由于你不能创建一个未定型的数组 因此 将Obiect[],替换为E[]会导致编译出错。
4、解决方法是 一是仍然保留在第6行的Object[],然后在其前面增加E[] 进行强制转型。但是会产生一个unchecked warning。二是在elements声明处,保留为Object[].但是在18行会出现错误,需要手动转型,同样会产生一个unchecked warning。
1 // Appropriate suppression of unchecked warning 2 public E pop() { 3 if (size == 0) 4 throw new EmptyStackException(); 5 // push requires elements to be of type E, so cast is correct 6 @SuppressWarnings("unchecked") E result =(E) elements[--size]; 7 elements[size] = null; // Eliminate obsolete reference 8 return result; 9 }
5、 注意使用Stack<int>这种基本类型是不行的,需要使用装箱类型。
Item 30 支持泛型方法
1、例子:
1 // Uses raw types - unacceptable! (Item 26) 2 public static Set union(Set s1, Set s2) { 3 Set result = new HashSet(s1); 4 result.addAll(s2); 5 return result; 6 }
1 // Generic method 2 public static <E> Set<E> union(Set<E> s1, Set<E> s2) { 3 Set<E> result = new HashSet<>(s1); 4 result.addAll(s2); 5 return result; 6 }
2、泛型单例工厂:
1 // Generic singleton factory pattern 2 private static UnaryOperator<Object> IDENTITY_FN = (t) -> t; 3 4 @SuppressWarnings("unchecked") 5 public static <T> UnaryOperator<T> identityFunction() { 6 return (UnaryOperator<T>) IDENTITY_FN; 7 }
1 // Sample program to exercise generic singleton 2 public static void main(String[] args) { 3 String[] strings = { "jute", "hemp", "nylon" }; 4 UnaryOperator<String> sameString = identityFunction(); 5 for (String s : strings) 6 System.out.println(sameString.apply(s)); 7 8 Number[] numbers = { 1, 2.0, 3L }; 9 UnaryOperator<Number> sameNumber = identityFunction(); 10 for (Number n : numbers) 11 System.out.println(sameNumber.apply(n)); 12 }
3、T可以和Comparable<T>进行比较。
4:互相比较:
1 // Using a recursive type bound to express mutual comparability 2 public static <E extends Comparable<E>> E max(Collection<E> c);
1 // Returns max value in a collection - uses recursive type bound 2 public static <E extends Comparable<E>> E max(Collection<E> c) { 3 if (c.isEmpty()) 4 throw new IllegalArgumentException("Empty collection"); 5 E result = null; 6 for (E e : c) 7 if (result == null || e.compareTo(result) > 0) 8 result = Objects.requireNonNull(e); 9 return result; 10 }
Item 31 使用绑定通配符增加API的灵活性
1、参数类型是不变量,也就是说不管Type1和Type2是什么关系,List<Type1>与List<Type2>不会有继承关系。
2、例子 :
1 public class Stack<E> { 2 public Stack(); 3 public void push(E e); 4 public E pop(); 5 public boolean isEmpty(); 6 }
如果想增加一个addAll方法:
1 // pushAll method without wildcard type - deficient! 2 public void pushAll(Iterable<E> src) { 3 for (E e : src) 4 push(e); 5 }
如果要进行如下操作:
1 Stack<Number> numberStack = new Stack<>(); 2 Iterable<Integer> integers = ... ; 3 numberStack.pushAll(integers);
会出现在错误,因为虽然Integer是Number的子类,但是作为参数类型是不变量。
因此应该使用如下绑定通配符:
1 // Wildcard type for a parameter that serves as an E producer 2 public void pushAll(Iterable<? extends E> src) { 3 for (E e : src) 4 push(e); 5 }
同理:
1 // Wildcard type for parameter that serves as an E consumer 2 public void popAll(Collection<? super E> dst) { 3 while (!isEmpty()) 4 dst.add(pop()); 5 }
3、关于使用super与extend的规律:
PECS producer-extends consumer-super
例如 push生产E实例给Stack用,pop调用用Stack中的E将其排出。
4、注意返回类型不能使用绑定通配符。
5、例子:
1 public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2)
Set<Integer> integers = Set.of(1, 3, 5); Set<Double> doubles = Set.of(2.0, 4.0, 6.0); Set<Number> numbers = union(integers, doubles);
然而在Java 8之前需要指定类型:
1 Set<Number> numbers = Union.<Number>union(integers, doubles);
6、 永远使用 Comparable<? super T> Comoarator类似。
7、如果一个类型参数在声明中只出现一次,使用相应的通配符进行代替。例如:
1 public static <E> void swap(List<E> list, int i, int j); 2 public static void swap(List<?> list, int i, int j);
然后他的一种直接的实现无法通过编译:
1 public static void swap(List<?> list, int i, int j) { 2 list.set(i, list.set(j, list.get(i))); 3 }
错误的原因是 不能将任何值除了null放进List<?>中!
解决方法如下:实质就是使用第一种声明方式:
1 public static void swap(List<?> list, int i, int j) { 2 swapHelper(list, i, j); 3 } 4 // Private helper method for wildcard capture 5 private static <E> void swapHelper(List<E> list, int i, int j) { 6 list.set(i, list.set(j, list.get(i))); 7 }
这样多此一举的好处是,使外部的使用者简单明了的直接使用,而不需要看见具体繁琐的声明。
Item 32 谨慎的结合泛型和可变参数(参数个数可变)
1、可变参数方法是 抽象泄漏 (leaky abstraction)的。因为当你使用可变参数方法时,会创建一个数组用于储存可变参数,而这个数组作为实现细节原本应该是不应该可见的,可是在这里可见了。
2、非具体类型是指在运行时的信息比编译期时的信息少的类型,几乎所有泛型和参数类型都是非具体类型。
3、如果把非具体类型作为可变参数 ,在它的声明处会出现一个编译器警告。会提示产生 堆污染 (heap pollution):
1 // Mixing generics and varargs can violate type safety! 2 static void dangerous(List<String>... stringLists) { 3 List<Integer> intList = List.of(42); 4 Object[] objects = stringLists; 5 objects[0] = intList; // Heap pollution 6 String s = stringLists[0].get(0); // ClassCastException 7 }
4、将一个值储存在泛型可变参数数组是不安全的。
5、在Java 7中 添加了 @SafeVarargs 注解用来 抑制由于使用了 泛型可变参数 而产生的警告。只有在确认这个方法真的安全的情况下才去使用!
6、如果在作为 方法参数 的 泛型可变参数 的 数组中 该方法不改变这个数组 并同时 不让其他不可信代码 获得这个数组的引用,那他就是安全的。也就是 参数仅仅在调用者和方法间进行传递!
7、例子:
1 // UNSAFE - Exposes a reference to its generic parameter array! 2 static <T> T[] toArray(T... args) { 3 return args; 4 }
1 static <T> T[] pickTwo(T a, T b, T c) { 2 switch(ThreadLocalRandom.current().nextInt(3)) { 3 case 0: return toArray(a, b); 4 case 1: return toArray(a, c); 5 case 2: return toArray(b, c); 6 } 7 throw new AssertionError(); // Can't get here 8 }
编译的时候,编译器创建了一个泛型可变数组将两个T实例传递给 toArray。 而这个数组的类型是Object[].
1 public static void main(String[] args) { 2 String[] attributes = pickTwo("Good", "Fast", "Cheap"); 3 }
会产生类型转化错误 因为无法将Object[]转化为 String[]。也就是之前所说的 不让其他不可信代码 获得这个数组的引用
8、正确的使用方法:
1 // Safe method with a generic varargs parameter 2 @SafeVarargs 3 static <T> List<T> flatten(List<? extends T>... lists) { 4 List<T> result = new ArrayList<>(); 5 for (List<? extends T> list : lists) 6 result.addAll(list); 7 return result; 8 }
9、注意只能使用在不能复写的类上 在Java 8z中这个注解只能用在静态方法或是final实例方法上,Java9中静态实例方法也可以使用。
10:另一种选择:
1 // List as a typesafe alternative to a generic varargs parameter 2 static <T> List<T> flatten(List<List<? extends T>> lists) { 3 List<T> result = new ArrayList<>(); 4 for (List<? extends T> list : lists) 5 result.addAll(list); 6 return result; 7 }
1 audience = flatten(List.of(friends, romans, countrymen));
1 static <T> List<T> pickTwo(T a, T b, T c) { 2 switch(rnd.nextInt(3)) { 3 case 0: return List.of(a, b); 4 case 1: return List.of(a, c); 5 case 2: return List.of(b, c); 6 } 7 throw new AssertionError(); 8 } 9 10 public static void main(String[] args) { 11 List<String> attributes = pickTwo("Good", "Fast", "Cheap"); 12 }
Item 33 考虑各式各样类型安全的容器
1、有时候可能对于容器需要多个参数,这时可以将 键(key)参数化而不是容器 也就是说将一个原本单类型的容器 变成一个map。其中key存放类型(使用Class类) value存放原本的值:
1 // Typesafe heterogeneous container pattern - API 2 public class Favorites { 3 public <T> void putFavorite(Class<T> type, T instance); 4 public <T> T getFavorite(Class<T> type); 5 }
1 // Typesafe heterogeneous container pattern - client 2 public static void main(String[] args) { 3 Favorites f = new Favorites(); 4 f.putFavorite(String.class, "Java"); 5 f.putFavorite(Integer.class, 0xcafebabe); 6 f.putFavorite(Class.class, Favorites.class); 7 String favoriteString = f.getFavorite(String.class); 8 int favoriteInteger = f.getFavorite(Integer.class); 9 Class<?> favoriteClass = f.getFavorite(Class.class); 10 System.out.printf("%s %x %s%n", favoriteString, 11 favoriteInteger, favoriteClass.getName()); 12 }
1 // Typesafe heterogeneous container pattern - implementation 2 public class Favorites { 3 private Map<Class<?>, Object> favorites = new HashMap<>(); 4 public <T> void putFavorite(Class<T> type, T instance) { 5 favorites.put(Objects.requireNonNull(type), instance); 6 } 7 public <T> T getFavorite(Class<T> type) { 8 return type.cast(favorites.get(type)); 9 } 10 }
第一点 由于未绑定通配符并非是map 的而是内部key的因此可以将任何类型的放入。
第二点 其中 值(value)的类型是object.也就是说没有保证 键与值的类型关系,每个值的类型由它的键所表示。
第三点 getFavorite 使用了动态类型转化,根据类型取出的值是Object的让后将其转化为相应的 T 类型。
1 public class Class<T> { 2 T cast(Object obj); 3 }
接受Object类型 返回T 类型。
1 // Achieving runtime type safety with a dynamic cast 2 public <T> void putFavorite(Class<T> type, T instance) { 3 favorites.put(type, type.cast(instance)); 4 }
Favourite类不能存放非具体类,否则无法通过编译。因为List<>不管是什么参数类型其Class都是LIst.
对于子类的情况:
1 // Use of asSubclass to safely cast to a bounded type token 2 static Annotation getAnnotation(AnnotatedElement element,String annotationTypeName) { 3 Class<?> annotationType = null; // Unbounded type token 4 try { 5 annotationType = Class.forName(annotationTypeName); 6 } 7 catch (Exception ex) { 8 throw new IllegalArgumentException(ex); 9 } 10 return element.getAnnotation(annotationType.asSubclass(Annotation.class)); 11 }