zoukankan      html  css  js  c++  java
  • java基础-泛型3

    浏览以下内容前,请点击并阅读 声明

    8 类型擦除

      为实现泛型,java编译器进行如下操作进行类型擦除:

    •  如果类型参数有限制则替换为限制的类型,如果没有则替换为Object类,变成普通的类,接口和方法。
    • 有必要时插入转换操作以保证类型安全
    • 产生桥接方法以保证继承类型的多态性

      类型的擦除确保了类型参数化后,泛型不会产生新的类,因此不会引发运行时开销。

    8.1 泛型类型

      整个擦除过程中,java编译器将擦除所有的类型参数,将其替换成第一个类型的限制,如无限制,则替换成Object。

      如对以下类声明的编译:

    public class Node<K,V extends Number> {
        private K data;
        private V next;
        public Node(K data, V next) }
            this.data = data;
            this.next = next;
        }
        public K getData() { return data; }
        // ...
    }

      java编译器则会作如下替换:

    public class Node  {
    //由于K是没有限制的,将其替换成Object
        private Object data;
    //而V上限为Number,将其替换成Number
        private Number next;
    //方法中的也会进行替换
        public Node(Object data, Number next) }
            this.data = data;
            this.next = next;
        }
        public Object getData() { return data; }
        // ...
    }

    8.2泛型方法的擦除

      泛型方法的擦除与泛型类型的擦除方法相同,如上述例子中getData方法的擦除所示。

    8.3类型擦除以及桥接方法的影响

      有些类型的擦除会产生你可能难以预料的情况,

      当编译一个继承一个类型参数化的父类的类或者是接口时,编译器可能需要生成一个方法,叫做桥接方法,这也是类型擦除中的一个过程,可能在stack trace中显示。

      两个继承关系的类声明如下:

    //泛型声明
    public class Node<T> {
        public T data;
        public Node(T data) { this.data = data; }
        public void setData(T data) {
            System.out.println("Node.setData");
            this.data = data;
        }
    }
    //继承上述类型参数化的类
    public class MyNode extends Node<Integer> {
        public MyNode(Integer data) { super(data); }
        public void setData(Integer data) {
            System.out.println("MyNode.setData");
            super.setData(data);
        }
    }

    经编译器类擦除:

    public class Node {
        public Object data;
        public Node(Object data) { this.data = data; }
        public void setData(Object data) {
            System.out.println("Node.setData");
            this.data = data;
        }
    }
    public class MyNode extends Node {
        public MyNode(Integer data) { super(data); }
        public void setData(Integer data) {
            System.out.println("MyNode.setData");
            super.setData(data);
        }
        // 由编译器生成的桥接方法
        public void setData(Object data) {
            setData((Integer) data);
        }
    }

    子类MyNode继承了setData方法签名与父类中的方法不同,为保证继承的特征,所以产生了一个桥接方法。

    8.4 不可具体化类型

      可具体化的类型是指类型信息在运行时可以获得的类型,包扩基本类型,非泛型,原始类型以及无限制通配符的调用。

      不可具体化的类型是指在编译时被类型擦除移除信息的类型,是为定义为无限制通配符的泛型的调用。在运行时,无法获得不可具体化的类型的全部信息。比如List<String>和List<Number>都是不可具体化的类型,Java虚拟机在运行时无法区分这些类型。

      当一个参数化的变量引用一个不是该参数化类型的对象时,就会发生堆污染,这种情况一般发生在编译时引起unchecked警告的操作中,unchecked警告可以在编译时或者运行时产生,对于参数化的类型的操作的正确性无法验证时。在原始类型和参数化的类型混用时比较容易产生堆污染。

      一般情况下,所有的代码同时编译时会产生unchecked警告以告知可能的堆污染,而分部分编译代码时比较难发现可能潜在的堆污染。

      含有不可具体化类型的可变变量的方法,容易产生堆污染,如:

    public class ArrayBuilder {
    
      public static <T> void addToList (List<T> listArg, T... elements) {
        for (T x : elements) {
          listArg.add(x);
        }
      }
      public static void faultyMethod(List<String>... l) {
        Object[] objectArray = l;     // Valid
        objectArray[0] = Arrays.asList(42);
        String s = l[0].get(0);    // 运行时将会抛出类转化异常
      }
    }

      注意java中不允许创建参数化的类型的数组,编译器首先将第一个方法中的elements变量转换成数组T[],再经类型擦除,elements又变成Object[] ,因此可能会导致堆污染。

      第二个方法中,经类擦除,l会转化成List[],List[]是Object[]的子类型,方法中的一二句不会有问题,第三句运行时就会抛出异常。

       声明一个含有参数化类型的参数的可变变量的方法时,编译器会产生unchecked警告,可以在非构造器的方法上声明@SafeVarargs注释,以消除警告信息,也可以使用@SuppressWarnings({"unchecked", "varargs"}) 注释,不过该注释只会消除定义方法产生的警告,而不会消除调用方法产生的警告信息。

    8.5 泛型的限制

      要高效地使用泛型,还要考虑到如下限制:

      8.5.1 不能使用基本数据类型去实例化一个泛型。

      例如:

    class Pair<K, V> {
    
        // ...
    }
    //一下语句将会编译出错
    Pair<int, char> p = new Pair<>(8, 'a'); 
    //要使用基本数据类型的封装类型,注意实参是进行了自动转化的
    Pair<Integer, Character> p = new Pair<>(8, 'a');
      8.5.2 不能创建一个类型参数的实例
    public static <E> void append(List<E> list) {
        E elem = new E();  // 编译时错误
        list.add(elem);
    }
    //上述方法的声明将编译出错
    //不过 可以通过Class<E>的方法newInstance创建对象
    public static <E> void append(List<E> list, Class<E> cls) throws Exception {
        E elem = cls.newInstance();   // OK
        list.add(elem);
    } 
      8.5.3 不能声明类型为类型参数的静态字段

      一个类的静态字段是其所有非静态的对象共有的类级的变量,所以类型为类型参数的静态字段是不允许出现的。

      8.5.4 不能对类型参数使用转化(cast)和instanceof操作符

      因为在编译时,java编译器会擦除所有的泛型代码中的类型参数,所以在运行时无法验证某个泛型使用了哪个类型参数。

    public static <E> void rtti(List<E> list) {
        if (list instanceof ArrayList<Integer>) {  //此处编译出错
            // ...
        }
    }

     因为运行时不会跟踪泛型的类型参数,所以运行时无法区分ArrayList<Integer>和ArrayList<String>,不过使用无限制通配符可以使用instanceof操作符:

    public static void rtti(List<?> list) {
        if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
            // ...
        }
    }

      这里最多的也就是验证list变量是否属于ArrayList类型。

      一般情况下,除了使用无限制通配符,你不能对一个参数化类型进行类转化:

    List<Integer> li = new ArrayList<>();
    List<Number>  ln = (List<Number>) li;  // 编译出错
    //然而有时候编译器判断类型参数合法时,就会允许进行转化,如下所示:
    
    List<String> l1 = ...;
    ArrayList<String> l2 = (ArrayList<String>)l1;  // OK 
      8.5.5 不能创建参数化类型的数组 
    List<Integer>[] arrayOfLists = new List<Integer>[2];  // 编译出错
      8.5.6 不能创建,捕获或者抛出参数化类型的对象

       泛型类不能直接或间接竭诚Throwable类,否则将编译不通过。

      方法中不能捕获一个类型参数:

    public static <T extends Exception, J> void execute(List<J> jobs) {
        try {
            for (J job : jobs)
                // ...
        } catch (T e) {   // 编译将出错
            // ...
        }
    }

       然而,类型参数可用于throws语句中,如:

    class Parser<T extends Exception> {
        public void parse(File file) throws T {     // OK
            // ...
        }
    }
      8.5.7 不能重载参数类型经类型擦除后的原始类型相同的方法

      因为编译器会对类型参数进行擦除,所以:

    //以下类声明将编译出错
    public class Example {
        public void print(Set<String> strSet) { }
        public void print(Set<Integer> intSet) { }
    }

      上述进行类型擦除以后,两种方法的签名将相同,其参数类型都为Set ,所以编译无法通过。

  • 相关阅读:
    我来了
    学习笔记-数据仓库和数据挖掘
    React及JSX常见问题
    JavaScript笔记,不定期更新
    比strlen执行速度更快的处理字符串长度的函数
    LESS笔记/注意手册(更新中)
    鼠标移到图片变化的三种写法(可移植性强、代码少)
    信息安全技术作业5--散列函数的应用及其安全性
    结对作业(web)
    读《构建之法》第4、17章
  • 原文地址:https://www.cnblogs.com/justforcon/p/6081553.html
Copyright © 2011-2022 走看看