Java泛型

Java泛型解惑篇. 实力至上

关于泛型类

public class Box {
    private String object;
    public void set(String object) { this.object = object; }
    public String get() { return object; }
}

如上,当前Box只支持String类型,如果以后要修改为int类型,则需要修改这个类或者新增一个类,那么泛型就起到作用了

public class Box<T> {
    // T stands for "Type"
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
正式使用:
Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>()
如果涉及多种类型,那么只要
public class Box<T,V>就好了.
Box<Integer,String>

关于泛型方法

声明泛型方法也比较简单,上面已经包含了,只要在返回值前加上类似<T,V>这样就可以了

public class Util {
    public static <K, V> boolean compare(Box<K, V> p1, Box<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

关于边界符

为什么泛型要搞个边界符呢,如下

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

只有java的标准数据类型才能通过>对比,所以编译器会报错

Demo.java:25: 错误: 二元运算符 '>' 的操作数类型错误
                if (e > elem)  // compiler error
                      ^
  第一个类型:  T
  第二个类型: T
  其中, T是类型变量:
    T扩展已在方法 <T>countGreaterThan(T[],T)中声明的Object

为了避免这种方法,我们必须要保证T实现了Comparable接口,通过compareTo(T o)对比大小

public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

extends T:限定参数类型的上界:参数类型必须是T或T的子类型
super T:限定参数类型的下界:参数类型必须是T或T的超类型

通配符

public void boxTest(Box<Number> n) { /* ... */ }
此时参数既不能接受Box<Integer>或者Box<Double>,因为Box<Integer>或者Box<Double>Box<Number>之间并没有任何的关系
这里使用Object也一样不可以

通过通配符?表示所有类型

// 正解
public void boxTest(Box<?> n) { /* ... */ }
问题1public <T> void boxTest(Box<T> n) { /* ... */ } 这样不也可以么.之间到底有什么区别?
1.无限定通配符,<?>。
2.上限通配符,<? extends Number>。表示参数类型只能是Number的子类。
3.下限通配符,<? supper Number>。表示参数类型只能是Number的父类。
class Reader<T>{
    T read1(List<? extends T>){} // 表示只要read内的参数满足是T的子类即可
    static <E> E read2(List<E> l){} // 另一种解决方案
}

Object & 通配符? & 泛型T 的区别

到这里为止,Object 和 泛型T的区别已经知道了,类似

void dosomething(Object object){
  // object.dosomething(); 这里肯定会报错
}
// 为了解决这种方法
public static <T extends SomeInterface> void dosomething(T t){
    t.dosomething(); // 这样就不会报错
}
这里Object是实际的类,并不是泛指

通配符 & 泛型T的区别呢

区别1:可以对T进行操作例如
printList(List<T> list){for(T t: list){....}}
printList(List<?> list){// 这里就不能像T一样那么操作,只能通过下标遍历}
结论:通配符<?>不能当“类型参数”用
区别2:
继续上面的问题1
Box<?> getBoxV1(Box<?> box);
<T> Box<T> getBoxV2(Box<T> box);
但是调用时:
public Box<String> getBox(){
    // return new IntImpl().getBoxV1(new Box<>()); 这样会报错
    return new IntImpl().getBoxV2(new Box<>());
}
其实后面发现
public Box<?> getBox(){
  return new IntImpl().getBoxV1(new Box<String>()); // 这样就可以了...
}
总结:<T>声明泛型类的类型参数用于约束类里的类型;<T>声明泛型方法也是为了保证泛型类型的一致性

PECS原则

List<? extends Person> perons = new ArrayList<Man>();
perons.add(new Man());
Demo.java:38: 错误: 对于add(Man), 找不到合适的方法
                perons.add(new Man());
                      ^
    方法 Collection.add(CAP#1)不适用
      (参数不匹配; Man无法转换为CAP#1)
    方法 List.add(CAP#1)不适用
      (参数不匹配; Man无法转换为CAP#1)
CAP#1:编译器在看到后面用ArrayList<Man>值以后,盘子里没有被标上“Man”。而是标上一个占位符:CAP#1,来表示捕获一个PersonPerson子类
List<? extends Fruit> l1 = new ArrayList<Apple>();
// l1.add(new Apple()); 确定了能处理继承于Fruit的类型,但是并不能确定具体的子类型,所以不能添加
// Apple apple = l1.get(0); 不能确定子类型
Apple apple = (Apple) l1.get(0); // 可以通过强制换取值
Fruit fruit = l1.get(0); // 可以通过上限类型取值

List<? super Apple> l2 = new ArrayList<Apple>();
// l2.add(new Fruit()); 确定了能处理Apple的父类型,但是由于ArrayList<Apple>不能存放Apple的父类
l2.add(new Apple()); // 确定了下限,则继承于Apple的类都可以添加
l2.add(new Aapple());
Object obj = l2.get(0); // 只能通过Object类获取,因为并不能确定父类型

实际应用:

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

总结:
当仅仅想取出元素到集合,请把这个集合看成生产者,请使用<? extends T>,这就是Producer extends原则,PECS原则中的PE部分
当仅仅想增加元素到集合,把这个集合看成消费者,请使用<? super T>。这就是Consumer super原则,PECS原则中的CS部分
如果既要存又要取,那么就不要使用任何通配符。

类型擦除

Java泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息,这样到了运行期间实际上JVM根本就知道泛型所代表的具体类型

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) }
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
类型擦除后:
public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}
即便声明了Node<String>还是其他,都视为Object
Node<String> node = new Node<>();
String str = node.getData();
相当于
Node node = new Node<>();
String str = (String)node.getData();

小结:Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常