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) { /* ... */ }
问题1:public <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,来表示捕获一个Person或Person子类
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的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常
