java 24-28

二十四、消除非受检警告:

      在进行泛型编程时,经常会遇到编译器报出的非受检警告(unchecked cast warnings),如:Set<Lark> exaltation = new HashSet(); 对于这样的警告要尽可能在编译期予以消除。对于一些比较难以消除的非受检警告,可以通过@SuppressWarnings("unchecked")注解来禁止该警告,前提是你已经对该条语句进行了认真地分析,确认运行期的类型转换不会抛出ClassCastException异常。同时要在尽可能小的范围了应用该注解(SuppressWarnings),如果可以应用于变量,就不要应用于函数。尽可能不要将该注解应用于Class,这样极其容易掩盖一些可能引发异常的转换。见如下代码:

  
  
  
  
  1. public <T> T[] toArray(T[] a) {  
  2.          if (a.length < size)  
  3.              return (T[])Arrays.copyOf(elements,size,a.getClass());  
  4.          System.arraycopy(elements,0,a,0,size);  
  5.          if (a.length > size)  
  6.              a[size] = null;  
  7.          return a;  
  8.      }  

   编译该代码片段时,编译器会针对(T[])Arrays.copyOf(elements,size,a.getClass())语句产生一条非受检警告,现在我们需要做的就是添加一个新的变量,并在定义该变量时加入@SuppressWarnings注解,见如下修订代码:

  
  
  
  
  1. public <T> T[] toArray(T[] a) {  
  2.          if (a.length < size) {  
  3.              //TODO: 加入更多的注释,以便后面的维护者可以非常清楚该转换是安全的。  
  4.              @SuppressWarnings("unchecked") T[] result =   
  5.                  (T[])Arrays.copyOf(elements,size,a.getClass());  
  6.              return result;  
  7.          }  
  8.          System.arraycopy(elements,0,a,0,size);  
  9.          if (a.length > size)  
  10.              a[size] = null;  
  11.          return a;  
  12.      }  

  这个方法可以正确的编译,禁止非受检警告的范围也减少到了最小。

      为什么要消除非受检警告,还有一个比较重要的原因。在开始的时候,如果工程中存在大量的未消除非受检警告,开发者认真分析了每一处警告并确认不会产生任何运行时错误,然而所差的是在分析之后没有消除这些警告。那么在之后的开发中,一旦有新的警告发生,极有可能淹没在原有的警告中,而没有被开发者及时发现,最终成为问题的隐患。如果恰恰相反,在分析之后消除了所有的警告,那么当有新警告出现时将会立即引起开发者的注意。

二十五、列表优先于数组:

      数组和泛型相比,有两个重要的不同点。首先就是数组是协变的,如:Object[] objArray = new Long[10]是合法的,因为LongObject的子类,与之相反,泛型是不可协变的,如List<Object> objList = new List<Long>()是非法的,将无法通过编译。因此泛型可以保证更为严格的类型安全性,一旦出现插入元素和容器声明时不匹配的现象是,将会在编译期报错。二者的另一个区别是数组是具体化的,因此数组会在运行时才知道并检查它们的元素类型约束。如将一个String对象存储在Long的数组中时,就会得到一个ArrayStoreException异常。相比之下,泛型则是通过擦除来实现的。因此泛型只是在编译时强化类型信息,并在运行时丢弃它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行交互。由此可以得出混合使用泛型和数组是比较危险的,因为Java的编译器禁止了这样的使用方法,一旦使用,将会报编译错误。见如下用例:

  
  
  
  
  1. public void test() {  
  2.          //这里我们先假设该语句可以通过编译  
  3.          List<String>[] stringLists = new List<String>[1];  
  4.          //该语句是正常的,intList中将仅包含值为42的一个整型元素  
  5.          List<Integer> intList = Arrays.asList(42);  
  6.          //该语句也是合法的,因为数组支持协变  
  7.          Object[] objects = stringLists;  
  8.          //由于泛型对象在运行时是擦除对象类型信息的,擦除后intList将变为List类型  
  9.          //而objects是Object类型的数组,List本身也是Object的子类,因此下面的语句合法。  
  10.          objects[0] = intList;  
  11.          //下面的语句将会抛出ClassCastException异常。很显然stringLists[0]是List<Integer>对象。  
  12.          String s = stringLists[0].get(0);  
  13.      }  

