java泛型
通俗的讲,泛型就是操作类型的占位符
一、通常泛型的写法
1.1定义泛型类
public class ApiResult{
int resultCode;
String resultMsg;
T resultObject;
}
1.2定义泛型方法
public JsonUtil{
public T str2Json(String jsonText, Class target){
T result = null;
...
return result;
}
}
1.3 K,V类型
public class ResultMap{
private K key;
private V value;
// 省略set,get方法
public void put(K key, V value){
this.key = key;
this.value = value;
}
}
二、使用泛型类和方法
ApiResult result = new ApiResult();
result.setResultCode(0);
result.setResultMsg("success");
result.setResultObject(new User());
String userJsonText = "userJsonText";
User u = JsonUtil.str2Json(jsonText, User.class);
ResultMap resultMap = new ResultMap();
resultMap.put("currentUserKey", new User());
三、类型擦除
public class Operate{
public static void main(String[] args){
List names = new ArrayList();
names.add("Jack");
names.add("Tom");
for(String name:names){
System.out.println(name);
}
}
}
其对应的class文件反汇编以后,我们使用java-gui反编译.exe 查看编译之后的代码如下:
发现没有,根本没有 这一部分了。这个限制为String类型的泛型被“擦除”了。写代码的时候,泛型会做校验,类型不对应的,无法add,但是编译之后边去掉了泛型类型。
四、为什么要使用java泛型??
这里的泛型就相当于“约法三章”,先给你定好“规矩”,我这个List 就是用来操作String类型的,你插入Person对象就不行。说白了就是为了类型安全。所以其好处有:
4.1类型安全:
通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
4.2消除强制类型转换
//该代码不使用泛型:
List li = new ArrayList();
li.put(new Integer(3));
Integer i = (Integer) li.get(0);
//该代码使用泛型:
List li = new ArrayList();
li.put(new Integer(3));
Integer i = li.get(0);
public class Fruit {
}
public class Apple extends Fruit {
}
public class Plate {
private
T item;
public
Plate(T t){
item
= t;
}
public
T getItem() {
return
item;
}
public
void setItem(T item) {
this.item
= item;
}
}
public class TestGenericParadigm {
public
static void main(String[] args){
Plate
p = new Plate(new Apple()); // 编译不通过
Plate
p = new Plate(new Fruit());
}
}
为什么上面这个会出现编译不通过??
解释:苹果是水果,但是装苹果的盘子不是装水果的盘子
所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的
为了让泛型用起来更舒服,Sun的大脑袋们就想出了 extends T>和 super T>的办法,来让”水果盘子“和”苹果盘子“之间发生关系
什么是上界?
Plate
翻译成人话就是:一个能放水果以及一切是水果派生类的盘子。再直白点就是:啥水果都能放的盘子。这和我们人类的逻辑就比较接近了。Plate和Plate最大的区别就是:Plate是Plate以及Plate的基类。直接的好处就是,我们可以用“苹果盘子”给“水果盘子”赋值了。
Plate extends Fruit> p=new
Plate(new Apple());
如果把Fruit和Apple的例子再扩展一下
//Lev 1
class Food{}
//Lev 2
class Fruit extends Food{}
class Meat extends Food{}
//Lev 3
class Apple extends Fruit{}
class Banana extends Fruit{}
class Pork extends Meat{}
class Beef extends Meat{}
//Lev 4
class RedApple extends Apple{}
class GreenApple extends Apple{}
在这个体系中,上界通配符
Plate
Fruit> 覆盖下图中蓝色的区域
什么是下界?
Plate
表达的就是相反的概念:一个能放水果以及一切是水果基类的盘子。Plate是Plate的基类,但不是Plate的基类。对应刚才那个例子,Plate覆盖下图中红色的区域。
上下界通配符的副作用
边界让Java不同泛型之间的转换更容易了。但不要忘记,这样的转换也有一定的副作用。那就是容器的部分功能可能失效。
还是以刚才的Plate为例。我们可以对盘子做两件事,往盘子里set()新东西,以及从盘子里get()东西。
public class TestGenericParadigm {
public
static void main(String[] args){
/*
* 上界通配符
* 不能存入任何元素
* 读出来的东西只能放在Fruit或它的基类中
*/
Plate
extends Fruit> p = new Plate(new Apple());
p.setItem(new
Fruit()); //Error
p.setItem(new
Apple()); //Error
Fruit
newFruit1 = p.getItem();
Object
newFruit2 = p.getItem();
Apple
new Fruit3 = p.getItem();//Error
}
}
原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道
所以通配符>和类型参数的区别就在于,对编译器来说所有的T都代表同一种类型。比如下面这个泛型方法里,三个T都指代同一个类型,要么都是String,要么都是Integer。
但通配符>没有这种约束,Plate>单纯的就表示:盘子里放了一个东西,是什么我不知道
下界 super
T>不影响往里存,但往外取只能放在Object对象里
使用下界
super Fruit>会使从盘子里取东西的get(
)方法部分失效,只能存放到Object对象里。set( )方法正常。
/*
* 下界通配符
* 存入元素正常
* 读取的东西只能存放在Object中
*/
Plate
super Fruit> p1 = new Plate(new Fruit());
p1.setItem(new
Fruit());
p1.setItem(new
Apple());
Apple
newFruit4 = p1.getItem();//Error
Fruit
newFruit5 = p1.getItem();//Error
Object
newFruit6 = p1.getItem();
因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失
PECS原则
最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:
频繁往外读取内容的,适合用上界Extends。
经常往里插入的,适合用下界Super。