Java generics

来自WHY42

泛型是Java1.5之后一个比较有用的特性,有点类似于C++的模板。最简单的一个例子:

class Wrapper<T> {
    final T data;

    Wrapper(T data) {
        this.data = data;
    }
}

有一些可能不是特别常用的Generics,我们来简单看一下。

Bounded Generics

Multiple bound

如果一个类继承了多个接口,是这样的写法:

interface I {}
interface M {}
abstract class C {}

class Foo extends C implements I,M {}

假如一个方法的泛型参数包含多个Bound,则要这样写了:

<T extends I & M> void bar(T arg){}
<T extends C> void ooo(T arg){}
<T extends C & I & M> void xxx(T arg){}

Unbounded wildcards

使用 ? 修饰符可以用作类型转换,List<?> 意味着是一个未知类型的List,可能是`List<A>` 也可能是`List`

private final List<String> strList = Arrays.asList("Hello", "World!");
private final List<Integer> intList = Arrays.asList(1, 2, 3);
private final List<Float> floatList = Arrays.asList(1.1f, 2.1f, 3.1f);
private final List<Number> numberList = Arrays.asList(1, 1.0f, 3000L);

public void cast() {
    List<?> unknownList = null;
    unknownList = strList;
    unknownList = intList;
    unknownList = floatList;
    unknownList = numberList;

    for (int i = 0; i < unknownList.size(); i++) {
        // Number item = unknownList.get(i); wrong! 
        Object item = unknownList.get(i);
        System.out.println(item + "(" + item.getClass() + ")");
    }
}
/* output
1(class java.lang.Integer)
1.0(class java.lang.Float)
3000(class java.lang.Long)
*/

Upper bounded wildcards

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}
//...
sumOfList(Arrays.asList(1, 2, 3));
sumOfList(Arrays.asList(1.0f, 2.0f, 3.0f));

Lower bounded wildcards

public static void addNumbers(List<? super Number> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
        list.add(1.0f);
    }
}
addNumbers(new ArrayList<Number>());

Type erase

Type erase process

Java的泛型是编译时有效的,在运行时,所有泛型参数会被编译器擦除。擦除的规则如下:

  • 如果参数是有Bound的,则会替换成这个Bound
  • 如果是Unbounded,则会替换成Object

如下所示:

public class Node<T> {                         // public class Node {
    private T data;                            //     private Object data;
    private Node<T> next;                      //     private Node next;
    public Node(T data, Node<T> next) {        //     public Node(Object data, Node next) {
        this data = data;                      //         this data = data;
        this next = next;                      //         this next = next;
    }                                          //     }
                                               // 
    public T getData() { return data; }        //    	public Object getData() { return data; }
}                                              // }

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

Bridge method

按照上面的擦除也会带来问题。考虑下面的例子,如果有一个子类:

public class MyNode extends Node<Integer> {       // public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }  //     public MyNode(Integer data) { super(data); }
                                                  // 
    public void setData(Integer data) {           //     public void setData(Integer data) {
        System.out.println("MyNode.setData");     //         System.out.println("MyNode.setData");
        super.setData(data);                      //         super.setData(data);
    }                                             //     }
}                                                 // }

然后,我们考虑如下的代码:

MyNode mn = new MyNode(5);                     // MyNode mn = new MyNode(5);
Node n = mn;                                   // Node n = (MyNode)mn;
n.setData("Hello");                            // n.setData("Hello");
Integer x = mn.data;                           // Integer x = (String)mn.data;

这里调用setData则会参数类型不能匹配。为了解决这个问题,Java编译器会生成一个Bridge method:

public void setData(Object data) {
    setData((Integer) data);
}

Q&A

List\<?\> vs List\<Object\>

>It's important to note that List<Object> and List<?> are not the same. You can insert an Object, or any subtype of >Object, into a List<Object>. But you can only insert null into a List<?>.

extends vs super

实际上泛型仅仅是为了做一个编译时的检查,从逻辑上确保程序是类型安全的。假设我们有这样的类定义: Object->Parent->T->Child 我们有这样几种写法:

  • ```List<?>``` 代表一种未知类型的List,可能是```List<Object>```,也可能是```List<Child>```,都可以
  • ```List<? extends T>``` 代表T或者T的子类的List,可以是```List<T>```,也可以是```List<Child> ```
  • ```List<? super T>``` 代表T或者T的父类的List,可以是```List<T>,List<Parent>,List<Object>```

我们有一个事实就是,Child是一定可以转化T或者Parent的,但是一个T不一定能转化成Child,因为可能会是别的子类。 比如我们现在做两个列表的拷贝,

public static <T> void copy(List dest, List src)

想实现从一个列表拷贝到另一个列表,比如

List<Parent> parents;
List<T> ts;
List<Child> childs;

基于上面说的类的继承的事实,ts/childs显然是可以转化成parents的,但是ts无法确保能转化成childs。因此我们的拷贝方法要这样定义:

public class Collections { 
  public static <T> void copy  
  ( List<? super T> dest, List<? extends T> src) {  // uses bounded wildcards 
      for (int i=0; i<src.size(); i++) 
        dest.set(i,src.get(i)); 
  } 
}

因为在desc.set()方法中,需要的是一个能够转化为T的对象的,src中<? extends T> 保证了src中的元素一定是一个T。

See also: