类型通配符
类型通配符一般是使用?代替具体的类型实参(此处是类型实参,而不是类型形参)。当操作类型时不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用 ? 通配符来表未知类型。例如 List<?> 在逻辑上是List<String>、List<Integer> 等所有List<具体类型实参>的父类。
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("zwj");
age.add(18);
number.add(120);
getNumberData(age); // 报错
getNumberData(number); // 120
getData(name); // zwj
getData(age); // 18
getData(number); // 120
//getUperNumber(name); // 出现错误,方法中的参数已经限定了参数泛型上限为Number
getUperNumber(age); //18
getUperNumber(number); //120
}
/**
* 在使用List<Number>作为形参的方法中,不能使用List<Ingeter>的实例传入,
* 也就是说不能把List<Integer>看作为List<Number>的子类;
*/
public static void getNumberData(List<Number> data) {
System.out.println("data :" + data.get(0));
}
/**
* 使用类型通配符可以表示同时是List<Integer>和List<Number>的引用类型。
* 类型通配符一般是使用?代替具体的类型实参,注意此处是类型实参;
* 和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。
*/
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
/**
* 类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
*/
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
}
有界的类型参数
有的时候我们会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这时猴就需要为泛型添加上边界,即传入的类型实参必须是指定类型的子类型。
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends或super关键字,最后紧跟它的上界或下界。由此可以知道泛型的上下边界的添加必须与泛型的声明在一起 。
<? extends T> 表示该通配符所代表的类型是T类型的子类。 例如往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。
<? super T> 表示该通配符所代表的类型是T类型的父类。例如当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
public class GenericMethodTest {
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T getMaxNuum(T x, T y, T z) {
T max = x; // 假设x是初始最大值
if ( y.compareTo( max ) > 0 ){
max = y; //y 更大
}
if ( z.compareTo( max ) > 0 ){
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main( String args[] ) {
System.out.println( "结果 " + getMaxNuum(3, 4, 5) ); // 结果 5
System.out.println( "结果 " + getMaxNuum(1.2, 6.6, 10.10) ); // 结果 10.10
}
}
我们也可以把之前的泛型类的定义改一下:
public class GenericClassDemo<T extends Number> {
private T t;
public GenericClassDemo() {
}
public GenericClassDemo(T t) {
this.t = t;
}
public void setT(T t) {
this.t = t;
}
public T getT(){
return t;
}
}
此时在实例化GenericClassDemo泛型类时参数类型只能是Number和Number的子类。在此基础上我们再来看一个泛型方法的例子:
/**
* 在泛型方法中添加上下边界限制的时候, 必须在泛型声明的时候添加;
* 也就是在权限修饰符与返回值之间的<T>上添加上下边界
*/
public <T extends Number> T getT(GeneriClassDemo<T> demo){
T t = demo.getT();
return t;
}
泛型数组
在java中是不能创建一个确切的泛型类型的数组的。
List<String>[] lsa = new ArrayList<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK