型·7第21章泛8System.out.print("The max number is"+max(intStack));9101/*#Findthemaximum in a stack of numbers*/12pub1icstatic doublemax(GenericStack<Number>stack)13doublemaxstack.popO.doubleValueO;//Initializemax1415while(Istack,isEmptyO)(16doublevalue-stack.popO.doubleValueO;17if(value>max)18maxvalue;1912021returnmax;22)23)程序清单21.6中的程序在第8行会出现编译错误,因为intstack不是Genericstack<number>的实例。所以,不能调用max(intstack)。尽管Integer是Number的子类型,但是,Genericstack<Integer>并不是Genericstack<Number>的子类型。为了避免这个问题,可以使用通配泛型类型。通配泛型类型有三种?、?extendsT或者?superT,其中T是某个泛型类型。形式一第一种形式?称为非受限通配(unboundedwildcard),它和?extendsObject是一样的。第二种形式?extendsT称为受限通配(boundedwildcard),表示T或T的一个未知子类型。第三种形式?superT称为下限通配(lower-boundwildcard),表示T或T的一个未知父类型。使用下面的语句替换程序清单21.6中的第12行就可以修复上面的错误:public static double max(GenericStack<?extends Number> stack)(<?extendsNumber>是一个表示Number或Number的子类型的通配类型。因此,调用max(newGenericstack<Integer>())或max(newGenericstack<Double>())都是合法的。程序清单21.7给出一个例子,它在print方法中使用?通配符,打印栈中的对象以及清空栈。<?>是一个通配符,表示任何一种对象类型。它等价于<?Extendsobject>。如果用Genericstack<object>替换Genericstack<?>,会发生什么情况呢?如果调用print(intstack)则会出错,因为intstack不是GenericStack<object>的实例。请注意,尽管Integer是object的一个子类型,但是Genericstack<Integer>并不是Genericstack<object>的子类型。程序清单21.7wi1dcardDemo2.java1pub1icclasswi1dCardDemo2(2publicstaticvoidmain(Stringargs)(3GenericStack<Integer>intStack-new GenericStack<Integer>O;4intStack.push(1):// 1is autoboxed into newInteger(1)5intStack.push(2):67intStack.push(-2):8print(intStack);61文10瓷11/*Prints objects and emptiesthe stack*/12public staticvoid print(GenericStack<?> stack)(13while(!stack.isEmptyO)(14System.out.print(stack.popO+"");15PDGJ163173什么时候需要<?superT>通配符?请看程序清单21.8中的例子。该例创建了一个字符串栈stack1(第3行)和一个对象栈stack2(第4行),然后调用add(stack1,stack2)(第8行)将stack1
型8·第21章泛中的字符串添加到stack2中。在第13行使用Genericstack<?superT>来声明栈stack2。如果用<T>代替<?superT>,那么在第8行的add(stack1,stack2)上就会产生一个编译错误,因为stack1的类型为Genericstack<string>,面stack2的类型为GenericStack<object>。<?superT>表示类型T或T的父类型。Object是String的父类型。程序清单21.8wildCardDemo3.java1pub1icclass wi1dCardDemo3(2publicstatic voidmain(String[)args)[3GenericStack<String>,stackl-newGenericStack<String>O;4GenericStack<Object>stack2=newGenericStack<Object>O:5stack2.push("Java");6stack2.push(2):7stackl.push("Sun"):8add(stackl,stack2);9wi1dCardDemo2.print(stack2);1011112public static<T> voidadd(GenericStack<T> stackl,13GenericStack<?super T>stack2){14while(1stack1.isEmptyO)15stack2.push(stackl.popO);15116)泛型类型和通配类型之间的继承关系在图21-6中进行了总结。在该图中,A和B表示类或者接口,面E是泛型类型参数。ObjectObjectA<?>superEE's superclassA<?extends B>A<?superB>AcB's subclass>A<B>A<B'ssubclass>?extends EE's subclass图21-6泛型类型和通配类型之间的关系21.7消除泛型和对泛型的限制泛型是使用一种称为类型消除(typeerasure)的方法来实现的。编译器使用泛型类型信息来编译代码,但是随后会消除它。因此,泛型信息在运行时是不可用的。这种方法可以使泛型代码向后兼容使用原始类型的遗留代码。泛型存在于编译时。一旦编译器确认泛型类型是安全使用的,就会将它转换为原始类型。例如,编译器会检查在图a中的代码里泛型是否被正确使用,然后将它翻译成如图b所示的在运行时使用的等价代码。图b中的代码使用的是原始类型。ArrayListlistnewArrayListO:ArrayList<String>list-newArrayList<String>O:PDG1ist.add("oklahoma");1ist.add("Oklahoma");String state-(String)(list.get(0)):String state -list.get(o):a)b)当编译泛型类,接口和方法时,编译器用Object类型代替泛型类型。例如,编译器会将图a中的方法
型·9第21章泛转换为图b中的方法。public static<E> voidprint(E[) 1ist)public static voidprint(Object[1ist)for (int i-O;i<1ist.length;i++)for (int i=O;i <list.length;i++)System.out.print(list[i]+"");System.out.print(list[i]+"");System.out.printlnO:System.out.printlnO:b)a)如果一个泛型类型是不受限的,那么编译器就会用一个受限类型来替换它。例如,编译器会将图a中的方法转换为图b中的方法。public static<E extends Geometricobject>pub1ic staticboolean equalArea(boolean equalArea(Eobjectl,GeometricObjectobjectlEobject2)Geometricobject object2)return objectl.getAreaOreturn objectl.getAreaOobject2.getAreaO:object2.getArea:1a)b)非常需要注意的是,不管实际的具体类型是什么,泛型类是被它的所有实例所共享的。假定按如下方式创建list1和list2ArrayList<String>1ist1=newArrayList<String>O;ArrayList<Integer>1ist2newArrayList<Integer>O;尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有个ArrayList类会被加载到JVM中,list1和list2都是ArrayList的实例,因此,下面两条语句的执行结果都为true:System.out.println(list1instanceofArrayList):System.out.println(1ist2 instanceof ArrayList):但是表达式list1instanceofArrayList<String>是错误的。由于ArrayList<string>并没有在JVM中存储为单独一个类,所以,在运行时使用它是毫无意义的。由于泛型类型在运行时被消除,因此,对于如何使用泛型类型是有一些限制的。下面是其中的一些限制:限制1:不能使用newE()不能使用泛型类型参数创建实例。例如,下面的语句是错误的:Eobject-new EO:出错的原因是运行时执行的是newE(),但是运行时泛型类型E是不可用的。限制2:不能使用newE[]S不能使用泛型类型参数创建数组。例如,下面的语句是错误的:E[] elements-new E[capacity]:可以通过创建一个object类型的数组,然后将它的类型转换为E[]来规避这个限制,如下所示:E[]elements=(E)newObject[capacity];但是,类型转换到(E1)会导致一个免检的编译警告。该警告会出现是因为编译器无法确保在运行时类型转换能成功。例如,如果E是string,而newobjectl是Integer对象的数组,那么(Stringl])(newObjectll)将会导致classCastException异常。这种类型的编译警告是对Java泛型的限制,也是无法避免的。不允许使用泛型类创建泛型数组。例如,下面的代码是错误的:
10·第21章泛型ArrayList<String>[ 1ist = new ArrayList<String>[10];可以使用下面的代码来规避这种限制:ArrayList<String>[1ist (ArrayList<String>[])newArrayList[10];你将会得到一个编译警告。限制3:在静态环境下不允许类的参数是泛型类型由于泛型类的所有实例都有相同的运行时类,所以泛型类的静态变量和方法是被它的所有实例所共享的。因此,在静态方法、数据域或者初始化语句中,为了类而引用泛型类型参数是非法的。例如,下面的代码是非法的:public class Test<E>fpublic static voidm(Eo1)(// I1legal1public static E ol;//I1legalstaticfEo2;//111ega1子1限制4:异常类不能是泛型的泛型类不能扩展java,lang.Throwable,因此,下面的类声明是非法的为什么?如果允许这择做,就应为MyException<T>添加一个catch子句,如下所示:pub1ic classMyException<T>extends Exception(3JVM必须检查这个从try子句中抛出的异常以确定它是否与catch子句中指定的类型匹配。但这是不可能的,因为在运行时类型信息是不出现的。tryf1catch (MyException<T>ex)321.8实例学习:泛型矩阵类本节给出了一个实例,使用泛型类型设计对矩阵进行操作的类。对于所有矩阵,除了元素类型不同以外,它们的加法和乘法操作都是类似的。因此,可以设计一个父类,不管它们的元素类型是什么,该父类描述所有类型的矩阵共享的通用操作,还可以创建若干个适用于指定矩阵类型的子类。这里的实例给出了两种类型一一int和Rational的实现。对于int类型而言,包装类Integer应该用于将一个int类型的值包装到一个对象中,这样,对象就被传递给完成操作的方法。该类的类图如图21-7所示。方法addMatrix和方法multiplyMatrix将泛型类型El]]的两个矩阵进行相加和相乘。静态方法printResult显示矩阵、操作以及它们的结果。方法add,multiply和zero都是抽象的,因为它们的实现依赖于数组元素的特定类型。例如,zero()方法对于Integer类型返回o,而对于Rationa1类型返回o/1。这些方法将会在指定了矩阵元素类型的子类中实现。IntegerMatrix和RationalMatrix是GenericMatrix的具体子类。这两个类实现了在GenericMatrix类中定义的add、multiply和zero方法。程序清单21.9实现了GenericMatrix类。第1行的<EextendsNumber>指明该泛型类型是Number的子类型。三个抽象方法add,multip1y和zero在第3、6、9行定义。这些方法是抽象的,因为在不知
型·11第21章泛道元素的确切类型时我们是不能实现它们的。addMatrix方法(第12~30行)和multiplyMatrix方法(第33~57行)实现了两个矩阵的相加和相乘。所有这些方法都必须是非静态的,因为它们使用的是泛型类型E来表示类。printResult方法(第60~84行)是静态的,因为它没有绑定到特定的实例。GeneneMartrcEcrtendsNumbenIntegerMatrix#add(elementl:E,element2:E):E#multiply(elementl:E,element2:E):EszeroO:E+addMatrix(matrixl:E[JUmatrix2:E):EDD)+multiplyMatrix(matrixl:ED(,matrix2:EDD):ED)+printResult(ml:Number[/l,m2:Number[RationaiMatrixm3:Number,op:char):void图21-7GenericMatrix类是IntegerMatrix类和RationalMatrix类的一个抽象父类矩阵元素的类型是泛型的。这样就可以使用任意类的对象,只要在子类中实现了抽象方法add、multiply和zero即可。addMatrix和multiplyMatrix方法是具体的方法。只要在子类中实现了add,multiply和zero方法,就可以使用它们。addMatrix和multiplyMatrix方法在进行操作之前检查矩阵的边界。如果两个矩阵的边界不匹配,那么程序会抛出一个异常(第16,36行)。程序清单21.9GenericMatrix.java1public abstract class GenericMatrix<Eextends Number>(2/* Abstract method for adding two elements of the matrices */3protected abstract E add(E ol, E o2);45/s Abstract method for multiplying two elements of the matrices/6protected abstract Emultiply(E ol, Eo2);78/* Abstract method for defining zero for thematrix element */9protected abstract E zeroO;1011/**Addtwomatrices*/12pub1icEaddMatrix(Ematrixl,Ematrix2)13Check bounds of the two matrices14if((matrixl.length 1-matrix2.length)1l15(matrix1[0].iength [=matrix2.1ength)){16throw new RuntimeException(17"The matrices do not have the same size");18子1920E result-21(E[])new Number[matrix].length][matrix1[0].length];3R// Perform addition24for(int i=; i < result.length; i++)567for(intj.-.o;j<result[ij.length;j++)友result[i][j]-add(matrixi[i][].matrix2[][j]];A78129return result;30子122PDG/Multiplytwomatrices*/33publicEmultiplyMatrix(E[matrixl,Ematrix2)453//Checkboundsif (matrix1[0].1ength1=matrix2.length){36throw new RuntimeException(37"The matrices do not have compatible size");