zoukankan      html  css  js  c++  java
  • Java泛型读书笔记 (三)

    泛型对于老代码的支持

    Java的泛型设计成类型擦除的目的,很大一部分是为了兼容老老代码。如下的一段代码:

    void setLabelTable(Dictionary table)
    

    table的类型是非泛型的Dictionary,但是我们可以传入泛型的Dictionary:

    Dictionary<Integer, Component> labelTable = new Hashtable<>(); 
    labelTable.put(0, new JLabel(new ImageIcon("nine.gif"))); 
    labelTable.put(20, new JLabel(new ImageIcon("ten.gif"))); 
    ...
    slider.setLabelTable(labelTable); // WARNING
    

    注意,当向setLabelTable传入一个泛型的Dic,编译器会产生一个警告。

    还有一种相反的情况,一个方法的返回类型是原始类型,但是可以分配给一个泛型类型持有:

    Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // WARNING
    

    这种情况也会产生一个警告信息。

    可以是用java注解来使这些警告消失掉:

    @SuppressWarnings("unchecked")
    Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // No warning
    

    或者注解在整个方法上面,使得方法内的所有警告消失:

    @SuppressWarnings("unchecked")
    public void configureSlider() { . . . }
    

    泛型的使用限制

    泛型的很多限制都是由于类型擦除机制带来的

    泛型的类型参数不能是原始类型

    Type Parameters Cannot Be Instantiated with Primitive Types

    不能用Pair<double>,而只能使用Pair<Double>
    

    运行时的类型查询与判断只能作用于非泛型类型,不支持泛型类的类型查询

    Runtime Type Inquiry Only Works with Raw Types
    由于Java虚拟机运行的代码都是没有泛型的,所以只能对非泛型类型进行类型查询与判断,如下代码是错误的:

    if (a instanceof Pair<String>) // ERROR
    if (a instanceof Pair<T>) // ERROR
    

    只能对a instanceof Pair,不能判断泛型类。

    下面这种情况会查询一个编译时的警告

    Pair<String> p = (Pair<String>) a; // WARNING--can only test that a is a Pair
    

    同样的,进行下面的判断,也只是对擦除后的类型进行的,与使用的什么类型参数无关:

    Pair<String> stringPair = . . .;
    Pair<Employee> employeePair = . . .;
    if (stringPair.getClass() == employeePair.getClass()) // they are equal
    

    上面的比较返回的是true,因为他们都属于擦除后的类型Pair.class

    不能创建泛型类的数组

    You Cannot Create Arrays of Parameterized Types

    下面这种代码是错误的:

    Pair<String>[] table = new Pair<String>[10]; // ERROR
    

    在类型擦除后,table的类型实际上是Pair[],可以将其转换为Object[]类型:

    Object[] objarray = table;
    

    由于数组是可以记住它内部元素的类型的,并且当你试图存储一个类型不匹配的元素时,会抛出ArrayStoreException异常。

    那么像下面的代码:

    objarray[0] = new Pair<Employee>();
    

    首先,进行类型擦除,那么就变成了objarray[0] = new Pair();
    但是objarray记住的内部元素类型应该是Pair, 与Pair不符,那么就会抛出异常。
    由于这种情况,Java不允许new一个泛型类型的数组。

    如果需要存储泛型类对象的集合,直接使用ArrayList即可,既安全又高效。

    ArrayList<Pair<String>> 
    

    可变参数使用泛型类型

    像下面这种泛型类型作为可变参数:

    public static <T> void addAll(Collection<T> coll, T... ts)
    {
        for (t : ts) coll.add(t);
    }
    

    由于可变参数实际上是语法糖,就是一个数组,那么ts就是一个T[]数组,当我们使用这个addAll方法,如下:

    Collection<Pair<String>> table = ...; 
    
    Pair<String> pair1 = ...; 
    
    Pair<String> pair2 = ...; 
    
    addAll(table, pair1, pair2);    
    

    Java会将形参ts转换为Pair泛型类型数组:

    Pair<String>[]
    

    根据上一个限制,Java是不允许创建泛型类型的数组的。但是,在这里Java允许创建这种泛型类型数组。
    这样传入泛型对象时,Java会报出警告信息,可以使用注解放在包含调用addAll方法的方法来去掉这种警告:

    @SuppressWarnings("unchecked")
    

    在Java7以后,可以使用新的注解@SafeVarargs来注解addAll方法本身,来去掉这种警告:

    @SafeVarargs
    public static <T> void addAll(Collection<T> coll, T... ts)
    

    不能初始化泛型类类型参数

    不能像如下代码使用来直接new一个类型参数:

    new T(...), new T[...], or T.class.
    

    如下在Pair< T >中直接new一个类型参数是非法的:

    public Pair() { first = new T(); second = new T(); } // ERROR
    

    因为类型擦除,会将T擦除为Object,那么上面的代码直接就是new Object(), 显然不是我们想要的。

    workaround的方法是使用反射机制来new一个泛型类型的对象,使用时,需要多传入一个Class< T > 对象,如下:

    public static <T> Pair<T> makePair(Class<T> cl)
    {
        try 
        { 
            return new Pair<>(cl.newInstance(), cl.newInstance()) 
        }
        catch (Exception ex) 
        { 
            return null; 
        } 
    }
    

    上面的方法需要使用方传入一个泛型类型T的Class对象,然后使用Class的newInstance方法产生泛型类型的实例。

    注意,Class自身就是泛型的,比如,String.class就是一个Class< String >的实例。

    同样也不允许直接new一个泛型类型数组,如下代码:

    // ERROR
    public static <T extends Comparable> T[] minmax(T[] a) 
    { 
        T[] mm = new T[2]; 
        . . . 
    }
    

    上面的代码,擦除到边界后变为了Comparable[] mm = new Comparable[2]; 这也不是我们想要的,如果你只想将泛型类数组当成一个内部使用的字段,那么可以想下面的ArrayList< E> 一样直接内部使用Object[] 即可:

    public class ArrayList<E>
    {
        private Object[] elements;
        ...
    
        @SuppressWarnings("unchecked") 
        public E get(int n) 
        { 
            return (E)elements[n];
        }
    
        public void set(int n, E e) 
        { 
            // no cast needed 
            elements[n] = e; 
        } 
    }
    

    当然,如果需要在一个方法中返回泛型数组的话,下面的代码是错误的:

    public static <T extends Comparable> T[] minmax(T... a)
    {
        Object[] mm = new Object[2];
        ...
        return (T[]) mm; // compiles with warning
    }
    

    因为编译器会擦除到Comparable,返回的数组就不是你想要的泛型类型数组了,解决方法如下:

    public static <T extends Comparable> T[] minmax(T... a)
    {
        T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
        ... 
    }
    

    使用Array的newIntance方法来生成数组。

    类的静态字段不能使用泛型类

    下面的静态字段不能使泛型类:

    public class Singleton<T>
    {
        private static T singleInstance; // ERROR
        public static T getSingleInstance() // ERROR
        {
            if (singleInstance == null)
            {
                construct new instance of T
                return singleInstance;
            }
        }
    }
    

    因为类型擦除,singleInstance不管传入什么,都是一个Object类型,所以没有意义。

    不能抛出泛型类异常,也不能捕获泛型类异常的实例

    下面的代码,直接扩展Exception是不允许的:

    public class Problem<T> extends Exception { /* . . . */ } // ERROR-- can't extend Throwable
    

    下面的代码,也不能catch一个泛型类型的异常:

    public static <T extends Throwable> void doWork(Class<T> t)
    {
        try
        {
            do work
        }
        catch (T e) // ERROR--can't catch type variable
        {
          Logger.global.info(...)
        } 
    }
    

    但是泛型类型参数作为方法名后的throws T是允许的,像下面的代码:

    public static <T extends Throwable> void doWork(T t) throws T // OK
    {
        try
        {
            do work
        }
        catch (Throwable realCause)
        {
              t.initCause(realCause);
            throw t; 
        }
    }
    

    注意泛型参数被擦除后带来的冲突

    如下代码:

    public class Pair<T>
    {
        public boolean equals(T value) 
        { 
            return first.equals(value) && second.equals(value); 
        }
        ... 
    }
    

    当我们定义了一个Pair< String>泛型类,如果没有擦除机制,那么Pair< String>类里面会有两个如下的equals方法,一个定义在此类中,另一个定义在隐式继承类Object中:

    boolean equals(String) // defined in Pair<T>
    boolean equals(Object) // inherited from Object 
    

    但是,在擦除掉Pair< String>中的String后,实际上下面这个equals方法:

    boolean equals(T)
    

    擦除后变为了下面这样:

    boolean equals(Object)
    

    那么这个equals方法就覆盖掉了Object类中的equals方法,也就发生了冲突。解决方法很简单,就是重命名Pair< T>中的equals方法。

    不允许一个类继承自两个只是泛型类参数不同的类

    像下面的GregorianCalendar类,间接继承自Comparable和Comparable两个只是泛型类参数不同的同一个接口是不允许的:

    class Calendar implements Comparable<Calendar> { . . . } 
    class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>
    { . . . } // ERROR
    

    泛型类型的继承规则

    如果Employee是父类,Manager继承自Employee,那么这两个类作为同一个泛型类的类型参数时,这两个泛型类没有任何关系

    注意泛型类与Java数组之间的一个重要的区别,可以将一个Manager[]数组赋给一个类型为Employee[]数组的变量:

    Manager[] managerBuddies = {ceo, cfo};
    Employee[] employeeBuddies = managerBuddies; //OK
    

    然而,数组带有特别的保护,如果试图将一个低级别的Employee存储到employeeBuddies[0], 虚拟机将会抛出ArrayStoreException异常。

    你总是可以将一个泛型类型转换为它的原始类型,例如,Pair< Employee>是原始类型

    Pair的一个子类,这种转换能力是用来兼容Java老代码的。

    最后,泛型类可以继承或者实现其他泛型类,在这一点上,它与普通类没有区别。例如,ArrayList< T>实现了接口List< T>,那么ArrayList< Manager>就可以转换为List< Manager>,但是ArrayList< Manager>与ArrayList< Employee>和List< Employee>无关,不能转换

  • 相关阅读:
    使用 Promise.all 同时发起多个请求
    vite 开发 Cesium 程序最佳配置实践
    【linux学习】使用grep命令获取过滤的数据作为下个命令的入参
    记一次k8s depolyment失败处理
    powerdesigner导出excel数据字典
    vue 时间格式
    ASP.NET MVC4 跨域配置
    Win10系统中切换虚拟桌面的快捷键如何设置
    不顾一切最简NHinbernate配置并读写数据库
    Windows time_wait过多解决办法
  • 原文地址:https://www.cnblogs.com/liupengblog/p/5176023.html
Copyright © 2011-2022 走看看