泛型(2) - 泛型方法 , 泛型通配符 泛型方法
当在一个方法签名中的返回值前面声明了一个 < T > 时,该方法就被声明为一个泛型方法
。< T >表明该方法声明了一个类型参数 T,并且这个类型参数 T 只能在该方法中使用。当然,泛型方法中也可以使用泛型类中定义的泛型参数
。
public <类型参数> 返回类型 方法名(类型参数 变量名) { ... }
只有在方法签名中声明了< T >的方法才是泛型方法,仅使用了泛型类定义的类型参数的方法并不是泛型方法。
public class Test <U> { public void testMethod (U u) { System.out.println(u); } public <T> T testMethod1 (T t) { return t; } }
泛型方法中可以同时声明多个类型参数。
public class TestMethod <U> { public <T, S> T testMethod (T t, S s) { return null ; } }
泛型方法中也可以使用泛型类中定义的泛型参数。
public class TestMethod <U> { public <T> U testMethod (T t, U u) { return u; } }
泛型类中定义的类型参数和泛型方法中定义的类型参数是相互独立的,它们一点关系都没有。
public class Test <T> { public void testMethod (T t) { System.out.println(t); } public <T> T testMethod1 (T t) { return t; } }
上面代码中,Test< T >
是泛型类,testMethod()
是泛型类中的普通方法,其使用的类型参数是与泛型类中定义的类型参数。而 testMethod1()
是一个泛型方法,他使用的类型参数是与方法签名中声明的类型参数。虽然泛型类中定义的类型参数标识和泛型方法中定义的类型参数标识都为< T >
,但它们彼此之间是相互独立的。也就是说,泛型方法始终以自己声明的类型参数为准。
注意事项: 1. < T >表明该方法声明了一个类型参数 T,并且这个类型参数 T 只能在该方法中使用。2. 为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。3. 与泛型类的类型参数定义一样,此处泛型方法中的 T 可以写为`任意标识`,常见的如 T、E、K、V 等形式的参数常用于表示泛型。
补充一点:将静态方法声明为泛型方法
前面在泛型类的定义中提到,在静态成员中不能使用泛型类定义的类型参数,但我们可以将静态成员方法定义为一个泛型方法。
public class Test2 <T> { public static <E> E show (E one) { return null ; } }
泛型方法的使用 泛型类,在创建类的对象的时候确定类型参数的具体类型; 泛型方法,在调用方法的时候再确定类型参数的具体类型。
泛型方法签名中声明的类型参数只能在该方法里使用,而泛型接口、泛型类中声明的类型参数则可以在整个接口、类中使用。 当调用泛型方法时,根据外部传入的实际对象的数据类型,编译器
就可以判断出类型参数 T
所代表的具体数据类型。
public class Demo { public static void main (String args[]) { GenericMethod d = new GenericMethod (); String str = d.fun("汤姆" ); int i = d.fun(30 ); System.out.println(str); System.out.println(i); GenericMethod.show("Lin" ); } } class GenericMethod { public <T> T fun (T t) { return t; } public static <E> void show (E one) { System.out.println("静态泛型方法 " + one); } }
泛型方法中的类型推断
在调用泛型方法的时候,可以显式地指定类型参数,也可以不指定。
当泛型方法的形参列表中有多个类型参数时,在不指定类型参数的情况下,方法中声明的的类型参数为泛型方法中的几种类型参数的共同父类的最小级,直到 Object。
在指定了类型参数的时候,传入泛型方法中的实参的数据类型必须为指定数据类型或者其子类。
public class Test { public static <T> T add (T x, T y) { return y; } public static void main (String[] args) { int i = Test.add(1 , 2 ); Number f = Test.add(1 , 1.2 ); Object o = Test.add(1 , "asd" ); int a = Test.<Integer>add(1 , 2 ); int b = Test.<Integer>add(1 , 2.2 ); Number c = Test.<Number>add(1 , 2.2 ); } }
泛型通配符
在现实编码中,确实有这样的需求,希望泛型能够处理某一类型范围内
的类型参数,比如某个泛型类和它的子类,为此 Java 引入了泛型通配符
这个概念。
泛型通配符有 3 种形式:
<?> :被称作无限定的通配符。
<? extends T> :被称作有上界的通配符。
<? super T> :被称作有下界的通配符。
1. 上界通配符 <? extends T>
上界通配符 <? extends T>
:T 代表了类型参数的上界,<? extends T>
表示类型参数的范围是 T 和 T 的子类。需要注意的是: <? extends T>
也是一个数据类型实参,它和 Number、String、Integer
一样都是一种实际的数据类型。
在泛型的继承中我们说到,ArrayList< Integer >
和 ArrayList< Number >
之间不存在继承关系。而引入上界通配符的概念后,我们便可以在逻辑上将 ArrayList<? extends Number>
看做是 ArrayList< Integer >
的父类,但实质上它们之间没有继承关系。
public class GenericType { public static void main (String[] args) { ArrayList<Number> list01 = new ArrayList <Integer>(); ArrayList<? extends Number > list02 = new ArrayList <Integer>(); } }
ArrayList<? extends Number>
可以代表 ArrayList< Integer >
、ArrayList< Float >
、… 、ArrayList< Number >
中的某一个集合,但我们不能指定 ArrayList<? extends Number>
的数据类型。
public class GenericType { public static void main (String[] args) { ArrayList<? extends Number > list = new ArrayList <>(); list.add(new Integer (1 )); list.add(new Float (1.0 )); } }
可以这样理解,ArrayList<? extends Number>
集合表示了:我这个集合可能是 ArrayList< Integer >
集合,也可能是 ArrayList< Float >
集合,… ,还可能是 ArrayList< Number >
集合;但到底是哪一个集合,不能确定;程序员也不能指定。
所以,在上面代码中,创建了一个 ArrayList<? extends Number>
集合 list
,但我们并不能往 list
中添加 Integer
、Float
等对象,这也说明了 list
集合并不是某个确定了数据类型的集合。
完整的用法展示
下面展示一段代码,设置一个方法(泛型方法),可以接受Number
类的子类集合,接收后将该集合中的元素转化为Int
型数据循环输出
package chapter06;import java.util.ArrayList;public class Fanxing_01 { public static void main (String[] args) { ArrayList<Integer> integerList = new ArrayList <>(); integerList.add(1 ); integerList.add(2 ); printIntVal(integerList); ArrayList<Float> floatList = new ArrayList <>(); floatList.add((float ) 1.0 ); floatList.add((float ) 2.0 ); printIntVal(floatList); } public static void printIntVal (ArrayList<? extends Number> list) { for (Number number : list) { System.out.print(number.intValue() + " " ); } System.out.println(); } }
错误用法: public class Test { public static void main (String[] args) { ArrayList<? extends Number > list = new ArrayList (); list.add(null ); list.add(new Integer (1 )); list.add(new Float (1.0 )); } public static void fillNumList (ArrayList<? extends Number> list) { list.add(new Integer (0 )); list.add(new Float (1.0 )); list.set(0 , new Integer (2 )); list.set(0 , null ); } }
一句话总结:使用 extends 通配符表示可以读,不能写
。
下界通配符 <? super T>(与上面的恰恰相反
)
下界通配符 <? super T>
:T 代表了类型参数的下界,<? super T>
表示类型参数的范围是 T 和 T 的超类,直至 Object
。需要注意的是:<? super T>
也是一个数据类型实参,它和 Number
、String
、Integer
一样都是一种实际的数据类型。
ArrayList<? super Integer>
在逻辑上表示为 Integer
类以及 Integer
类的所有父类,它可以代表 ArrayList< Integer>
、ArrayList< Number >
、 ArrayList< Object >
中的某一个集合,但实质上它们之间没有继承关系。
public class GenericType { public static void main (String[] args) { ArrayList<Integer> list01 = new ArrayList <Number>(); ArrayList<? super Integer> list02 = new ArrayList <Number>(); } }
与上界通配符一相反下界通配符同样能用于赋值操作,用于指定某一个集合在接收某类型参数的范围
public class GenericType { public static void main (String[] args) { ArrayList<? super Number> list = new ArrayList <>(); list.add(new Integer (1 )); list.add(new Float (1.0 )); list.add(new Object ()); } }
这里奇怪的地方出现了,为什么和ArrayList<? extends Number>
集合不同, ArrayList<? super Number>
集合中可以添加 Number
类及其子类的对象呢?
其原因是, ArrayList<? super Number>
的下界是 ArrayList< Number >
。因此,我们可以确定 Number
类及其子类的对象自然可以加入 ArrayList<? super Number>
集合中; 而 Number
类的父类对象就不能加入 ArrayList<? super Number>
集合中了,因为不能确定 ArrayList<? super Number>
集合的数据类型。
完整例子展示: package chapter06;import java.util.ArrayList;public class Fanxing_01 { public static void main (String[] args) { ArrayList<Number> list = new ArrayList (); list.add(new Integer (1 )); list.add(new Float (1.1 )); fillNumList(list); System.out.println(list); } public static void fillNumList (ArrayList<? super Number> list) { list.add(new Integer (0 )); list.add(new Float (1.0 )); } }
错误例子展示: public class Test { public static void main (String[] args) { ArrayList<Integer> list = new ArrayList <>(); list.add(new Integer (1 )); fillNumList(list); } public static void fillNumList (ArrayList<? super Number> list) { list.add(new Integer (0 )); list.add(new Float (1.0 )); for (Number number : list) { System.out.print(number.intValue() + " " ); System.out.println(); } for (Object obj : list) { System.out.println(obj);使用 } } }
注意,ArrayList<? super Number>
代表了 ArrayList< Number >
、 ArrayList< Object >
中的某一个集合,而 ArrayList< Integer >
并不属于 ArrayList<? super Number>
限定的范围,因此,不能往 fillNumList()
方法中传入 ArrayList< Integer >
集合。
并且,不能将传入集合的元素赋值给 Number
对象,因为传入的可能是 ArrayList< Object >
集合,向下转型可能会产生ClassCastException
异常。
不过,可以将传入集合的元素赋值给 Object
对象,因为 Object
是所有类的父类,不会产生ClassCastException
异常,但这样的话便只能调用 Object
类的方法了,不建议这样使用。
一句话总结:使用 super 通配符表示可以写,不能读。
无限定通配符 <?>
我们已经讨论了<? extends T>
和<? super T>
作为方法参数的作用。实际上,Java 的泛型还允许使用无限定通配符<?>,即只定义一个?
符号
无界通配符>:? 代表了任何一种数据类型,能代表任何一种数据类型的只有 null。需要注意的是: > 也是一个数据类型实参,它和 Number、String、Integer 一样都是一种实际的数据类型。
注意:Object 本身也算是一种数据类型,但却不能代表任何一种数据类型,所以 ArrayList< Object > 和 ArrayList> 的含义是不同的,前者类型是 Object,也就是继承树的最高父类,而后者的类型完全是未知的;ArrayList> 是 ArrayList< Object > 逻辑上的父类。
ArrayList<?>
在逻辑上表示为所有数据类型的父类,它可以代表 ArrayList< Integer>
、ArrayList< Number >
、ArrayList< Object >
中的某一个集合,但实质上它们之间没有继承关系。
public class GenericType { public static void main (String[] args) { ArrayList<Integer> list01 = new ArrayList <>(123 , 456 ); ArrayList<?> list02 = list01; } }
ArrayList> 既没有上界也没有下界,因此,它可以代表所有数据类型的某一个集合,但我们不能指定 ArrayList> 的数据类型。
public class GenericType { public static void main (String[] args) { ArrayList<?> list = new ArrayList <>(); list.add(null ); Object obj = list.get(0 ); list.add(new Integer (1 )); Integer num = list.get(0 ); } }
大多数情况下,可以用类型参数 < T > 代替 <?> 通配符。
static <?> void isNull (ArrayList<?> list) { ... } static <T> void isNull (ArrayList<T> list) { ... }