一、简介
1、什么是泛型
Java自从Jdk1.5后引用泛型,解决了容器类型安全问题,其本质是参数化类型,就是指将所操作的数据类型作为参数的一种语法。不过Java的泛型其实是一种伪泛型,只在编译期有效,运行时会类型擦除,不像C++的模板,是在运行期也有效。
2、作用
(1)、将代码安全性检查提前到编译器
Jdk1.5引用泛型前,比如
List<Apple> apples = new ArrayList<>();
apples.add(new Banana());
这类错误,需要在运行时才能检查出来
Jdk1.5后,可以让编译器在编译的时候就借助传入参数类型检查对容器的插入,获取操作是否合法,从而将运行时类转换异常,提前到编译时。
(2)、提高代码复用性
使用泛型,可以写出更加通用的代码,提高了灵活性,这在自己写框架和第三方框架用的比较多。
(3)、可以省去类型转换
在Jdk1.5前,Java容器都是通过向上转型Object类型来实现的,当取出对象时候需要强制向下强制转换。加入泛型后,编译期会自动强制转换,省略了很多代码。
3、历史演变
在JDK1.5前,需要使用泛型作用的地方都是通过Object向上转型,这会存在安全隐患 ,在获取“真正”的数据的时候,如果不小心强制转换成了错误类型,这种错误只能在真正运行的时候才能发现。
因此Java 1.5推出了“泛型”,也就是在原本的基础上加上了编译时类型检查的语法糖。Java 的泛型推出来后,引起来很多人的吐槽,因为相对于C++等其他语言的泛型,Java的泛型代码的灵活性依然会受到很多限制。这是因为Java被规定必须保持二进制向后兼容性,也就是一个在Java 1.4版本中可以正常运行的Class文件,放在Java 1.5中必须是能够正常运行的
在1.5之前,这种类型的代码是没有问题的。
public static void addRawList(List list){
list.add("123");
list.add(2);
}
1.5之后泛型大量应用后:
public static void addGenericList(List<String> list){
list.add("1");
list.add("2");
}
虽然我们认为addRawList()
方法中的代码不是类型安全的,但是某些时候这种代码是有用的,在设计JDK1.5的时候,想要实现泛型有两种选择:
- 需要泛型化的类型(主要是容器(Collections)类型),以前有的就保持不变,然后平行地加一套泛型化版本的新类型;
- 直接把已有的类型泛型化,让所有需要泛型化的已有类型都原地泛型化,不添加任何平行于已有类型的泛型版。
什么意思呢?也就是第一种办法是在原有的Java库的基础上,再添加一些库,这些库的功能和原本的一模一样,只是这些库是使用Java新语法泛型实现的,而第二种办法是保持和原本的库的高度一致性,不添加任何新的库。
在出现了泛型之后,原本没有使用泛型的代码就被称为raw type
(原始类型)
Java 的二进制向后兼容性使得Java 需要实现前后兼容的泛型,也就是说以前使用原始类型的代码可以继续被泛型使用,现在的泛型也可以作为参数传递给原始类型的代码。
比如
List<String> list=new ArrayList<>();
List rawList=new ArrayList();
addRawList(list);
addGenericList(list);
addRawList(rawList);
addGenericList(rawList);
上面的代码能够正确的运行。
Java 设计者选择了第二种方案
为了实现以上功能,Java 设计者将泛型完全作为了语法糖加入了新的语法中,什么意思呢?也就是说泛型对于JVM来说是透明的,有泛型的和没有泛型的代码,通过编译器编译后所生成的二进制代码是完全相同的。
这个语法糖的实现被称为擦除
擦除的过程
泛型是为了将具体的类型作为参数传递给方法,类,接口。
擦除是在代码运行过程中将具体的类型都抹除。
前面说过,Java 1.5 之前需要编写模板代码的地方都是通过Object
来保存具体的值。比如:
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();
}
}
这样的实现能满足绝大多数需求,但是泛型还是有更多方便的地方,最大的一点就是编译期类型检查,于是Java 1.5之后加入了泛型,但是这个泛型仅仅是在编译的时候帮你做了编译时类型检查,成功编译后所生成的.class
文件还是一模一样的,这便是擦除
1.5 以后实现
public class Node<T>{
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<Student> node=new Node<>();
node.set(stu);
Student stu2=node.get();
}
}
两个版本生成的.class文件:
Node:
public Node();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()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
}
Node
public class Node<T> {
public Node();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()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
}
可以看到泛型就是在使用泛型代码的时候,将类型信息传递给具体的泛型代码。而经过编译后,生成的.class
文件和原始的代码一模一样,就好像传递过来的类型信息又被擦除了一样。
二、泛型的类型
1、类泛型
public class GenericClass<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
类名后面接 <T>
2、接口泛型
public interface GenericInterface<T> {
T getValue(T value);
}
接口名后面接<T>
3、方法泛型
private static <T extends Number & Comparable<T>> T max(T a, T b) {
if(a.compareTo(b)>=0){
return a;
}else{
return b;
}
}
方法名前面加<T>
三、泛型常见的定义
1、定义
E:元素(Element),多用于java集合框架
K:关键字(Key)
N:数字(Number)
T:类型(Type)
V:值(Value)
2、多个泛型类型参数
第二个、第三个、第四个参数: S U V
private static <T extends Integer, S extends Integer, U extends Integer, V extends Integer> int sum(T t, S s, U u, V v) {
return t.intValue()+s.intValue()+u.intValue()+v.intValue();
}
3、泛型继承多个类和接口的情况
因为java时单继承,所以最多只能一个类,但可以实现多个接口,使用泛型的时候,类必须在 extends前面
interface A{
void a();
}
interface B{
void b();
}
class C{
void c(){
}
}
class D<T extends C & A & B>{
}
四、泛型的通配符
1、extend
上界通配符
class C{
void c(){
}
int getValue(){
return 0;
}
}
private void printCList(List<? extends C> list){
for (C c: list){
System.out.println(c.getValue());
}
}
能够接受 C类 或 C的子类
遵循 PESC原则(生产者、extend‘、super、消费)的原则
上界通配符 只能读数据,不能写数据
2、super
下界通配符
private void addC(List<? super C> list, C c){
list.add(c);
}
能够接受C类和C的父类
遵循 PESC原则(生产者、extend‘、super、消费)的原则
下界通配符 只能写数据,不能读数据
3、?
无界通配符
private void showList(List<?> list){
for(Object object:list){
System.out.println(object);
}
}
?可以接受任何类型,类似于 T泛型,只是不用在方法前定义