@author LeslieTong
@data 2017-11-20
Stay hungry, stay foolish. ——Steve·Jobs
定义泛型方法
假设需要实现一个方法——该方法负责将一个Object数组的所有元素添加到一个Collection集合中。
考虑采用如下代码来实现该方法。
static void fromArrayToCollection(Object[] a, Collection<Object> c) {
for (Object o: a) {
c.add(o);
}
}
上面定义的方法没有任何问题,关键在于方法中的c形参,它的数据类型是Collection。
正如前面所介绍的,Collection< String >不是Collection< Object >的子类型——所以这个方法的功能非常有限,它只能将Object数组的元素复制到Object(Object的子类不行)Collection集合中,
即下面代码将引起编译错误。
String[] strArr = {"a", "b"};
List<String> strList = new ArrayList<>();
// Collection<String>对象不能当成Collection<Object>使用,下面代码出现编译错误
fromArrayToCollection(strArr, strList);
可见上面方法的参数类型不可以使用Collection< String >,那使用通配符Collection
修饰符 <T, S> 返回值类型 方法名(形参列表) {
// 方法体
}
于是可以把上面的fromArrayToCollection方法改为如下格式:
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o);
}
}
下面程序示范了完整的用法。
public class GenericMethodTest {
// 声明一个泛型方法,该泛型方法中带一个T类型形参
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o);
}
}
public static void main(String[] args) {
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<>();
// 下面代码T代表Object类型
fromArrayToCollection(oa, co);
String[] sa = new String[100];
Collection<String> cs = new ArrayList<>();
// 下面代码T代表String类型
fromArrayToCollection(sa, cs);
// 下面代码中T代表Object类型
fromArrayToCollection(sa, co);
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<>();
// 下面代码中T代表Number类型
fromArrayToCollection(ia, cn);
// 下面代码中T代表Number类型
fromArrayToCollection(fa, cn);
// 下面代码中T代表Number类型
fromArrayToCollection(na, cn);
// 下面代码中T代表Object类型
fromArrayToCollection(na, co);
// 下面代码中T代表String类型,但na是一个 Number数组
// 因为Number既不是String类型,也不是它的子类
// 所以出现编译错误
// fromArrayToCollection(na, cs);
}
}
为了让编译器能准确推断出泛型方法中类型形参的类型 ,不要制造迷惑。
一旦系统迷惑了,就是你错了! ——李刚
看如下程序
public class ErrorTest {
// 声明一个泛型方法,该泛型方法中带一个T类型参数
static <T> void test(Collection<T> from, Collection<T> to) {
for (T ele : from) {
to.add(ele);
}
}
public static void main(String[] args) {
List<Object> as = new ArrayList<>();
List<String> ao = new ArrayList<>();
// 下面代码将产生编译错误
//test(as, ao);
}
}
上面程序定义了test方法,该方法用于将前一个集合里的元素复制到下一个集合中,该方法中的两个形参from、to的类型都是Collection,这要求调用该方法时的两个集合实参中的泛型类型相同,否则编译器无法准确推断出泛型方法中类型形参的类型。
上面程序中调用test方法传入了两个实际参数,其中as的数据类型是List,而ao的数据类型是List,与泛型方法签名进行对比:test(Collection< T > a, Collection< T> c),编译器无法正确识别T所代表的实际类型。为了避免这种错误,可以将该方法改为如下形式 :
public class RightTest {
// 声明一个泛型方法,该泛型方法中带一个T类型参数
static <T> void test(Collection<? extends T> from, Collection<T> to) {
for (T ele : from) {
to.add(ele);
}
}
public static void main(String[] args) {
List<Object> ao = new ArrayList<>();
List<String> as = new ArrayList<>();
// 下面代码正常
test(as, ao);
}
}
上面代码改变了test方法签名,将该方法的前一个形参类型改为Collection< ? extends T>,这种采用类型通配符的表示方式,只要test方法的前一个Collection集合里的元素类型是后一个Collection集合里元素类型的子类即可。
泛型方法和类型通配符的区别
大多数时候都可以用泛型方法代替类型通配符。例如,对于Collection接口中两个方法定义:
public interface Collection<E> {
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
...
}
我们看到上面两个方法都使用了类型通配符的形式,下面我们用泛型方法的形式:
public interface Collection<E> {
boolean <T> containAll(Collection<T> c);
boolean <T> allAll(Collection<T> c);
...
}
上面方法使用了< T extends E >泛型形式,这时定义类型形参时设定上限。其中E是Collection接口里定义的类型形参,在该接口里E可当成普通类型使用。
设定通配符下限
Java允许设定通配符的下限:\< ? super Type>,这个通配符表示它必须是Type本身或者Type的父类。代码如下:
public class MyUtils {
// 下面des集合元素的类型必须与src集合元素的类型相同或者是其父类
public static <T> T copy (Collection<? super T> des, Collection<T> src) {
T last = null;
for (T ele : src) {
last = ele;
des.add(ele);
}
return last;
}
public static void main(String[] args) {
List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
li.add(5);
// 此处可准确知道最后一个被复制的元素是Integer类型
// 与src集合元素类型相同
Integer last = copy(ln, li);
System.out.println(ln);
}
}