从以上示例得出,当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List<E>,而不是数组类型E[]。这样可能会损失一些性能或简洁性,但是换回的却是更高的类型安全性和互用性。见如下示例代码:

  
  
  
  
  1. static Object reduce(List l, Function f, Object initVal) {  
  2.          Object[] snapshot = l.toArray();  
  3.          Object result = initVal;  
  4.          for (Object o : snapshot) {  
  5.              return = f.apply(result,o);  
  6.          }  
  7.          return result;  
  8.      }  
  9.      interface Function {  
  10.          Object apply(Object arg1,Object arg2);  
  11.      }  

 事实上,从以上函数和接口的定义可以看出,如果他们被定义成泛型函数和泛型接口,将会得到更好的类型安全,同时也没有对他们的功能造成任何影响,见如下修改为泛型的示例代码: 

  
  
  
  
  1. static <E> E reduce(List<E> l,Function<E> f,E initVal) {  
  2.          E[] snapshot = l.toArray();  
  3.          E result = initVal;  
  4.          for (E e : snapshot) {  
  5.              result = f.apply(result,e);  
  6.          }  
  7.          return result;  
  8.      }  
  9.      interface Function<E> {  
  10.          E apply(E arg1,E arg2);  
  11.      }  

这样的写法回提示一个编译错误,即E[] snapshot = l.toArray();是无法直接转换并赋值的。修改方式也很简单,直接强转就可以了,如E[] snapshot = (E[])l.toArray();在强转之后,仍然会收到编译器给出的一条警告信息,即无法在运行时检查转换的安全性。尽管结果证明这样的修改之后是可以正常运行的,但是这样的写法确实也是不安全的,更好的办法是通过List<E>替换E[],见如下修改后的代码: 

  
  
  
  
  1. static <E> E reduce(List<E> l,Function<E> f,E initVal) {  
  2.          E[] snapshot = new ArrayList<E>(l);  
  3.          E result = initVal;  
  4.          for (E e : snapshot) {  
  5.              result = f.apply(result,e);  
  6.          }  
  7.          return result;  
  8.      }  

二十六、优先考虑泛型:

      如下代码定义了一个非泛型集合类: 

  
  
  
  
  1. public class Stack {  
  2.          private Object[] elements;  
  3.          private int size = 0;  
  4.          private static final int DEFAULT_INITIAL_CAPACITY = 16;  
  5.          public Stack() {  
  6.              elements = new Object[DEFAULT_INITIAL_CAPACITY];  
  7.          }  
  8.          public void push(Object e) {  
  9.              ensureCapacity();  
  10.              elements[size++] = e;  
  11.          }  
  12.          public Object pop() {  
  13.              if (size == 0)  
  14.                  throw new EmptyStackException();  
  15.              Object result = elements[--size];  
  16.              elements[size] = null;  
  17.              return result;  
  18.          }  
  19.          public boolean isEmpty() {  
  20.              return size == 0;  
  21.          }  
  22.          private void ensureCapacity() {  
  23.              if (elements.length == size)  
  24.                  elements = Arrays.copyOf(elements,2 * size + 1);  
  25.          }  
  26.      }  

 在看与之相对于的泛型集合实现方式: 

  
  
  
  
  1. public class Stack<E> {  
  2.          private E[] elements;  
  3.          private int size = 0;  
  4.          private static final int DEFAULT_INITIAL_CAPACITY = 16;  
  5.          public Stack() {  
  6.              elements = new E[DEFAULT_INITIAL_CAPACITY];  
  7.          }  
  8.          public void push(E e) {  
  9.              ensureCapacity();  
  10.              elements[size++] = e;  
  11.          }  
  12.          public E pop() {  
  13.              if (size == 0)  
  14.                  throw new EmptyStackException();  
  15.              E result = elements[--size];  
  16.              elements[size] = null;  
  17.              return result;  
  18.          }  
  19.          public boolean isEmpty() {  
  20.              return size == 0;  
  21.          }  
  22.          private void ensureCapacity() {  
  23.              if (elements.length == size)  
  24.                  elements = Arrays.copyOf(elements,2 * size + 1);  
  25.          }  
  26.      }  

 上面的泛型集合类Stack<E>在编译时会引发一个编译错误,即elements = new E[DEFAULT_INITIAL_CAPACITY]语句不能直接实例化泛型该类型的对象。修改方式如下:elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY],只要我们保证所有push到该数组中的对象均为该类型的对象即可,剩下需要做的就是添加注解以消除该警告:

  
  
  
  
  1. @SuppressWarning("unchecked")  
  2.      public Stack() {  
  3.          elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];  
  4.      }  

   总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。在设计新类型的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成是泛型的。

