1.定义泛型方法:
在定义类、接口时可以使用类型形参,在该类的方法定义和Field定义、接口的方法定义中,这些类型形参可被当成普通类型来用。在另外一些情况下,我们定义类、接口时没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,Java5提供了对泛型方法的支持。
假设需要实现这样一个方法,该方法负责将一个Object数组的所有元素添加到一个Collection集合中:
static void fromArrayToCollection(Object[] a, Collection<Object> c){
for(Object o : a){
c.add(o);
}
}
因为Collection<String>不是Collection<Object>的子类型,所以这个方法的功能非常有限,它只能将Object数组的元素复制到Object的Collection集合中。那么使用通配符Collection<?>是否可行呢?显然也不行,我们不能把对象放进一个未知类型的集合中。
为了解决这个问题,可以使用Java5提供的泛型方法。所谓泛型方法,就是在声明方法时定义一个或多个类型形参。泛型方法的用法格式如下:
修饰符 <T,S> 返回值类型 方法名(形参列表){
//方法体
}
采用支持泛型的方法,就可以将上面的fromArrayToCollection方法改为如下形式:
static <T> void fromArrayToCollection(T[] a, Collection<T> c){
for(T o : a){
c.add(o);
}
}
类似的有:
static <T> void test(Collection<? extends T> from, Collection<T> to){
for(T ele : from){
to.add(ele);
}
}
比较上面两个方法,我们提出一个问题:泛型方法和类型通配符都可以实现类似功能,那么两者的区别何在?
2.泛型方法和类型通配符的区别:
大多数时候都可以使用泛型方法来代替类型通配符。例如对于Java的Collection接口中的两个方法定义:
public interface Collection<E>{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
}
上面接口中两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式:
public interface Collection<E>{
<T> boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
}
泛型方法的形式中,接口的两个方法中类型形参T只使用了一次,类型形参T产生的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的。
泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。
如果某个方法中一个形参a的类型或返回值的类型依赖于另一个形参b的类型,则形参b的类型声明不应该使用通配符---因为形参a或返回值的类型依赖于该形参b的类型,如果形参b的类型无法确定,程序就无法定义形参a的类型。在这种情况下,只能考虑使用在方法签名中声明类型形参---也就是泛型方法。
如果有需要,我们可以同时使用泛型方法和通配符,如Java的Collections.copy()方法:
public class Collections{
public static <T> void copy(List<? super T> dest, List<? extends T> src){...}
}
JDK在定义src形参类型时使用的是类型通配符,而不是泛型方法。这是因为:该方法无须向src集合中添加元素,也无须修改src集合里的元素,所以可以使用类型通配符,无须使用泛型方法。
当然也可以将上面的方法签名改为使用泛型方法,不使用类型通配符:
public class Collections{
public static <T,S extends T> void copy(List<T> dest, List<S> src){...}
}
注意上面的类型形参S,它仅使用了一次,没有其他参数的类型、方法返回值的类型依赖于它,那类型形参S就没有存在的必要,即可以用通配符来代替S。
使用通配符比使用泛型方法更加清晰和准确,因此Java设计该方法时采用了通配符,而不是泛型方法。
3.设定通配符下限: