Java 杂谈

CoreJava笔记 - 范型程序设计(3)

2018-07-26  本文已影响0人  杀死BL

约束与局限性

  1. 不能用primitive作为范型类型
    类型擦除后,默认限定类是Object,而primitive的基类不是Object

  2. 运行时类型查询:无范型信息(虚拟机上没有范型类型)
    if (a instanceof Pair<String>) 判断类型=>ERROR
    Pair<String> p = (Pair<String>)a 类型转换=>WARNING

  3. 不能创建参数化类型的数组:因为参数擦除,和数组类型检查
    Pair<String>[] a = new Pair<String>[10] //ERROR

    Java中的数组是有类型检查的,如果add的类型有误,会抛出ArrayStoreException

    Pair<String>Pair<Employee>在类型擦除后,都是Pair。因此数组Pair<String>[]无法检查非法类型的成员Pair<Employee>

    • 注意:只是不能初始化这样的数组,声明范型的数组变量是允许的。
    • 注意:可以用后面提到的通配类型的数组来进行范型数组的初始化,然后进行类型转换。
      Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
    • 注意:要使用范型对象的集合,只有使用相应的容器类才是唯一安全有效的方法:ArrayList<Pair<String>>
  4. Varargs警告
    不定长参数实际上是一个数组,下面的不定长参数列表会在系统中建立一个范型数组:

    public static <T> void addAll(Collection<T> c, T... ts)
    Collection<Pair<String>> table = ...;
    addAll(c, new Pair<String>(), new Pair<String>);
    

    对于这种情况,Java放松了限制,只会给出一个警告,而不是Error。这个警告可以由@SafeVarargs来消除。

  5. 不能实例化类型变量
    由于类型擦除的原因:new T()这种代码肯定是不允许的。

    //错误的用法
    public Pair() {first = new T(); second = new T();}
    
    // Java8: Lambda表达式 - 构造器表达式
    // Supplier接口是一个函数式接口,无参数且返回T。
    public static <T> Pair<T> makePair(Supplier<T> constr) {
        return new Pair<>(constr.get(), constr.get());
    }
    //调用
    Pair<String> p = Pair.makePair(String::new);
    
    // Java8以前的方法:通过反射newInstance()
    public static <T> Pair<T> makePair(Class<T> cl) {
        try {
            return new Pair<>(cl.newInstance(), cl.newInstance());
        }
        catch (Exception ex) {
            return null;
        }
    } 
    //调用:
    Pair<String> p = Pair.makePair(String.class);
    
  6. 不能构造参数类型数组T[]

    //由于类型擦除,mm总会是: Comparable[2]
    public static <T extends Comparable> T[] minmax(T[] a) {
        T[] mm = new T[2]; //ERROR
        ...
    }
    
    //Java8: Lambda表达式 - 构造器表达式
    public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr, T... a) {
        T[] mm = constr.apply(2);
    }
    //调用
    String[] ss = minmax(String[]::new, "Str1", "Str2", "Str3");
    
    // Java8以前的方法:通过反射newInstance()
    public static <T extends Comparable> T[] minmax(T... a) {
        T[] mm = Array.newInstance(a.getClass().getComponentType(), 2);
        ...
    } 
    //调用:
    Pair<String> p = Pair.makePair(String.class);
    

    ArrayList<T>.toArray()在类型擦除后,无法通过参数获取T的类型,因此只能提供两种方案:

    • Object[] toArray():传统方法
    • T[] toArray(T[] a):a是目标数组,足够大就装进去,不足够大也可以获取T的类型,从而构造个新的。
  7. 不能在静态域或静态方法中引用类型变量
    由于类型擦除,下面的代码会导致ERROR

    public class Singleton<T> {
        private static T singleton; //ERROR
        public static T getSingleton() { //ERROR
            if (singleton == null) {
                ...
            }
            return singleton;
        } 
    }  
    
  8. 不能抛出、捕获T,不能用范型类扩展Throwable

    • thorw T; //ERROR

    • catch (T e); //ERROR

    • P<T> extends Throwable //ERROR

    • 函数声明抛出T异常:OK

      public static <T extends Throwable> void doWork(T t) throws T {
          ...
      }
      
  9. Trick:不检查checked异常
    Java异常处理的一个基本原则是:必须为所有checked异常提供一个处理器。

    // 定义一个语句块,这个语句块的body在一个单独的线程中运行
    public abstract class Block {
    
        public Thread toThread() {
            return new Thread() {
                public void run() { //只会抛出unchecked异常,无需声明throws,外部无需catch
                    try {
                        body(); //body是abstract方法,由用户实现,可抛出异常
                    } catch (Throwable t) { 
                        //如果有异常,当作RuntimeException(属于unchecked异常)抛出
                        Block.<RuntimeException>throwAs(t);
                    }
                }
            }
        }
        
        @SuppressWarnings("unchecked")
        public static <T extends Throwable> void throwAs(Throwable e) throws T {
            //由于类型擦除,虚拟机上的T实际上都是Throwable
            //但是编译器层面会认为抛出的类是RuntimeException,因而算作unchecked异常
            throw (T) e;
        }
        
        public abstract void body() throws Exception;
    }
    
    // 调用
    new Block() {
        public void body() throws Exception {...}
    }.toThread().start();
    
  10. 类型擦除可能引发的冲突

        //冲突1: 类型擦除后,与Object.equals(Object)冲突
        public class Pair<T> {
            public boolean equals(T value) {return ...}
        }
        
        //冲突2: Manager是Comparable<Manager>、Comparable<Employee>这两个接口类的子类
        // 而且这两个接口类是相通接口的不同类型化,这导致内部生成桥2个如下的桥函数(X不同)
        // public int compareTo(Object other) {return compareTo((X) other);}
        class Employee implements Comparable<Employee> {...}
        class Manager implements Comparable<Manager> {...}
    

范型类型的继承规则

上一篇 下一篇

猜你喜欢

热点阅读