泛型基础
泛型类定义一个基本的Animals类
public class Animals {
private String object;
public void set(String object){this.object=object;}
public String get(){return object;}
}
上面的代码缺点是将类型定死为String类型,当想传入其他参数时,必须重写Animals类
接着我们用泛型重新顶一下 Animals类
public class Animals<T> {
private T object;
public void set(T object){this.object=object;}
public T get(){return object;}
}
这样定义的Animals类可复用 我们可以将替换成任何我们需要的类型
Animals<Integer> a = new Animals();
Animals<String> b = new Animals();
Animals<Float> c = new Animals();
泛型方法
泛型方法则是在返回类型前面加个<K,V>
public static 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());
}
}
public static 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(){this.key = key;}
public void setValue(){this.value = value;}
public k getKey(){return key;}
public v getValue() {return value;}
}
接下来就可以调用泛型类
Pair<Integer,String> p1 = new Pair(123,"abc");
Pair<Integer,String> p2 = new Pair(456,"cba");
boolean same = Util.compare(p1,p2);
边界符
来看下面这个函数
public static <T> int countcompare(T[] array,T elem){
Integer count = 0;
for (T e:array)
if (e > elem)
++ count;
return count;
}
这个代码是错误的,因为除了short, int, double, long, float, byte, char等原始类型,其他的类并不一定能使用操作符>,所以会报错
要怎么修改呢?
使用边界符 Comparable<T>
public static <T extends Comparable<T>> int Test(T[] anArray,T elem){
Integer count = 0;
for (T e:anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}
调用Test
public static void main(String[] args){
Integer[] arr = {1,2,3,4,5,6};;
Integer t = Test(arr,4);
System.out.println(t);
}
程序正常运行结果:2
通配符
通配符分为上界通配符和下界通配符
<? extends T>
为上界通配符
<? super T>
为下界通配符
为什么要用通配符和边界?
我们来看个例子
我们有个frult类,他的派生类Apple类
class Fruit{}
class Apple extends Fruit{}
接下来我们定义一个Plate类,用来放一个泛型的“东西”,简单定义他的放和取
class plate<T>{
private T item;
public plate(T t){item = t;}
public void set(T t){item = t;}
public T get(){return item;}
}
现在我们定义一个"水果盘子",看逻辑上是可以装苹果
Plate<Fruit> p = new Plate<Apple>(new Apple())
但实际上Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”
error:incompatiable types:Palte<Apple> cannot be converted to Plate<Fruit>
上界通配符
这里就要用到我们说的上界通配符
Plate<? extend Fruit>
加上这句话的意思是:一个能放水果以及一切水果派生类的盘子,说人话是一个能放所有水果的盘子 ,plate<? extends Frult> p = new plate<Apple>(new Apple());
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
接下来我们扩展下,再创建水果,肉类,食物的类
#level1
class Food{}
#level2
class Meat{}
class Fruit{}
#level3
class Apple extends Fruit{}
class Banana extends Fruit{}
class Pork extends Meat{}
class Beaf extends Meat{}
#level4
class green Apple extends Apple{}
class red Apple extends Apple{}

下界通配符
现在我们来讲下界通配符
Plate<? super Fruit>
这段代码的意思就是,一个能装水果和所有水果基类的盘子,这里要注意下,Plate<? super Fruit>是Fruit的基类,而不是Apple的基类,如下图,红色区域:

上下界通配符的副作用
这样使用通配符也是有坏处的,你如容器部分可能会失效,我们定义下Plate类,给他赋予两个简单的功能set()存和get()取
class plate<T>{
private T item;
public plate(T t){item = t;}
public void set(T t){item = t;}
public T get(){return item;}
}
上界通配符只能取不能存
Plate<? extends Fruit> 回事盘子里的set方法失效,但get()还是能用的
如下面的set()代码
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());`
# 不能存入任何元素
p.set(new Fruit()); #error
p.set(new Apple()); #error
# 读取出来的东西只能存在Fruit或他的基类
Fruit newFruit1 = p.get()
Object newFruit3 = p.get();
Apple newFruit4 = p.get(); #error
原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?编译器在看到后面用Plate<Apple>;赋值以后,盘子里没有被标上有“苹果”。而是标上一个占位符,来表示捕获一个Fruit或Fruit的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入Apple或者Meat或者Fruit编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。
所以通配符<?>;和类型参数<T>;的区别就在于,对编译器来说,所有的T都代表同一种类型
下界通配符
下界<? super T>不影响往里存,但往外取只能放在Object对象里
使用下界<? super Fruit>会使从盘子里取东西的get( )方法部分失效,只能存放到Object对象里。set( )方法正常。
Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
//存入元素正常
p.set(new Fruit());
p.set(new Apple());
//读取出来的东西只能存放在Object类里。
Apple newFruit3=p.get(); //Error
Fruit newFruit1=p.get(); //Error
Object newFruit2=p.get();
因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。
PECS原则
最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:
- 频繁往外读取内容的,适合用上界Extends。
- 经常往里插入的,适合用下界Super。
类型擦除
在java 1.5前,java是没有泛型这一概念的,java为了能向下兼容在转入jvm之前,将泛型将泛型相关的擦除转为object,这种在java术语中叫类型擦除
网友评论