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 }    

 

posted @ 2018-06-25 14:11  wutingjia  阅读(391)  评论(0编辑  收藏  举报