什么是泛型:也许有会说是广泛的参数类型,但是泛型的本质其实是:参数化类型。泛型的特点是泛,经常用于一些传入的参数,或者是存储参数不确定的场景。
泛型是java1.5之后出现的内容。在泛型出现之前,我们很多时候都是利用强制转换来实现泛型的。这样虽然在功能层面上会满足需求。但是又一个巨大的安全问题就是类型安全。因为没有泛型的时候,你写的类型转换它不会帮你去看的,如果这时候你把一个Object转换成一个你其实不想要的类型的时候的,代码是不会出现问题的。
我们经常会看到这样的代码:
Appler apple = (Apple) xx
使用泛型之后,在一开始的时候,程序其实不知道我们传进去的参数是什么,但是在参数传进去之后,程序就会知道,原来你现在传进来的参数是Apple类型的。那么下次你在过去你的Apple的时候只要像下面这么写就好了。
Apple apple = xx
为什么这么说呢?举个例子如果你又一个举动是削皮,那么现在手里有两个物体:一个是苹果,一个是地瓜。这下继承没有办法解决了。那么你现在需要给这两个物体进行削皮,总不能买两个削皮刀来削皮吧。这样的话代码重复度就太高了。所以这是对削皮这个动作做泛型的处理。就可以解决了。
引用一句话:模版/泛型代码,就好像做雕塑时候的模版,有了模版,需要生产的时候只需要向里面注入具体的材料就行。根据材料的不同可以产生不一样的效果。这就是泛型的宗旨。
如果没有泛型的存在,那么以下代码如果你没有运行那么是找不到错误的。
Apple apple = new Apple()
List apples = new ArrayList();
apples.add(new Orange());
但是有了泛型之后你在写代码的过程中就会发现这个问题。
这时候的泛型是在原本的基础上面加上了一个语法糖。也就是一个.class文件能够在1.4的环境下面编译,那么他也能在1.5的环境下面编译。
那么两者间有什么区别呢?
1.5之前这样的代码是不会检测出来问题的。
List list = new ArrayList();
list.add(1);
list.add(1.1);
1.5之后应该是下面这样的。
List list = new ArrayList<>();
list.add("1");
list.add("1.1");
list.add(1)//error
按照现在的方式来看,前者的代码是不安全的。但是也许有些地方会使用到。后面我去查了一下在1.5的时候想要实现泛型有两种方式:
第一种:在原先的jar依赖包里面在增加一些库,这些库和原先的一样,区别在于新的库是由泛型实现的。
第二种:不添加任何库。
##后面实现是选了第二种##--->擦除
顾名思义就是除去一些东西,具体是在那里去除呢?是在我们代码编译成class文件的过程中除去了传进来的类型。我们上面也说了,如果要保持一个.class文件能够在1.4的环境下面编译,那么他也能在1.5的环境下面编译的情况。而1.5之前基本上都是利用强转,利用Object来存放信息的。虽然能满足一定的需求,但是还是存在编译时候的一些问题。而泛型的引入其实可以理解为只是帮你在你写代码的时候为你做了检查,但是你编译出来的.class文件和之前没有泛型的时候是一样的。这个就是擦除,擦掉传进来的类型。
引用一个网上编译后的代码
1.5之前的实现
public class Node{
private Object obj;
public Object get(){
return obj;
}
public void set(Object obj){
this.obj=obj;
}
public static void main(String[] argv){
Student stu=new Student();
Node node=new Node();
node.set(stu);
Student stu2=(Student)node.get();
}
}
1.5之后的实现
public class Node{
private T obj;
public T get(){
return obj;
}
public void set(T obj){
this.obj=obj;
}
public static void main(String[] argv){
Student stu=new Student();
Node node=new Node<>();
node.set(stu);
Student stu2=node.get();
}
}
两个版本产生的.class文件
public Node();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public java.lang.Object get();
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public void set(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
}
public class Node {
public Node();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public T get();
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
}
是不是一样的。
介绍这个规则之前,先说一下下面下面这三个规则:
协变就是extend
在泛型里面其实是重载了extend。我们可以像下面这样写
public void test(List extends Fruit> list);
然后可以这样去用
List list = new ArrayList<>();
List list1 = new ArrayList<>();
test(list);
test(list1);
List list2 = new ArrayList<>();
test(list2);//error
我们可以看到我们方法里面要求了必须是继承Fruit的类才能使用。但是,如果这时候有以下的操作的时候是会出现问题的
public void test(List extends Fruit> list){
list.add(new Orange());
}
为什么会出问题呢?因为虽然你Orange是Fruit的子类,但是你无法知道这时候list是不全部是Apple。如果是的话,那么吧一个Orange加到Apple里面去的话,这样不就出问题类吗?
但是这样就没问题了
public void test(List extends Fruit> list){
Fruit fruit=list.get(0);
}
和上面的道理一样,只是这个时候实现的是super。
public void test(List list);
List
它只允许是Fruit的父类使用。
但是下面的操作的时候,他又会出问题:因为你不知道这个父类是不是对的。是不是有那味了?
public void test(List super Fruit> list){
Food obj=list.get(0);
}
但是现在就没有问题了
public void test(List super Fruit> list){
list.add(new Apple());
}
以上就是PECS原则:Producer-Extend,Customer-Super
,也就是泛型代码是生产者,使用Extend:意思就是泛型的代码如果是生产数据的话,那么就用extends,应为你能够确定这个?的最终类型是什么。
泛型代码作为消费者 使用Super:如果这个泛型代码是消费数据的话,那么就用Super,因为至少你能够确定你的子类,最后是会上转为你指定的父类的。
##结束##