读书人

Java Generic (二)

发布时间: 2012-10-27 10:42:26 作者: rapoo

Java Generic (2)

4 泛型和继承
??? 首先考虑如下代码:

public class Base {    private String id;    public String getName() {        return "Base";    }    public final String getId() {        return id;    }    public final void setId(String id) {        this.id = id;    }}
public class Derived extends Base {    public String getName() {        return "Derived";    }        public static void main(String args[]) {        //        Base base = new Base();        Derived derived = new Derived();        List<Base> list = new ArrayList<Base>();        list.add(base);        list.add(derived);        //        List<Derived> list2 = new ArrayList<Derived>();        List<Base> list3 = list2; // Type mismatch: cannot convert from List<Derived> to List<Base>        //              List list4 = list2;        list4.add(new Integer(1));        for(Object o : list4) {            System.out.println(o);        }        try {            for(Base b : list2) {                System.out.println(b); // Runtime Exception            }        } catch(ClassCastException e) {            e.printStackTrace();        }        //         List<?> list5 = list2;    }}

??? 泛型类可以继承或者实现其它泛型类或接口,例如ArrayList<T>实现了List<T>接口。因此ArrayList<Base>可以被转换成List<Base>,此外向List<Base>中添加base和derived也是合法的。
??? 但是将list2赋值给list3会导致编译错误,就像关于继承的那个经典问题一样:苹果可以是水果的子类,但是能够装苹果的袋子不是能够装水果的袋子的子类。将list2赋值给list4是合法的,但是如果向list4中添加一个Integer对象,那么在访问list2的时候会导致运行时异常。
??? list5使用了通配符,这在稍后会介绍。

?

5 通配符
??? 在引入泛型之前,遍历Collection的方法通常如下:

public static void visit(Collection c) {    for(Iterator iter = c.iterator(); iter.hasNext(); ) {        System.out.println(iter.next());    }}public static void main(String args[]) {    List list = new ArrayList();    list.add("1");    visit(list);}

??? 使用泛型后,可以写成:

public static void visit(Collection<Object> c) {    for(Object o : c) {        System.out.println(o);    }}public static void main(String args[]) {    List<String> list = new ArrayList();    list.add("1");    visit(list); // The method visit(Collection<Object>) in the type Test is not applicable for the argument (List<String>)}

??? 由于List<String>并不是Collection<Object>的子类(Collection<Object>并不是所有类型的集合的父类型),因此调用visit(list)会造成编译错误。为了解决这个问题,Java泛型引入了通配符“?”。Collection<?>是所有类型的集合的父类型,叫做“未知集合”。

??? 需要注意的是,向“未知集合”中添加对象不是类型安全的,这会导致编译错误,唯一例外的是null。此外从“未知集合”中获得的对象的类型也只能是Object类型。例如:

Collection<?> c = new ArrayList<String>();c.add("1"); // The method add(capture#1-of ?) in the type Collectoin<capture#1 of ?> is not applicable for the arguments (String)c.add(null);Object o = c.get(0);  

?

5.1 有界通配符
5.1.1 上界
??? 可以使用extends为通配符限定上界,例如以下代码中限定了通配符的上界是Base类:

public static void visit(Collection<? extends Base> c) {    for(Base b : c) {        System.out.println(b);    }}public static void main(String args[]) {    List<Base> list = new ArrayList<Base>();    list.add(new Base());    list.add(new Derived());    visit(list);}

???? 在visit方法中,同样不能向c中添加非null对象。例如以下代码会造成编译错误:

public static void visit(Collection<? extends Base> c) {    c.add(new Derived()); // The method add(capture#1-of ? extends Base) in the type Collection<capture#1 -of ? extends Base> is not applicable for the arguments(Derived)}

??? 有时也可以不使用通配符而达到相同的目的,例如:?

public static <T> void copy(List<T> dest, list< ? extends T> src) {...}public static <T, S extends T> void copy(List<T> dest, List<S> src) {...}

?

5.1.2 下界
??? 可以使用super为通配符限定下界,考虑以下代码:

public class Derived extends Base implements Comparable<Object> {    public int compareTo(Object obj) {        return System.identityHashCode(this) - System.identityHashCode(obj);    }    public static <T extends Comparable<T>> T max(Collection<T> c) {        return Collections.max(c);    }    public static void main(String args[]) {        List<Derived> list = new ArrayList<Derived>();        list.add(new Derived());        Derived.max(list); // Bound mismatch: The generic method max(Collection<T>) of type Derived is not applicable for the arguments (List<Derived>). The inferred type Derived is not a valid substitute for the bounded parameter <T extends Comparable<T>>    }}

??? 以上的max方法中限定了T类型必须实现了Comparable<T>接口,那么在main方法中以list为参数调用max方法会导致编译错误,这是因为Derived类没有实现Comparable<Derived>接口。这样的限制有些严格,如果将max方法改成如下方式,可以带来更大的灵活性。

public static <T extends Comparable<? super T>> T max(Collection<T> c) {    return Collections.max(c);}

??? 以下的代码虽然没有太大的意义,但是需要注意的是,在print方法中,p.setFirst()方法和p.setSecond()方法的参数类型为T,但是p.getFirst()方法和p.getSecond()方法的返回值类型只能是Object型。

public class Pair<T> {    //    private T first;    private T second;    public Pair(T first, T second) {        this.first = first;        this.second = second;    }    public T getFirst() {        return first;    }    public void setFirst(T first) {        this.first = first;    }    public T getSecond() {        return second;    }    public void setSecond(T second) {        this.second = second;    }    public void print(Pair<? super T> p) {        Object first = p.getFirst();        Object second = p.getSecond();        System.out.println("first: " + first + ",  second: " + second);    }    public static void main(String args[]) {        Pair<Derived> p1 = new Pair<Derived>(new Derived(), new Derived());        Pair<Base> p2 = new Pair<Base>(new Base(), new Base());        p1.print(p2);    }} 

?

5.2 通配符捕获
??? 考虑为Pair类编写一个方法,用于交换first和second。由于通配符不是变量类型,那么如下的写法是非法的:

? t = p.getFirst();

??? 此外,以下的写法也会导致编译错误:

public static void swap(Pair<?> p) {    Object first = p.getFirst();    p.setFirst(p.getSecond()); // The method setFirst(captur#4-of ?) in the type Pair<capture#4-of ?> is not applicable for the arguments (capture#5-of ?)    p.setSecond(first); // The method setFirst(captur#6-of ?) in the type Pair<capture#6-of ?> is not applicable for the arguments (Object)}

??? 可以通过一个额外的泛型方法中的类型参数来捕获通配符,例如:?

public static void swap(Pair<?> p) {    doSwap(p);}private static <T> void doSwap(Pair<T> p) {    T first = p.getFirst();    p.setFirst(p.getSecond());    p.setSecond(first);}

?

6 泛型和反射
??? Class类是泛型的,而且实现了java.lang.reflect.Type接口。Class类没有公共构造方法。Class对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。Class类的newInstance方法通过默认的构造函数返回类型为T的对象。例如:

public static <T> Pair<T> make(Class<T> c) throws InstantiationException, IllegalAccessException {    return new Pair<T>(c.newInstance(), c.newInstance());}Pair<String> ps = Pair.make(String.class);

??? 接口 TypeVariable<D extends GenericDeclaration>,ParameterizedType, WildcardType,GenericArrayType继承自Type,用于通过反射获得泛型类型的信息(尽管进行了擦除)。
??? TypeVariable 表示各种类型变量。其getName()方法返回在源码中声明的变量名;getBounds()方法返回类型变量上边界Type对象数组;getGenericDeclaration()返回GenericDeclaration对象,以表示声明此类型变量的一般声明。
??? ParameterizedType 表示参数化类型,例如Collection<String>。其getRawType()返回原始类型,也就是声明此类型的类或接口;getActualTypeArguments()方法返回表示此类型实际类型参数的 Type对象数组。
??? WildcardType 表示一个通配符类型表达式,如 ?、? extends Number 或 ? super Integer。getLowerBounds()方法返回类型变量下边界Type对象数组;getUpperBounds()方法返回类型变量上边界Type对象数组。
??? GenericArrayType 表示一种数组类型,其组件类型为参数化类型或类型变量。getGenericComponentType() 返回此数组的元素类型的Type对象。
??? 关于以上接口的详细文档,请参考Javadoc。以下是一个简单的例子,用于获得类型变量的Class类型:

import java.lang.reflect.ParameterizedType;public abstract class GenericBase<T extends Comparable<? super T>> {    //    private Class<T> clazz;    @SuppressWarnings("unchecked")    public GenericBase() {        ParameterizedType pt = (ParameterizedType)getClass().getGenericSuperclass();        clazz = (Class<T>)pt.getActualTypeArguments()[0];    }    public Class<T> getClazz() {        return clazz;    }    public void setClazz(Class<T> clazz) {        this.clazz = clazz;    }}public class GenericDerived extends GenericBase<String> {    public static void main(String args[]) {        GenericDerived gd = new GenericDerived();        System.out.println(gd.getClazz());    }}
1 楼 trydofor 2008-04-24 苹果可以是水果的子类,但是能够装苹果的袋子不是能够装水果的袋子的子类
2 楼 kruce 2008-07-25 苹果可以是水果的子类,但是能够装苹果的袋子不是能够装水果的袋子的子类
协变问题了, 数组是协变的, 但泛型不是
一个Integer数组可以是一个Number数组, 但一个List<Integer>不是List<Number>
协变和非协变都有优缺点, 比如泛型重载, 虽然java的泛型是可擦除的 3 楼 whitesock 2008-07-25 不敢苟同数组的协变性,
Object oa[] = new Integer[5];
oa[0] = new Object();
以上代码会导致异常,也就是说在对你说:
java认为Integer数组是一个Object数组(可以通过编译),但尝试把它当成Object数组使用是个错误(抛出运行时异常)。就抱错的时机来说,我更倾向于编译时错误。
4 楼 laser_lu 2010-02-18 Java的Generic只是方便Compiler进行部分类型检查的一种语法糖,本质上都还是raw types。而且这种“表面形式”的Generic会导致很多难以理解的问题和莫名其妙的语法限制,其实某种程度是增加了程序员的负担,每次用Generics写代码脑子里都要提防Type Erasure带来的种种错误和警告。。。

读书人网 >软件架构设计

热点推荐