泛型方法

来源:互联网 发布:电影人工智能结局 编辑:程序博客网 时间:2024/05/16 02:02

@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);    }}