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>无关,不能转换

原文地址:https://www.cnblogs.com/liupengblog/p/5176023.html