http://www.importnew.com/24029.html
https://cloud.tencent.com/developer/article/1033693
这篇文章主要借鉴了这两篇作品,这里做一个总结方便自己学习
泛型
先来看个泛型的例子吧
public class Box{
private String myBox;
public void setMyBox(String s){
myBox = s;
}
public String getMyBox(){
return myBox;
}
}
我们平时通常会这样写一个类,但是问题来了,这个box类只能接受一个String类型的参数,那如果我需要接受一个Integer类型的参数呢,是不是又要加多一个类?泛型的其中一个作用就是用来解决这种通用性问题的,我们可以把Box类改成下面的样子
public class Box<T>{
private T myBox;
public void setMyBox(T t){
myBox = t;
}
public T getMyBox(){
return myBox;
}
}
这个时候我们就可以通过一系列的new操作来实现我们想要的效果
Box<String> box = new Box<String>();
Box<Integer> box = new Box<Integer>();
类可以实现泛型,那么方法肯定也可以实现泛型
这里有一个泛型类,接受两个泛型参数
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
这里有一个Util工具类,要用到泛型方法
public class Util{
public static <K,V> boolean compare(Pair<k,v> p1,Pair<k,v> p2){
return p1.getKey().equals(p2.getKey())&&p1.getValue().equals(p2.getValue());
}
}
在1.7和1.8之后,我们可以直接调用这个泛型方法
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
在这里总结一下什么是泛型,泛型的好处是什么
在你没有使用泛型的时候,一个集合类存储的元素可以是各种类型的,也就是Object的,但是在编译期间编译器并不会去检查你存入的元素是否合理是否有效,这就很大程度上加大了运行期出现的可能性,如果要避免这种情况发生就必须使用各种复杂的类型转换,这是件很麻烦的事情。
泛型主要是把运行期的检查工作提前到了编译期,你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException,确保了编译器的类型安全。
边界符
我们需要一个功能,查找某一泛型数组中大于特定元素的元素个数
public static <T> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e > elem) // compiler error
++count;
return count;
}
我们可能会这样实现,但是这样明显是行不通的,因为除了short, int, double, long, float, byte, char等原始类型,其他数据类型是无法用>操作符进行比较的,所以编译器会报错,我们可以用边界符来解决这个问题
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;
}
通配符
假设我们有个方法
public void testBox(Box<Number> box){}
这里有个问题,我们是否能传入一个Box<Integer>实例?
答案是不行的,因为在泛型里面,Integer和Number是没有联系的,但是在平时的编码过程中,Integer和Number明明是有联系的,那我们要怎么去体现这种关系呢,这里就用到了通配符
public void testBox(Box<? extends Number> box){}
我们通常说的通配符有两种,一种是限定通配符,一种是非限定通配符
限定通配符又包括两种
(1)<? extends T>,通过确保类型必须是T的子类来限定类型的上界
(2)<? super T>,通过确保类型必须是T的父类来限定类型的下界
非限定通配符
<?>表示非限定通配符,<?>可以用任意类型来替代
PECS原则
当我们使用限定通配符的时候,我们必须遵循PECS原则,所谓PECS原则:
”Producer Extends, Consumer Super”
“Producer Extends” – 如果你需要一个只读List,用它来produce T,那么使用? extends T。
“Consumer Super” – 如果你需要一个只写List,用它来consume T,那么使用? super T。
如果需要同时读取以及写入,那么我们就不能使用通配符了
例如,我们有如下关系,Fruit类为父类,Apple类,Orange类都为Fruit类的子类
当我们使用List<? extends Fruit>,我们只能使用get()方法,而不能使用add()方法,这是为什么呢?
因为这个List<? extends Fruit>可以有如下多种定义
List<? extends Fruit> list = new List<Apple>();
List<? extends Fruit> list = new List<Orange>();
这个时候,编译器只知道我们传入的这个泛型<? extends Fruit>是Fruit的某个子类,但是具体是哪个子类并不清楚,所以只能阻止add的传入
同样的,当我们使用<? super Fruit>的时候,我们是可以使用add的,但是无法使用get,因为我们已经知道这个泛型一定是Fruit或者Fruit的父类T,那么所有Fruit和Fruit的子类都可以被加入到这个集合,而由于编译器不知道这个父类到底是什么,所以获取的时候只能返回Object对象