二十七、优先考虑泛型方法:

      和优先选用泛型类一样,我们也应该优先选用泛型方法。特别是静态工具方法尤其适合于范兴华。如Collections.sort()Collections.binarySearch()等静态方法。见如下非泛型方法: 

  
  
  
  
  1. public static Set union(Set s1, Set s2) {  
  2.          Set result = new HashSet(s1);  
  3.          result.addAll(s2);  
  4.          return result;  
  5.      }  

 这个方法在编译时会有警告报出。为了修正这些警告,最好的方法就是使该方法变为类型安全的,要将方法声明修改为声明一个类型参数,表示这三个集合的元素类型,并在方法中使用类型参数,见如下修改后的泛型方法代码:

  
  
  
  
  1. public static <E> Set<E> union(Set<E> s1,Set<E> s2) {  
  2.          Set<E> result = new HashSet<E>(s1);  
  3.          result.addAll(s2);  
  4.          return result;  
  5.      }  

和调用泛型对象构造函数来创建泛型对象不同的是,在调用泛型函数时无须指定函数的参数类型,而是通过Java编译器的类型推演来填充该类型信息,见如下泛型对象的构造:

      Map<String,List<String>> anagrams = new HashMap<String,List<String>>();

      很明显,以上代码在等号的两边都显示的给出了类型参数,并且必须是一致的。为了消除这种重复,可以编写一个泛型静态工厂方法,与想要使用的每个构造器相对应,如:

  
  
  
  
  1. public static <K,V> HashMap<K,V> newHashMap() {  
  2.          return new HashMap<K,V>();  
  3.      }  

 我们的调用方式也可以改为:Map<String,List<String>> anagrams = newHashMap();

      除了在以上的情形下使用泛型函数之外,我们还可以在泛型单例工厂的模式中应用泛型函数,这些函数通常为无状态的,且不直接操作泛型对象的方法,见如下示例:

  
  
  
  
  1. public interface UnaryFunction<T> {  
  2.          T apply(T arg);  
  3.      }  
  4.      private static UnaryFunction<Object> IDENTITY_FUNCTION   
  5.          = new UnaryFunction<Object>() {  
  6.              public Object apply(Object arg) {  
  7.                  return arg;  
  8.              }  
  9.          };  
  10.      @SuppressWarning("unchecked")  
  11.      public static <T> UnaryFunction<T> identityFunction() {  
  12.          return (UnaryFunction<T>)IDENTITY_FUNCTION;  
  13.      }  

调用方式如下:

  
  
  
  
  1. public static void main(String[] args) {  
  2.          String[] strings = {"jute","hemp","nylon"};  
  3.          UnaryFunction<String> sameString = identityFunction();  
  4.          for (String s : strings)  
  5.              System.out.println(sameString.apply(s));  
  6.            
  7.          Number[] numbers = {1,2.0,3L};  
  8.          UnaryFunction<Number> sameNumber = identityFunction();  
  9.          for (Number n : numbers)  
  10.              System.out.println(sameNumber.apply(n));  
  11.      }  

 对于该静态函数,如果我们为类型参数添加更多的限制条件,如参数类型必须是Comparable<T>的实现类,这样我们的函数对象便可以基于该接口做更多的操作,而不仅仅是像上例中只是简单的返回参数对象,见如下代码: 

  
  
  
  
  1. public static <T extends Comparable<T>> T max(List<T> l) {  
  2.          Iterator<T> i = l.iterator();  
  3.          T result = i.next();  
  4.          while (i.hasNext()) {  
  5.              T t = i.next();  
  6.              if (t.compareTo(result) > 0)  
  7.                  result = T;  
  8.          }  
  9.          return result;  
  10.      }  

总而言之,泛型方法就想泛型对象一样,提供了更为安全的使用方式。

