java学习脚印: 泛型(Generics)认识之三
上接,《java学习脚印: 泛型(Generic)认识之二》。
1. 再谈类型变量限定
在《java学习脚印: 泛型(Generic)认识之一》 中我们初步认识了使用类型限定来限制传入类型变量的类型实参,实际上为了使类型限定变得更加合理,即可以限定传入的类型实参必须是某种类型的父类型或者子类型,java引入了通配符(wildCard)机制。首先通过实例认识一下通配符类型。
1.1 通配符类型
如果要打印一个数值类型的数组列表中的数值,
我们可以尝试三种方法,如下清单所示。
代码清单: 1-1 GenericDemo3.java
package com.learningjava;
import java.util.ArrayList;
/**
* print arrayList values
* @author wangdq
*/
public class GenericsDemo3 {
public static void main(String[] args) {
//printNumber(ArrayList<Number>) is not applicable for ArrayList<Integer>
ArrayList<Integer> listInteger = new ArrayList<>();
listInteger.add(Integer.valueOf(1));
listInteger.add(Integer.valueOf(2));
//printNumber(listInteger);//error
//ok
ArrayList<Long> listLong = new ArrayList<>();
listLong.add(Long.valueOf(100));
listLong.add(Long.valueOf(120));
print(listLong);
//ok
ArrayList<Double> listDouble = new ArrayList<>();
listDouble.add(Double.valueOf(2.732));
listDouble.add(Double.valueOf(3.14159));
printValues(listDouble);
}
//version1
public static void printNumber(ArrayList<Number> p) {
for(Number num: p ) {
System.out.println(num.byteValue());
}
}
//version2
public static <T extends Number> void print(ArrayList<T> p) {
for(Number num: p ) {
System.out.println(num.byteValue());
}
}
//version3
public static void printValues(ArrayList<? extends Number> p) {
for(Number num: p ) {
System.out.println(num.byteValue());
}
}
}
这里打印数值的方法version1,将不能正常工作,因为ArrayList<Integer>或者ArrayList<Double>均不是ArrayList<Number>的子类型(具体原因可参见2 泛型类间关系部分),方法无法正常调用。
version2版本,定义了类型变量<T extends Number>则能如期工作;
在Version3中我们引入了'?','?'即是通配符,<? extends Number>表示Number的所有子类型,该方法也能够如期调用。
version2中定义类型变量的方法虽然可以解决问题,但是无法解决像限定类型为某个类的超类的问题,java引入通配符来解决很多类型限定问题。下面详细讲述通配符的限定方式。
1.2 上界限定通配符(Upper Bounded Wildcards)
所谓上界限定,就是类型变量的超类型已经限定了,那么定义的类型必须是超类型的子类型。例如<? extends Number>,这里表示类型变量只能是Number的子类型,如Integer,Long等。
上界限定使用extends关键字,如我们要打印ArrayList<Integer>,ArrayList<Long>
等,那么可以从这些数值类型中找到它们共同的超类型Number,因此可以使用:
public void printValues(ArrayList<? extends Number> p)来完成打印数值功能。
例如一个备份数组列表对象到文件的方法,可以声明如下:
public void backupToFile(ArrayList<? extends Serializable> p);表面必须是实现了Serializable的子类才可以调用该方法序列化数组列表。
1.3 下界限定通配符(Lower Bounded Wildcards)
所谓下界限定,就是类型变量必须是限定的类型的超类型,使用super关键字来指示下界限定。例如<? super Integer>表示类型变量必须是Integer的超类型,如Number,Object。
定义一个添加整数的方法如下:
public static void addNumbers(ArrayList<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
则该方法只能往ArrayList<Integer>,ArrayList<Number>,ArrayList<Object>数组列表中加入元素。例如,调用方法addNumbers(new ArrayList<Long>());将报错:
The method addNumbers(ArrayList<? super Integer>) in the type GenericsDemo10 is not applicable for the arguments (ArrayList<Long>)
1.4 无界限定通配符(Unbounded Wildcards)
无限定通配符就是以<?>形式的使用通配符,这种形式主要用在两种情形:
1)编写一个只依赖Object类的方法就可以实现的方法时。
例如使用Object类的toString方法时。
2)在泛型类执行与类型变量无关的操作时,例如求取数组列表大小。
例如如下方法,使用无限定通配符来打印数组列表的值:
public static void printList(ArrayList<?> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
小结:
可以以无界限定、上界限定和下界限定三种形式使用通配符;
上界限定即是限定类型变量必须是指定类型的子类型,下界限定即是限定类型变量必须是指定类型的超类型,无界限定一般使用在与类型变量无关的方法中。
2.泛型类间关系关键点
引入泛型以后,类间关系变得复杂,特别是引入了通配符之后,像诸如<T extends Comparable<? super T>>代表什么类型变量,像List<?>与ArrayList<Number>间是什么关系,这类问题初次理解起来比较费劲。
关于泛型类间关系的详细展开,可以查看 http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html 网站。
到底这些问题应用价值有多大,目前还不了解,这里不打算深入去学习和分析,只给出几条简单的规则,细节问题留待以后查询。
1)非泛型类,依然保持熟知的类间关系不变。
例如Integer是Number的子类,可以定义的Manager是Employee的子类。
2) 加入原本具有类间关系的类型实参后,泛型的实例类间关系不再保持。
例如ArrayList<Integer>不再是ArrayList<Number>的子类,这个从清单1-1的实例可以看出。1)和2)所述关系可以参见下图:
3)引入通配符的泛型关系
引入通配符后,泛型类间关系变得复杂了,可能需要一段时间才能很好的理解。
这里简化为以下几个原则:
原则一: 只要类型实参不变,泛型类的实例间超类和子类关系不变。
An instantiation of a generictype is the supertype of all instantiations of generic subtypes that havethe same type argument.
例如有如下类间关系:
public class ArrayList<E> extends AbstractList<E> implements List<E>
public interface List<E> extends Collection<E>
interface PayloadList<E,P> extends List<E>
则只要保持类型实参不变,就有如下图所示关系:
原则二: 原型是其所对应的泛型的实例的超类型。
The raw type is the supertype of allinstantiations of the corresponding generic type.
原则三: 未限定的通配符实例是该泛型的所有实例的超类型,并且也是所有具有未限定通配符的子类泛型实例的超类型。
An unbounded wildcard instantiation isthe supertype of all instantiations of the generic type.At the same, an unbounded wildcard instantiation is supertype of allunbounded instantiations of any generic subtypes.
原则四:具有上界限定的通配符泛型实例,是那些类型实参为上界子类型的所有实例的超类型。
A wildcard instantiation wild an upperbound is supertype of all instantiations of the same generic type where the type argument is a subtype of the upper bound.
原则五: 具有下界限定的通配符泛型实例,是那些类型实参为上界的超类型的所有实例的超类型。
A wildcard instantiation wild a lowerbound is supertype of all instantiations of the generic type where the type argument is a supertype of the lower bound.
上述几条原则,可以体现在下图所示的关系之中:
5.泛型使用的约束
1)不能用基本数据类型代替类型参数,例如ArrayList<E>,这里不能用int,而要用Integer来作为类型实参传入。
2)运行时类型查询只适用于原始类型
例如:
ArrayList<String> strList = new ArrayList<>();
ArrayList<Integer> numList = new ArrayList<>();
if(strList instanceof ArrayList<?>) //strList is a instance of ArrayList
{
System.out.println("is");
}
if(strList.getClass() == numList.getClass()) //equal
{
System.out.println("equal");
}
3)不能利用泛型类抛出或者捕获泛型类异常
4)不能声明参数化类型的数组
例如: ArrayList<String>[] str = new ArrayList<String>[10];//不允许
5)泛型类的静态上下文中类型变量无效
不能在静态域或者方法中引用类型变量,也就是禁止带有类型变量的静态域和方法。
6)不要在类中重载类型变量擦出后签名相同的方法
例如:
/**
* error method declaring
* Method print(Set<String>) has the same erasure print(Set<E>) as another method in type Example
*/
class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
这个类中两个print方法的重载是无效的,类型擦除后这两个方法具有相同的签名。