• 和我一起学Effective Java之泛型


    泛型

    不要在新代码中使用原始类型

    泛型(generic):声明中具有一个或多个类型参数

    原始类型(raw type):不带任何实际类型参数的泛型名称

    格式: 类或接口的名称 < 对应于泛型形式类型参数的实际参数 >

    List<String> 就是对应于List<E>的实际参数为String的参数化类型

    如与List<E>对应的原始类型是List

    优点:

    • 在编译时发现插入类型的错误(越早发现错误越好,编译时发现好于运行时发现)
    • 不再需要手工转换类型
       //JDK5之前的写法,使用的是原始类型
        private static final List stringList = new ArrayList();
    
        //有了泛型之后的写法,使用泛型
        private static final List<String> stringList2 = new ArrayList<String>();
    
        //JDK7 能将后面<>里的类型省略,被称为Diamond
        private static final List<String> stringList3 = new ArrayList<>();
    
        public static void main(String[] args) {
            String str = "test";
    
            Integer integer = 1;
    
            stringList.add(str);
            stringList.add(integer);//可通过编译,但之后报ClassCastException错误
    
            stringList2.add(str);
    //        stringList2.add(integer);//无法通过编译
    
            for(Iterator iterator = stringList.iterator();iterator.hasNext();){
                String string = (String) iterator.next();
                System.out.println(string);
            }
            for(Iterator iterator = stringList2.iterator();iterator.hasNext();){
                String string =  iterator.next();
                System.out.println(string);
        }
    

    ListList<Object>之间的区别?

    List逃避了泛型检查,List<Object>则是告知编译器,它能够持有任意类型的对象

    无限制的通配符类型:
    使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。如List<?>

    泛型信息在运行时会被擦除

    学习链接:

    1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

    2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java

    下面通过一个小demo说明类型擦除

          //类型擦除
            List<String> stringList = new ArrayList<>();
            List<Integer> integerList = new ArrayList<>();
            System.out.println(stringList.getClass().toString());
            System.out.println(integerList.getClass().toString());
            System.out.println(stringList.getClass()==integerList.getClass());
    
    
    
            integerList.add(100);
            Method method = integerList.getClass().getMethod("add",Object.class);
            method.invoke(integerList,"abc");
    
            System.out.println(integerList);
    
    

    运行结果:

    一般不在代码中使用原始类型,除了两种例外情况(都是因为泛型信息在运行时会被擦除):

    • 1.在类文字(class literals)中
    如:
    List.class,String[].class,int.class都合法
    List<String>.class,List<String>.class都不合法
    
    • 2.instanceof
      if(o instanceof Set){   //原始类型(Raw Type)
      Set<?> set = (Set<?>)o;//通配符类型(WildCard Type)
    }
    

    下面的表格是泛型相关的术语:
    泛型相关的术语

    下面这张图很好的介绍了无限制通配符和其他泛型符号之间的关系:

    消除非受检警告

    始终在尽可能小的范围内使用SuppressWarnings注解

    Java源码中的ArrayList类中有个toArray方法,其中就有强转的警告:

     @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            if (a.length < size)
                // Make a new array of a's runtime type, but my contents:
                return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
    
    

    最好是将范围进一步缩小。将注解由整个方法到局部的变量声明上去。

     @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            if (a.length < size){
                @SuppressWarnings("unchecked")
                T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass());
               return result;
               }
            System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
    
    

    列表优于数组

    • 数组是协变的(covariant),泛型则是不可变的
    
       Object[] objectArray = new String[1];
       List<Object> objectList = new ArrayList<String>();//无法通过编译 imcompatible types
       // String类是Object类的子类
       //String[]是Object[]的子类
       //而List<String>并不是List<String>的子类型  
    
    • 数组是具体化的(reified),在运行时才知道并检查它们的元素类型约束。而泛型通过擦除来实现的。泛型只在编译时强化类型信息,并在运行时擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码可以互用。
    
              Object[] objectArray = new String[1];
              List<String> objectList = new ArrayList<String>();
              objectArray[0] = 3;//可通过编译,运行时报错
    //        objectList.add(1);//编译时报错
    
    

    数组和泛型不能很好地混合使用。可用列表代替数组。

    总结:数组是协变且可具体化的,泛型是不可变的且可被擦除的。-->数组提供了运行时类型安全而编译时类型不安全。而泛型反之。

    优先考虑泛型

    泛型相比于Object的优点:

    • 不需要强制类型转换
    • 编译时类型安全
    public class SomeClazz<T> {
        public Object dosthWithObj(Object obj){
            return obj;
        }
        
        public T dosthWithT(T t){
            return t;
        }
    
        public static void main(String[] args) {
            SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
            Foo foo = new Foo();
            Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
            Foo foo2 = someClazz.dosthWithT(foo);
        }
    }
    
    
    public class Stack<E> {
        private E [] elements;
        private static final int MAX_SIZE = 16;
        private int size = 0;
    
        @SuppressWarnings("unchecked")
        public Stack(){
            elements = (E[]) new Object[MAX_SIZE];
        }
    
        public void push(E e){
            ensureSize();
            elements[size++]=e;
        }
    
        public E pop(){
            if(size==0)
                throw new EmptyStackException();
            E e = elements[--size];
            elements[size]=null;
            return e;
        }
    
        private void ensureSize() {
            if(size==elements.length){
                elements= Arrays.copyOf(elements,2*size+1);
            }
        }
    
        public static void main(String[] args) {
            Stack<Integer> stack = new Stack<>();
            for(int i =0;i<50;i++){
                stack.push(i);
            }
            for(int i = 0;i<10;i++){
                System.out.println(i+": "+stack.pop());
            }
        }
    
    
    }
    class EmptyStackException extends RuntimeException{
    
    }
    

    前面曾鼓励优先使用列表而不是数组。并不意味着所有的泛型中都要使用列表。况且Java并不是生来就支持列表的。

    每个类型都是它自身的子类型。

    如有 SomeClazz<E extends Number>
    
        SomeClazz<Number>是合法的
    

    优先考虑泛型方法

    方法可以考虑泛型化,特别是静态工具方法。

    泛型方法语法:

    方法修饰语 泛型 返回值 方法名()

    public static <T> T foo(T args);

    /**
         * 使用泛型方法
         * 返回两个集合的联合
         * @param s1
         * @param s2
         * @param <E>
         * @return
         *
         * 局限:两个参数和返回的结果的类型必须全部相同
         * 解决方法:使用有限制的通配符
         */
        public static <E> Set<E> unionGeneric(Set<E> s1,Set<E> s2){
            Set<E> result = new HashSet<>(s1);
            result.addAll(s2);
            return result;
        }
    
    public static <K,V> Map<K,V> newHashMap(){
            return new HashMap<K,V>();
        }
    

    泛型单例工厂:

     public interface UnaryFunction<T>{
            T apply(T arg);
        }
        private static UnaryFunction<Object> IDENTITY_FUNCTION =
                new UnaryFunction<Object>() {
                    @Override
                    public Object apply(Object arg) {
                        return arg;
                    }
                };
    
        @SuppressWarnings("unchecked")
        public static <T> UnaryFunction<T> identityFunction(){
            return (UnaryFunction<T>) IDENTITY_FUNCTION;
        }
    
        /**
         * 每次都要创建一个,很浪费,而且它是无状态的.
         * 泛型被具体化了,每个类型都需要一个恒等函数,但是它们被擦除后,就只需要一个泛型单例了.
         * @param <T>
         * @return
         */
        public static <T> UnaryFunction<T> identityFunction2(){
            return new
                    UnaryFunction<T>() {
                        @Override
                        public T apply(T arg) {
                            return arg;
                        }
                    };
        }
    

    递归类型限制:

    通过某个包含该类型参数本身的表达式来限制类型参数

    <T extends Comparable<T>>//针对可以与自身进行比较的每个类型T
    

    利用有限制通配符来提升API的灵活性

    参数化类型是不可变的。

    虽然String类是Object类的子类,但是List<String>和List<Object>无关
    
    /**
     * 栈的实现
     * @param <E>
     * API:
     *   public Stack();
     *   public void push(E e);
     *   public E pop();
     *   public boolean isEmpty();
     *
     * 新增API:
     *   before:
     *     public void pushAll(Iterable<E> i);
     *     public void popAll(Collection<E> c);
     *   after:
     *     使用有限制的通配符类型(bounded wildcard type)
     *    public void pushAll(Iterable<? extends E> i);
     *    public void popAll(Collection<? super E> c);
     *
     */
    
    class Stack<E>{
        private E [] elements;
        private static final int INIT_CAPABILITY = 16;
        private int size = 0;
        @SuppressWarnings("unchecked")
        public Stack(){
            elements = (E[]) new Object [INIT_CAPABILITY];
        }
        public void push(E e){
            checkCapability();
            elements[size++]=e;
        }
        public E pop(){
            if(size==0)
                throw new RuntimeException("Empty Stack");
            E e = elements[--size];
            elements[size]=null;
            return e;
        }
    
        private void checkCapability() {
            if(size==elements.length)
                elements = Arrays.copyOf(elements,2*elements.length-1);
        }
        public boolean isEmpty(){
            return size==0;
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            String start = super.toString();
            sb.append(start);
            for(int i = 0 ;i<size;i++){
                String s = " ["+elements[i]+"]";
                sb.append(s);
            }
            return sb.toString();
        }
    
        //before
    //    public void pushAll(Iterable<E> i){
    //        for(E e:i){
    //            push(e);
    //        }
    //    }
    //    public void popAll(Collection<E> c){
    //        while (!isEmpty()){
    //            c.add(pop());
    //        }
    //    }
        //after
        public void pushAll(Iterable<? extends E> i){
            for(E e:i){
                push(e);
            }
        }
        public void popAll(Collection<? super E> c){
            while(!isEmpty()){
                c.add(pop());
            }
        }
    
    
    
            Stack<Number> stack= new Stack<>();
            Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
            Collection<Object> objectCollection = new LinkedList<>();
            //before
    //        stack.pushAll(integers);//参数类型不对
    //        stack.popAll(objectCollection);//参数类型不对
            //after
            stack.pushAll(integers);
            System.out.println(stack);
            stack.popAll(objectCollection);
            System.out.println(stack);
    
    

    从上面的Demo中我们知道,Java中提供了有限制的通配符类型来提高API的灵活性。

    Collection<? extends E>

    Collection<? super E>

    一般在表示生产者消费者的输入参数上使用通配符类型。

    PECS:Producer-extends Consumer-super

          ------------------
    
     * 参数化类型  通配符类型
     *  T生产者   extends
     *  T消费者   super
     * ------------------
    
  • 相关阅读:
    登琴台石有感
    天台之约
    游小九寨有感
    又见白沫江
    大学生创业 提前要做好三项准备
    游十八里香草沟有感
    观响水滩瀑布有感
    创业十把金钥匙,看了非常有启发~(转)
    2009年春季商务人士最关注的经管图书
    有了你,就有了诗意的人生
  • 原文地址:https://www.cnblogs.com/JohnTsai/p/5344733.html
Copyright © 2020-2023  润新知