泛型类
Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义泛型形参时设定上限,用于表示传给该泛型形参的实际类型要么是该上限类型,要么是该上限类型的子类。
上面程序定义了一个Apple泛型类,该Apple类的泛型形参的上限是Number类,这表明使用Apple类时为T形参传入的实际类型参数只能是Number或Number类的子类。上面程序在①处将引起编译错误:类型T的上限是Number类型,而此处传入的实际类型是String类型,既不是Number类型,也不是Number类型的子类型,所以将会导致编译错误。
在一种更极端的情况下:
程序需要为泛型形参设定多个上限(至多有一个父类上限,可以有多个接口上限),表明该泛型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。
与类同时继承父类、实现接口类似的是,为泛型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为泛型形参指定类上限,类上限必须位于第一位。
泛型方法
在定义类、接口时可以使用泛型形参,在该类的方法定义和成员变量定义、接口的方法定义中,这些泛型形参可被当成普通类型来用。在另外一些情况下,定义类、接口时没有使用泛型形参,但定义方法时想自己定义泛型形参,这也是可以的,Java 5还提供了对泛型方法的支持。
自定义定义泛型方法
假设需要实现这样一个方法:该方法负责将一个Object数组的所有元素添加到一个Collection集合中。考虑采用如下代码来实现该方法
上面定义的方法没有任何问题,关键在于方法中的c形参,它的数据类型是 Collection 。 其中Collection不是Collection的子类型—所以这个方法的功能非常有限,它只能将Object[]数组的元素复制到元素为Object(Object的子类不行)的Collection集合中,即下面代码将引起编译错误。
可见上面方法的参数类型不可以使用Collection,那使用通配符Collection<?>是否可行呢?显然也不行,因为Java不允许把对象放进一个未知类型的集合中。
为了解决这个问题,可以使用Java 5提供的泛型方法(Generic Method)。所谓泛型方法,就是在声明方法时定义一个或多个泛型形参。泛型方法的语法格式如下:
把上面方法的格式和普通方法的格式进行对比,不难发现泛型方法的方法签名比普通方法的方法签名多了泛型形参声明,泛型形参声明以尖括号括起来,多个泛型形参之间以逗号(,)隔开,所有的泛型形参声明放在方法修饰符和方法返回值类型之间。
采用支持泛型的方法,就可以将上面的fromArrayToCollection方法改为如下形式:
完整例子
上面程序定义了一个泛型方法,该泛型方法中定义了一个T泛型形参,这个T类型就可以在该方法内当成普通类型使用。与接口、类声明中定义的泛型不同的是,方法声明中定义的泛型只能在该方法里使用,而接口、类声明中定义的泛型则可以在整个接口、类中使用。
与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数,如上当调用fromArrayToCollection()方法时,无须在调用该方法前传入String、Object等类型,但系统依然可以知道为泛型实际传入的类型,因为编译器根据实参推断出泛型所代表的类型,它通常推断出最直接的类型。例如,下面调用代码:
上面代码中cs是一个Collection类型,与方法定义时的fromArrayToCollection(T[] a,Collectionc)进行比较—只比较泛型参数,不难发现该T类型代表的实际类型是String类型。对于如下调用代码:
上面的cn是Collection类型,与此方法的方法签名进行比较—只比较泛型参数,不难发现该T类型代表了Number类型。
反面教材
上面程序中定义了test()方法,该方法用于将前一个集合里的元素复制到下一个集合中,该方法中的两个形参from、to的类型都是Collection,这要求调用该方法时的两个集合实参中的泛型类型相同,否则编译器无法准确地推断出泛型方法中泛型形参的类型。
上面程序中调用test方法传入了两个实际参数,其中as的数据类 型是List,而ao的数据类型是List,与泛型方法签名进行对比:test(Collection a, Collection c),编译器无法正确识别T所代表的实际类型。怎么优化呢?
上面代码改变了test()方法签名,将该方法的前一个形参类型改为Collection<? extends T>,这种采用类型通配符的表示方式,只要test() 方法的前一个Collection集合里的元素类型是后一个Collection集合里元素类型的子类即可。