二十八、利用有限制通配符来提升API的灵活性:  

      前面的条目已经解释为什么泛型不支持协变,而在我们的实际应用中可能确实需要一种针对类型参数的特化,幸运的是,Java提供了一种特殊的参数化类型,称为有限制的通配符类型(bounded wildcard type),来处理类似的情况。见如下代码:

  
  
  
  
  1. public class Stack<E> { 
  2.          public Stack(); 
  3.          public void push(E e); 
  4.          public E pop(); 
  5.          public boolean isEmpty(); 
  6.      } 

 现在我们需要增加一个方法:

  
  
  
  
  1. public void pushAll(Iterable<E> src) { 
  2.          for (E e : src) 
  3.              push(e); 
  4.      } 

 如果我们的E类型为Number,而我们却喜欢将Integer对象也插入到该容器中,现在的写法将会导致编译错误,因为即使IntegerNumber的子类,由于类型参数是不可变的,因此这样的写法也是错误的。需要进行如下的修改: 

  
  
  
  
  1. public void pushAll(Iterable<? extends E> src) {  
  2.          for (E e : src)  
  3.              push(e);  
  4.      }  

修改之后该方法便可以顺利通过编译了。因为参数中Iterable的类型参数被限制为E(Number)的子类型即可。

      既然有了pushAll方法,我们可能也需要新增一个popAll的方法与之对应,见如下代码: 

  
  
  
  
  1. public void popAll(Collection<E> dst) {  
  2.          while (!isEmpty())  
  3.              dst.add(pop());  
  4.      }  

  popAll方法将当前容器中的元素全部弹出,并以此添加到参数集合中。如果Collections中的类型参数和Stack完全一致,这样的写法不会有任何问题,然而在实际的应用中,我们通常会将Collection中的元素视为更通用的对象类型,如Object,见如下应用代码:

      Stack<Number> numberStack = new Stack<Number>();

      Collection<Object> objs = createNewObjectCollection();

      numberStack.popAll(objs);

      这样的应用方法将会导致编译错误,因为ObjectStackNumber参数类型是不匹配的,而我们对目标容器中对象是否为Number并不关心,Object就已经满足我们的需求了。为了到达这种更高的抽象,我们需要对popAll做如下的修改: 

  
  
  
  
  1. public void popAll(Collection<? super E> dst) {  
  2.          while (!isEmpty())  
  3.              dst.add(pop());  
  4.      }  

  修改之后,之前的使用方式就可以顺利通过编译了。为参数集合的类型参数已经被修改为E(Number)的超类即可。

      这里给出了一个助记方式,便于我们记住需要使用哪种通配符类型:

      PECS(producer-extends, consumer-super)

      解释一下,如果参数化类型表示一个T生产者,就使用<? extends T>,如果它表示一个T消费者,就使用<? super T>。在我们上面的例子中,pushAllsrc参数产生E实例供Stack使用,因此src相应的类型为Iterable<? extends E>popAlldst参数通过Stack消费E实例,因此dst相应的类型为Collection<? super E>PECS这个助记符突出了使用通配符类型的基本原则。

      在上一个条目中给出了下面的泛型示例函数:

        public static <E> Set<E> union(Set<E> s1, Set<E> s2);

       这里的s1s2都是生产者,根据PECS原则,它们的声明可以改为:

      public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2);

      由于泛型函数在调用时,其参数类型是可以通过函数参数的类型推演出来的,如果上面的函数被如下方式调用时,将会导致Java的编译器无法推演出泛型参数的实际类型,因此引发了编译错误。

       Set<Integer> integers = new Set<Integer>();

      Set<Double> doubles = new Set<Double>();

      Set<Number> numbers = union(integers,doubles);

      如果想顺利通过编译并得到正确的执行结果,我们只能通过显示的方式指定该函数类型参数的实际类型,从而避免了编译器的类型参数自动推演,见修改后的代码:

      Set<Number> numbers = Union.<Number>union(integers,doubles);   

      现在我们再来看一下前面也给出过的max方法,其初始声明为:

      public static <T extends Comparable<T>> T max<List<T> srcList);

      下面是修改过的使用通配符类的声明:

      public static <T extends Comparable<? super T>> T max(List<? extends T> srcList);

      下面将逐一给出新声明的解释:

      1.    函数参数srcList产生了T实例,因此将类型从List<T>改为List<? extends T>

      2.    最初T被指定为扩展Comparable<T>,然而Comparable又是T的消费者,用于比较两个T之间的顺序关系。因此参数化类型Comparable<T>被替换为Comparable<? super T>

      注:ComparatorComparable一样,他们始终都是消费者,因此Comparable<? super T>优先于Comparable<T>

你可能感兴趣的:(java,职场,休闲)