天天看点

泛型通配符、定义泛型类型

一、泛型的通配符

1、

如果要定义一个方法,用于打印出任何参数化类型的集合中的所有数据,该如何定义呢?上一篇提到过了,参数化类型不考虑参数类型

间的继承关系。

因此如下定义就通不过编译:

public static void printCollection(Collection<Object> cols) {

    for(Object obj:cols) {

        System.out.println(obj);

    }

}

正确的方式应把Object替换为?通配符:

public static void printCollection(Collection<?> cols) {

    for(Object obj:cols) {

        System.out.println(obj);

    }

    //cols.add("abc");// 错误,因为?无法确定是否将来匹配的是String

    cols.size();// 没错,此方法与参数类型无关

    //cols = new HashSet<Date>();

}

因此,?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化

有关的方法(比如add)

泛型中的?通配符的扩展

1)限定通配符的上边界:

正确:Vector<? extends Number> x = new Vector<Integer>();

错误:Vector<? extends Number> x = new Vector<String>();

2)限定通配符的下边界:

正确:Vector<? super Integer> x = new Vector<Number>();

错误:Vector<? super Integer> x = new Vector<Byte>();

而且限定通配符总是包括自己。

2、泛型综合应用的体现

HashMap<String, Integer> maps=new HashMap<String, Integer>();

maps.put("sss", 24);

maps.put("ddd", 27);

maps.put("aaa", 25);

//Map类未实现Itretor接口,所以不能直接迭代,但可以用entrySet()方法获取里面所有元素的集合

Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();

//Map.Entry<K,V>是Map的一个嵌套类。它有getKey和getValue方法,相当于一个JavaBean。

//迭代取出Set集合中的键值对。

for(Map.Entry<String, Integer> entry:entrySet) {

    System.out.println(entry.getKey() + "," + entry.getValue());

}

另外,jsp页面中也经常要对Set或Map集合进行迭代:

<c:forEach items="${map}" var="entry">

  ${entry.key}:${entry.value}

</c:forEach>

3、自定义一个泛型方法

// 自定义一个类型的时候,要在返回值的前面加上<T>

// 定义一个泛型方法,可以调换某个类型数组的两个元素位置

//只有引用类型才可以作为泛型方法的实际参数,基本类型编译时不会通过

private static <T> void swap(T[] a, int i, int j) {

T tmp = a[i];

a[i] = a[j];

a[j] = tmp;

}

那么在调用方法时:

swap(new String[]{"abc","xxx","ddd"}, 1, 2);

swap(new int[]{1,2,3,4,5}, 2, 4);

上面两句第一句没错,第二句会报错。

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如Class.getAnnotation()方法的定义。并且

可以用&来指定多个边界,如<V extends Serializable&clonable> void method(){}

普通方法、构造方法和静态方法中都可以使用泛型。

也可以用类型变量表示异常,成为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。

在泛型中可以同时又多个类型参数,在定义他们的尖括号中用逗号分隔,例如:

public static <K,V> V getValue(K key) {return map.get(key);}

二、定义泛型类型

1、

如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型

的方式进行定义,也就是类级别的泛型,语法格式如下:

public Class GenericDao<T> {

    private T field1;

    public void save(T obj) {}

    public T getById(int id) {}

}

类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:

GenericDao<String> dao = null;

new GenericDao<String>();

注意:

在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。

当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员时被所

有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。

当类中只有一个方法需要使用泛型时,使用的是类级别的泛型。

2、

假如有这么一个方法:

public static void applyVector(Vector<Date> v) {

}

//那么如何通过一个泛型集合的变量,获取这个类型参数到底是什么类型呢?下面的方式显然不行。

Vector<Date> v = new Vector<Date>();

//下面的方法变量进行定义时,getMethod的第二个参数,即参数类型,应该就是Vector.class

//而不是Vector<Date>.class,因为在编译完了之后,集合变量已经去类型化了

//同时,如果同时定义两个applyVector方法,分别为:

//applyVector(Vector<Date> v)

//applyVector(Vector<Integer> v)

//编译器会报错,因为它会把这两个方法看做是同一个。(去类型化的原因)

Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);

Type[] types = applyMethod.getGenericParameterTypes();//获取该方法的参数类型数组

ParameterizedType pType = (ParameterizedType)types[0];//获取参数类型数组的第一个值

System.out.println(pType.getRawType());

System.out.println(pType.getActualTypeArguments()[0]);//知道参数只有一个,直接取第一位的值