基础语法
在编程中我们总是希望我们的代码能得到很好的重用,但在没有泛型之前,我们的代码都是在具体类上进行的,如果我们要写一个功能相似的类或方法使它能应用到更多的类型上,我们就只能针对不同的类型写不同的方法,这样做不仅繁琐而且扩展性非常不好(每当我们需要将这个功能应用到新的类型上时就要重新写一个类或方法)。比如我们要写一个方法实现两个数相加,在不应用泛型时,我们就只能这样写:
class Test {
int add(int a, int b) {
return a + b;
}
float add(float a, float b) {
return a+b;
}
double add(double a, double b) {
return a+b;
}
}
为了很好地解决此类问题,Java引入了泛型编程。当要对类使用泛型编程时,只需要在类申明时在类名的后面加上”< T >”(其中T为泛型类型,可以用任意标识符,不过通常都选用T、E之类的大写字母,如果需要多个泛型类型,只需要在<>中依次申明这些类型,用”,”将其隔开就可以了),这样申明之后我们就可以在类中将T当作普通的类型使用了,只不过我们在类中我们并不知道这个T具体是什么类型就是了,比如:
class ObjectHolder<T> {
private T t = null;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
@Override
public String toString() {
if (t !=null) {
return t.toString();
} else {
return "null";
}
}
}
public class Test {
public static void main(String[] args) {
ObjectHolder<String> strHolder = new ObjectHolder<>();
strHolder.set("Hello World");
String s = strHolder.get();
ObjectHolder<Integer> intHolder = new ObjectHolder<>();
intHolder.set(3);
int i = intHolder.get();
//intHolder.set("Test");//compile error,intHolder只能作用在Integer上
}
}
从上面的例子我们可以看到ObjectHolder可以应用到任何类型上,不过在ObjectHolder这个类中,我们并不知道T这个泛型类型对应的具体类型到底是什么,所以我们对t这个对象只能调用Object类中的方法(我们知道T肯定是Object或其子类,所以Object类中的方法是可以使用的)。虽然ObjectHolder可以应用到任何类型上,不过当我们申明ObjectHolder的对象将其限定在某一类型上时,那么它就只能对这种类型做相应的操作了,比如上例中的intHolder调用set(“Test”)就会报编译错误。
对方法使用泛型编程和对类使用泛型编程差不多,我们只需要在申明方法时在方法的返回类型前面申明泛型类型列表就可以了,比如:
class Test {
static <T> void test(T t) {
System.out.println(t.toString());
}
}
擦除
泛型是Java在JDK1.5中引入的,在JDK1.5之前要实现ObjectHolder类似的功能就只能按照下面的方式:
class ObjectHolder {
private Object obj;
public Object get() {
return obj;
}
public void set(Object obj) {
this.obj = obj;
}
}
public class Test {
public static void main(String[] args) {
ObjectHolder holder = new ObjectHolder();
holder.set(3);
int i = (int)holder.get();
System.out.println(i);
holder.set("hello world");
String s = (String) holder.get();
System.out.println(s);
//Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
int k = (int) holder.get();
System.out.println(k);
}
}
我们可以看到虽然ObjectHolder也实现了类似的功能,但同一个ObjectHolder对象可以存储不同类型的数据(因为ObjectHolder内部使用的是Object,而在Java中任何类型都是Object的子类),而且在调用get方法的时候我们得到的也只是一个Object对象,如果我们要得到具体类型的对象,我们需要强制类型转换,加入ObjectHolder内部存储对象的类型和我们强制类型转换的类型不一致在运行时就会抛出异常,比如上面的int k = (int) holder.get();为了解决上述问题,在JDK1.5中引入了泛型编程,但为了能够兼容那些使用旧版本JDK的代码(比如我们在开发一个应用时用到了第三方类库A,B,而A,B代码都是在JDk1.5之前的版本-比如1.4基础上进行开发的,那么A,B中就没有使用泛型编程-像容器等基础类库,而我们的代码很多地方都可能是在泛型编程基础上进行的,这时就存在对A,B代码兼容的问题),Java泛型使用的是运行时擦除机制。
其实Java泛型编程中的类型参数只不过是编译时的类型占位符而已,也就是在编译期间表明这是某一具体类型,但到底是什么具体类型编译器并不关心(编译器只知道它是某一限定类型,在类型参数后面使用extends Type,就表示该类型参数是Type类型或其子类型,其中Type必须是某一具体类型)。编译完成后,类型参数信息会被擦除(擦除到限定类型),所以泛型编程只不过是为我们建立起了编译期间的类型检查而已。这些我们通过反编译.class文件就可以得到,还是用ObjectHolder作为例子:
class ObjectHolder<T> {
T t = null;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
ObjectHolder<Integer> intHolder = new ObjectHolder<>();
intHolder.set(3);
int k = intHolder.get();
}
}
编译后我们得到ObjectHolder.class文件,我们再运行javap -verbose ObjectHolder就可以得到下面这些信息:
Classfile /Users/belows/Desktop/Java/ObjectHolder.class
Last modified 2016-11-11; size 740 bytes
MD5 checksum 8b1b4b5395329ded60f47771b6b1d522
Compiled from "ObjectHolder.java"
public class ObjectHolder<T extends java.lang.Object> extends java.lang.Object
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#30 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#31 // ObjectHolder.t:Ljava/lang/Object;
#3 = Class #32 // ObjectHolder
#4 = Methodref #3.#30 // ObjectHolder."<init>":()V
#5 = Methodref #8.#33 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#6 = Methodref #3.#34 // ObjectHolder.set:(Ljava/lang/Object;)V
#7 = Methodref #3.#35 // ObjectHolder.get:()Ljava/lang/Object;
#8 = Class #36 // java/lang/Integer
#9 = Methodref #8.#37 // java/lang/Integer.intValue:()I
#10 = Class #38 // java/lang/Object
#11 = Utf8 t
#12 = Utf8 Ljava/lang/Object;
#13 = Utf8 Signature
#14 = Utf8 TT;
#15 = Utf8 <init>
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 get
#20 = Utf8 ()Ljava/lang/Object;
#21 = Utf8 ()TT;
#22 = Utf8 set
#23 = Utf8 (Ljava/lang/Object;)V
#24 = Utf8 (TT;)V
#25 = Utf8 main
#26 = Utf8 ([Ljava/lang/String;)V
#27 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;
#28 = Utf8 SourceFile
#29 = Utf8 ObjectHolder.java
#30 = NameAndType #15:#16 // "<init>":()V
#31 = NameAndType #11:#12 // t:Ljava/lang/Object;
#32 = Utf8 ObjectHolder
#33 = NameAndType #39:#40 // valueOf:(I)Ljava/lang/Integer;
#34 = NameAndType #22:#23 // set:(Ljava/lang/Object;)V
#35 = NameAndType #19:#20 // get:()Ljava/lang/Object;
#36 = Utf8 java/lang/Integer
#37 = NameAndType #41:#42 // intValue:()I
#38 = Utf8 java/lang/Object
#39 = Utf8 valueOf
#40 = Utf8 (I)Ljava/lang/Integer;
#41 = Utf8 intValue
#42 = Utf8 ()I
{
T t;
descriptor: Ljava/lang/Object;
flags:
Signature: #14 // TT;
public ObjectHolder();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aconst_null
6: putfield #2 // Field t:Ljava/lang/Object;
9: return
LineNumberTable:
line 1: 0
line 2: 4
public T get();
descriptor: ()Ljava/lang/Object;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field t:Ljava/lang/Object;
4: areturn
LineNumberTable:
line 5: 0
Signature: #21 // ()TT;
public void set(T);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #2 // Field t:Ljava/lang/Object;
5: return
LineNumberTable:
line 9: 0
line 10: 5
Signature: #24 // (TT;)V
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #3 // class ObjectHolder
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_3
10: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
16: aload_1
17: invokevirtual #7 // Method get:()Ljava/lang/Object;
20: checkcast #8 // class java/lang/Integer
23: invokevirtual #9 // Method java/lang/Integer.intValue:()I
26: istore_2
27: return
LineNumberTable:
line 13: 0
line 14: 8
line 16: 16
line 17: 27
}
Signature: #27 // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "ObjectHolder.java"
在其中我们找到
我们可以看到变量t的描述符是Ljava/lang/Object,也就是说在编译完成后t实际上就已经是一个Object对象了,那如果这样的话我们使用泛型和不使用泛型的区别又是什么呢?在main函数中我们可以看到
在main函数中调用了get方法后,又马上将其转换为了Integer类,也就是说泛型编程在set和get边界在编译阶段为我们建立起了类型检查,使我们对ObjectHolder< Integer >类型的对象只能set Integer,并且只get后自动为我们进行了类型转换,在编译期间通过对参数列表的检查保证这样的类型转换是安全的。
类型限定
上面说到java中的泛型是将类型参数擦除到限定类型,那么什么是限定类型呢?在类型参数后面使用extends Type,其中Type就是限定类型(Type必须是某一具体类型)。比如:
class Person {
int id;
}
class Student extends Person {
}
class Teacher extends Person {}
public class PersonHolder<T extends Person> {
T t = null;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
PersonHolder<Teacher> teacherHolder = new PersonHolder<>();
teacherHolder.set(new Teacher());
Teacher teacher = teacherHolder.get();
PersonHolder<Student> studentHolder = new PersonHolder<>();
studentHolder.set(new Student());
Student student = studentHolder.get();
}
}
反编译PersonHolder.class后得到下面这些信息:
Classfile /Users/belows/Desktop/Java/PersonHolder.class
Last modified 2016-11-11; size 681 bytes
MD5 checksum 7be01dea0cd9ec6a253d15df60c29f8c
Compiled from "PersonHolder.java"
public class PersonHolder<T extends Person> extends java.lang.Object
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #11.#31 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#32 // PersonHolder.t:LPerson;
#3 = Class #33 // PersonHolder
#4 = Methodref #3.#31 // PersonHolder."<init>":()V
#5 = Class #34 // Teacher
#6 = Methodref #5.#31 // Teacher."<init>":()V
#7 = Methodref #3.#35 // PersonHolder.set:(LPerson;)V
#8 = Methodref #3.#36 // PersonHolder.get:()LPerson;
#9 = Class #37 // Student
#10 = Methodref #9.#31 // Student."<init>":()V
#11 = Class #38 // java/lang/Object
#12 = Utf8 t
#13 = Utf8 LPerson;
#14 = Utf8 Signature
#15 = Utf8 TT;
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 set
#21 = Utf8 (LPerson;)V
#22 = Utf8 (TT;)V
#23 = Utf8 get
#24 = Utf8 ()LPerson;
#25 = Utf8 ()TT;
#26 = Utf8 main
#27 = Utf8 ([Ljava/lang/String;)V
#28 = Utf8 <T:LPerson;>Ljava/lang/Object;
#29 = Utf8 SourceFile
#30 = Utf8 PersonHolder.java
#31 = NameAndType #16:#17 // "<init>":()V
#32 = NameAndType #12:#13 // t:LPerson;
#33 = Utf8 PersonHolder
#34 = Utf8 Teacher
#35 = NameAndType #20:#21 // set:(LPerson;)V
#36 = NameAndType #23:#24 // get:()LPerson;
#37 = Utf8 Student
#38 = Utf8 java/lang/Object
{
T t;
descriptor: LPerson;
flags:
Signature: #15 // TT;
public PersonHolder();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aconst_null
6: putfield #2 // Field t:LPerson;
9: return
LineNumberTable:
line 10: 0
line 11: 4
public void set(T);
descriptor: (LPerson;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #2 // Field t:LPerson;
5: return
LineNumberTable:
line 13: 0
line 14: 5
Signature: #22 // (TT;)V
public T get();
descriptor: ()LPerson;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field t:LPerson;
4: areturn
LineNumberTable:
line 17: 0
Signature: #25 // ()TT;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=5, args_size=1
0: new #3 // class PersonHolder
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: new #5 // class Teacher
12: dup
13: invokespecial #6 // Method Teacher."<init>":()V
16: invokevirtual #7 // Method set:(LPerson;)V
19: aload_1
20: invokevirtual #8 // Method get:()LPerson;
23: checkcast #5 // class Teacher
26: astore_2
27: new #3 // class PersonHolder
30: dup
31: invokespecial #4 // Method "<init>":()V
34: astore_3
35: aload_3
36: new #9 // class Student
39: dup
40: invokespecial #10 // Method Student."<init>":()V
43: invokevirtual #7 // Method set:(LPerson;)V
46: aload_3
47: invokevirtual #8 // Method get:()LPerson;
50: checkcast #9 // class Student
53: astore 4
55: return
LineNumberTable:
line 21: 0
line 22: 8
line 23: 19
line 25: 27
line 26: 35
line 27: 46
line 28: 55
}
Signature: #28 // <T:LPerson;>Ljava/lang/Object;
SourceFile: "PersonHolder.java"
找到
对比之前的ObjectHolde< Integer >,我们可以看到成员变量t,set的形参和get的返回值都被擦除到了Person,因为我们使用了在定义PersonHolder时使用了PersonHolder< T extends Person>,也就是类型参数T的限定类型是Person,所以在PersonHolder类编译后T就会被擦除到Person,也就如上面的结果所示。