1.1泛型概述
Java泛型(generics)是JDK5中引入的一个特性,泛型提供了编译时安全监测机制,该机制允许程序员在编译时监测非法的类型。使用泛型机制编写的程序代码要比那些杂乱地使用Object
变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用。
泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。
1.2 泛型的好处
在不使用泛型的情况下,我们可以使用Object类型来实现任意的参数类型,但是在使用时需要我们强制类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是在编译期我们无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译器就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码可读性
总结,两好处:
- 代码可读性更好【不用强制转换】
- 程序更加安全【只要编译时期没有警告,运行时期就不会出现ClassCastException】
1.3 类型擦除
编码时采用泛型写的类型参数,编译期会在编译时去掉,这称为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数和编译后会被替换成Object,运行时虚拟机不知道泛型。
泛型主要是为了方便程序员的代码编写,以及更好的安全性检测。
下面具体介绍一波类型擦除
类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类;
public class Holder4<T> {
private T a;
private T b;
private T c;
public Holder4(T a, T b, T c) {
this.a = a;
this.b = b;
this.c = c;
}
public T getA() {
return a;
}
public T getB() {
return b;
}
public T getC() {
return c;
}
public void setA(T a) {
this.a = a;
}
public void setB(T b) {
this.b = b;
}
public void setC(T c) {
this.c = c;
}
public static void main(String[] args) {
Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());
Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型
Automobile b = holder4.getB();
Automobile c = holder4.getC();
}
}
为什么选择这种实现机制?
- 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型;
- Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了“类型擦除”这种折中的实现方式。
Java泛型依赖编译器实现,只存在于编译期,JVM中没有泛型的概念;那么,编译器做了什么工作呢?(1)set方法是编译期检查;(2)get方法的返回值进行转型,编译器插入了一个checkcast语句。
我们通过字节码进行观察,可以看出:(1)Holder4和Holder4Raw两个类的字节码完全相同;(2)在main函数的33、41和49行就是编译器插入的checkcast语句;
public class org.java.learn.generics.Holder4<T> {
public org.java.learn.generics.Holder4(T, T, T);
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #2 // Field a:Ljava/lang/Object;
9: aload_0
10: aload_2
11: putfield #3 // Field b:Ljava/lang/Object;
14: aload_0
15: aload_3
16: putfield #4 // Field c:Ljava/lang/Object;
19: return
public T getA();
Code:
0: aload_0
1: getfield #2 // Field a:Ljava/lang/Object;
4: areturn
public T getB();
Code:
0: aload_0
1: getfield #3 // Field b:Ljava/lang/Object;
4: areturn
public T getC();
Code:
0: aload_0
1: getfield #4 // Field c:Ljava/lang/Object;
4: areturn
public void setA(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field a:Ljava/lang/Object;
5: return
public void setB(T);
Code:
0: aload_0
1: aload_1
2: putfield #3 // Field b:Ljava/lang/Object;
5: return
public void setC(T);
Code:
0: aload_0
1: aload_1
2: putfield #4 // Field c:Ljava/lang/Object;
5: return
public static void main(java.lang.String[]);
Code:
0: new #5 // class org/java/learn/generics/Holder4
3: dup
4: new #6 // class org/java/learn/generics/Automobile
7: dup
8: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
11: new #6 // class org/java/learn/generics/Automobile
14: dup
15: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
18: new #6 // class org/java/learn/generics/Automobile
21: dup
22: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
25: invokespecial #8 // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
28: astore_1
29: aload_1
30: invokevirtual #9 // Method getA:()Ljava/lang/Object;
33: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
36: astore_2
37: aload_1
38: invokevirtual #10 // Method getB:()Ljava/lang/Object;
41: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
44: astore_3
45: aload_1
46: invokevirtual #11 // Method getC:()Ljava/lang/Object;
49: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
52: astore 4
54: return
}
2.1 定义泛型
2.2 泛型类
泛型类就是爸爸泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型的具体使用方法是在类的名称后添加一个或多个类型的参数声明,如:<T>
, <T, K, V>
public class 类名<泛型表示符号>{
}
2.3 泛型接口
泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
public interface 接口<泛型表示符号>{
}
2.4泛型方法
泛型类中所定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅一某一个方法上使用泛型,这时候可以使用泛型方法。
泛型方法是指将方法的参数类型定义成泛型,以便在调用时接受不同类型的参数。类型参数可以有多个,用逗号隔开,如:<K,V>.定义时,类型参数一般放到返回值前面。
调用泛型方法时,不需要像泛型类那样告诉编译器什么类型,编译器可以自动推断出来。
2.4.1 非静态方法
public <泛型表示符号> void getName(泛型表示符号 name){
}
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
2.4.2 静态方法
静态方法中使用泛型时有一种情况需要注意,那就是静态方法无法访问类上定义泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static <泛型表示符号> void getName(泛型表示符号 name){
}
public static <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
2.5 通配符和上下限定
2.5.1 无界统配符
“?”表示类型通配符,用于代替具体的类型。他只能在“<>”中使用。可以解决当具体类型不确定的问题。
public void showFlag(Generic<?> generic){
}
2.5.2 通配符的上限限定
上限限定表示通配符的类型是T类以及T类的之类或者T接口以及T接口的子接口。
“上限限定”可以理解为限定了上限,所以只能是自己或者自己的孩子。
public void showFlag(Generic<? extends Number> generic){
}
2.5.3 统配符的下限限定
下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的父接口。
注意:该方法不适用于泛型类。
3.1泛型总结
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型的类型信息。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况错误:
1.基本类型不能用于泛型。
Test<int> t; 这样写法错误,我们可以使用对应的包装类;Test<Integer> t;
2.不能通过类型参数创建对象。
T elm = new T(); 运行时类型参数T会被替换成Object,无法创建T类型的对象,容易引起误解,所以在Java中不支持这种写法。
3.1泛型总结
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型的类型信息。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况错误:
1.基本类型不能用于泛型。
Test<int> t; 这样写法错误,我们可以使用对应的包装类;Test<Integer> t;
2.不能通过类型参数创建对象。
T elm = new T(); 运行时类型参数T会被替换成Object,无法创建T类型的对象,容易引起误解,所以在Java中不支持这种写法。