zoukankan      html  css  js  c++  java
  • 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 }    
  • 相关阅读:
    DOM对象和JQuery对象的区别
    处理android手机html5页面中,点击text文本框无法自动获取焦点的处理方法
    ORACLE之VBO-5530无法删除用户的解决办法
    当oracle clob类型不能和group by并用,但是需要去除多列重复
    Android 4主线程访问网络
    Android: How to get Address from geolocation using Geocoder
    解决乱码的最后方法
    tomcat启动时自动运行代码
    android 组件隐蔽显示状态
    android模拟器Genymotion 连接eclipse项目
  • 原文地址:https://www.cnblogs.com/WutingjiaWill/p/9224071.html
Copyright © 2011-2022 走看看