注:以下内容基于Java 8,所有代码都已在Java 8环境下测试通过
目录:
Java定义泛型类与定义非泛型类的方法类似,只是需要自类名后面添加类型参数。类型参数用尖括号括住,放在类名后面,泛型类接受多个类型参数,参数间用逗号隔开。具体的定义格式如下:
class 类名 <泛型标记符1, 泛型标记符2……> {
private 泛型标记符1 t1;
private 泛型标记符2 t2;
private void setT1(泛型标记符1 t1) {
this.t1 = t1;
}
……
}
其中,泛型标记符相当于一个类型占位符,提示这里需要一个类型参数并暂时使用泛型标记符表示,具体是哪种类型需要等实际使用也就是实例化对象的时候才能确定。
泛型标记符是可以任意设置的,但为了方便阅读代码,建议遵循以下规范:
T:任意的Java类
E:集合中的元素
K:键值对中的键
V:键值对中的值
N:数值类型
比如,下面这个泛型类:
class Test<T, SetItAnyWill> {
private T a;//成员变量a的数据类型为 T ,T 代表的具体类型在实例化时指定
private SetItAnyWill b;//成员变量b的数据类型为 SetItAnyWill ,SetItAnyWill 代表的具体类型在实例化时指定
public void setA(T aValue) {
this.a = aValue;
}
public void setB(SetItAnyWill bValue) {
this.b = bValue;
}
public T getA() {
return this.a;
}
public SetItAnyWill getB() {
return this.b;
}
}
这个泛型类包含两个泛型标记符,分别叫做 T
和 SetItAnyWill
(之所以取这个名字,只是想再展示一下泛型标记符可以任意设置),至于 T
和 SetItAnyWill
具体代表的是什么类型,只有实例化这个类时(见下面“使用泛型类”)才会确定下来。
注意,泛型类中静态变量和静态方法不可以使用定义泛型类时使用的泛型标记符(静态变量和静态方法在类加载时已经初始化,而此时泛型类的类型参数还未指定,因此不能使用泛型类的泛型标记符),但静态方法可以在定义时使用新的泛型标记符(此时,静态方法成为泛型方法)。如:
class Test<T, SetItAnyWill> {
static T staticA;//编译时报错,静态变量不允许使用定义泛型类时用的泛型标记符
static void setStaticA(T staticAValue) {//编译时报错,静态方法不允许使用定义泛型类时用的泛型标记符
}
static <N> void print(N number){//静态方法可以在定义时使用新的泛型标记符,泛型标记符在定义泛型类时未使用过
System.out.println(number);
}
}
泛型方法如何定义和使用后面会有具体的介绍。
有两种实例化一个泛型类的对象的格式,一种是Java5开始使用的语法,一种是从Java7开始使用的简写语法,如下:
类名<数据类型1, 数据类型2……> 对象名 = new 类名<>();//Java7开始使用的简写语法
类名<数据类型1, 数据类型2……> 对象名 = new 类名<数据类型1, 数据类型2……>();//Java5使用的语法
比如:
public class Main {
public static void main(String[] args) {
Test<String, Integer> test = new Test<>(); //T 被替换为 Sting,SetItAnyWill 被替换为 Integer
test.setA("this is string");
test.setB(10);
//test.setA(20);//编译报错,对于 test 来说 setA()方法需要传入一个字符串
//test.setB("this is string");//编译报错,对于 test 来说 setB()方法需要传入一个整形变量
Test<Integer, Character> another = new Test<>(); //T 被替换为 Integer,SetItAnyWill 被替换为 Character
another.setA(30);
another.setB('a');
//another.setA(true);//编译报错,对于 another 来说 setA()方法需要传入一个整形变量
//another.setB("cde");//编译报错,对于 another 来说 setA()方法需要传入一个字符
}
}
为了便于理解,当使用Test
实例化一个泛型类后,我们可以认为编译器会将上面定义的泛型类根据<>中传入的数据类型进行泛型标记符替换(仅仅是便于理解,实际不是这样的),替换后的代码如下:
//泛型标记符 T 被替换为 String
//泛型标记符 SetItAnyWill 被替换为 Integer
class Test{
private String a;
private Integer b;
public void setA(String aValue) {
this.a = aValue;
}
public void setB(int bValue) {
this.b = bValue;
}
public String getA() {
return this.a;
}
public Integer getB() {
return this.b;
}
}
在使用泛型类时需要注意以下几点:
传入的类型参数需要和定义泛型类时的数目保持一致
可以不传入类型参数,此时默认传入的是Object类,比如:
Test test1 = new Test();//不传入类型参数
这相当于:
Test
类型参数只能是引用类型,不能是基本类型
Java有8种基本类型,这8种类型在使用泛型类时不能作为类型参数传入,只能使用对应的封装类型
基本类型及其对应的封装类:
基本类型 封装类 字节数 取值范围 默认值 备注 int Integer 32 -2^31 ~ 2^31-1 0 整型 short Short 16 -2^15 ~ 2^15-1 0 短整型 long Long 64 -2^63 ~ 2^63-1 0 长整型 float Float 32 -2^128 ~ 2^128 0.0f或0.0F 单精度浮点型 double Double 64 -2^1024 ~ 2^1024 0 双精度浮点型,默认的小数类型 char Character 16 ‘\u0000’ ~ ‘\uFFFF’ ‘\u0000’ 字符型,Unicode编码 boolean Boolean 1 true 或 false false 布尔型 byte Byte 8 -2^7 ~ 2^7-1 0 字符型,数据在内存中的最原始状态
泛型类也可以继承,任何一个泛型类都可以作为父类或子类。不过泛型类在继承时需要注意以下几点:
子类是泛型类,
class Father<T> {
}
//子类中与父类同名的泛型标记符可以传递给父类
//子类的泛型标记符包含父类的泛型标记符
class Child_1<T> extends Father<T> {
}
class Child_2<T, U, V, W> extends Father<T> {
}
//子类的泛型标记符不包含父类的泛型标记符
class Child_3<U, V, W> extends Father<String>{//指明父类泛型参数的数据类型
}
class Child_4<U, V, W>extends Father{//未指明父类泛型参数的数据类型,则默认父类泛型参数是Object
}
子类不是泛型类
class Father<T> {
}
class Child_5 extends Father<String>{//指明父类泛型参数的数据类型
}
class Child_6 extends Father{//未指明父类泛型参数的数据类型,则默认父类泛型参数是Object
}