注:以下内容基于Java 8,所有代码都已在Java 8环境下测试通过
目录:
- Java泛型1——概述
- Java泛型2——泛型类
- Java泛型3——泛型接口
- Java泛型4——泛型方法
- Java泛型5——泛型擦除
- Java泛型6——通配符
定义泛型方法
泛型方法的定义格式如下:
<泛型标记符1, 泛型标记符2……> 返回类型 方法名(泛型标记符1 变量名1, 泛型标记符2 变量名2……) {}
泛型标记符出现了两次
一次在<>中,一次在()中,其中:
- <>中指明在接下来的函数参数列表中可以使用的泛型标记符
- ()中是函数参数,可以出现的类型有:
- 8种基本数据类型
- <>中的泛型标记符
- 定义泛型类(如果这个方法在一个泛型类中)时的泛型标记符
比如下面代码:
class TestClass<T> {public <U, V> void f(int a, T b, U c) {}
}
泛型方法 f()
共有3个参数,包括:
-
1个基本数据类型a,在定义函数的时候就指明是int类型的了
-
1个泛型参数类型b,在实例化对象的时候才能确定T代表的数据类型(T是定义泛型类时使用的泛型参数)
-
1个泛型参数类型c,在调用函数的时候才能确定U代表的数据类型(U是定义泛型方法时使用的泛型参数)
上面泛型方法在定义的时候提供了两个泛型参数即U和V,但实际在参数列表中只使用了一个U,虽然这种写法很神经,但这种写法在语法上没问题。
注意泛型方法和使用泛型类中的类型参数的方法
class TestClass<T> {void f_1(T t) {//这只是使用了定义泛型类时的类型参数,不是泛型方法System.out.println("这不是泛型方法");}public <U> void f_2(U u) {//这才是泛型方法System.out.println("这才是泛型方法");}
}
- 定义泛型方法:函数签名的返回值前面加了
<>
- 使用泛型类中的类型参数:函数签名中仅有参数中使用了定义类时的类型参数
泛型方法的几种定义方式
class TestClass<T> {public void f_1(T t) {//这只是使用了定义泛型类时的类型参数,不是泛型方法System.out.println("这不是泛型方法");}public <U> void f_2(U u) {//这才是泛型方法System.out.println("这才是泛型方法");}public static <U> void f_3(U u) {//静态泛型方法System.out.println("静态泛型方法");}public <U, V> void f_4(U u, V v) {//同时使用多个类型参数System.out.println("使用多个类型参数");}public <U> void f_5(U u, T t) {//使用了定义泛型类时的类型参数System.out.println("使用了定义泛型类时的类型参数");}
}
泛型方法重载
对泛型方法重载时,只可以通过参数数目进行重载,如:
class TestClass {public <T> void f_1(T t) {}public <U> void f_1(U u) {//类型参数数目相同,编译报错}public <T> void f_1(T t1, T t2) {//类型参数数目不同,方法重载}
}
public <T> void f_1(T t)
和 public <U> void f_1(U u)
仅仅是使用的类型参数名字不同(一个是T一个是U),无法区分两个方法。
泛型方法和可变长参数
class TestClass {public <T> void f_1(T... args) {for (T t : args) {System.out.println(t);}}
}
调用泛型方法
显式调用泛型方法的格式如下:
对象名(或类名,如果是静态方法).<数据类型1, 数据类型2……>方法名(参数1, 参数2……)
同时,泛型方法也支持非显式调用。实例化泛型类或者实现泛型接口时需要指定类型参数(否则自动设置为Object),但在调用泛型方法时,通常不需要指定参数类型,编译器会根据传入参数的数据类型自动判断出类型参数所代表的具体数据类型,这被称为类型参数推断。对于基本类型,编译器还可以根据变量类型进行自动装箱(将基本类型自动转换成它们对应的包装类,将包装类自动转换成它们对应的基本类型称为自动拆箱)。
一个类型参数
public class Main {private static <T> void f(T t) {System.out.println(t);}public static void main(String[] args) {Main.f(1);//自动装箱成 IntegerMain.<Integer>f(1);//与上面等价Main.f('a');//自动装箱成 CharacterMain.<Character>f('a');Main.f("abc");//字符串是一个String类型的对象,不是基本类型,不进行自动装箱Main.<String>f("abc");}
}
多个类型参数
-
显式调用的时候根据指定的参数类型设置
-
非显式调用的时候会进行类型推断,若参数的类型相同则设置为相同的类型,否则将类型参数设置为所有传入参数最小的共同父类,比如:
- 1 和 2 取 Integer
- 1.2 和 3 取 Number
- 1 和 “abc” 取 Object
public class Main {private static <T, U> void f(T t, U u) {System.out.println(t);}public static void main(String[] args) {Main.f(1, 2);//自动装箱为 Integer 和 IntegerMain.<Integer, Integer>f(1, 2);//与上面等价,参数类型相同取相同的Main.f(1.2, 3);//自动装箱为 Double 和 IntegerMain.<Number, Number>f(1.2, 3);//与上面等价,设置为 Double 和 Integer 的最小共同父类 NumberMain.f(1, "abc");//1自动装箱为 IntegerMain.<Object, Object>f(1, "abc");//与上面等价,设置为 Integer 和 String 的最小共同父类 Object} }