2·第21章泛型方法来比较Date对象和一个字符串。这样的代码会产生编译错误,因为传递给compareTo方法的参数必须是Date类型的。由于这个错误可以在编译时而不是运行时被检测到,因而泛型类型使程序更加可靠。Comparable c-newDateO;Comparable<Date>CnewDateO:System.out.printin(c.compareTo("red"));System.out.println(c.compareTo("red"));b) JDK 1.5a)在JDK1.5之前图21-2新泛型类型在编译时检测到可能的错误在11.11节中介绍过ArrayList类,从JDK1.5开始,该类是一个泛型类。图21-3分别给出ArrayList类在JDK1.5之前和从JDK1.5开始的类图。Jayaat.AmayListJavautiLAmyLotcEs+ArrayListO+ArrayListO+add(o:Object):void+add(o:E): void+add(index:int,o:Object):void+add(index:int,o:E):void+clearO:void+clearO: void+contains(o: Object):boolean+contains(o:Object):boolean+get(tindex:int):Object+get(index:int): E+indexOf(o:Object):int+indexof(o:Object):int+isEmptyO:boolean+isEmptyO:boolean+lastIndexOf(o:Object):fnt+lastIndexof(o:Object):int+remove(o:Object):boolean+remove(o:Object):boolean+sizeO:int+sizeO:int+remove(index:int);boolean+remove(index: int): boolean+set(index:int,o: Object):Object+set(index:int,o:E):Ea)在JDK1.5之前的ArrayListb)在JDK1.5中的ArrayList图21-3从JDK15开始,ArrayList是一个泛型类例如,下面的语句能够创建一个字符串的线性表:ArrayList<String>1istnew ArrayList<String>O;现在,就只能向该线性表中添加字符串。例如,1ist.add("Red");如果试图向其中添加非字符串,就会产生编译错误。例如,下面的语句就是不合法的,因为1ist只能包含字符串:1ist.add(new Integer(1));泛型类型必须是引用类型。不能用像intdouble或char这样的基本类型来替换泛型类型。例如,下面的语句是错误的:ArrayList<int>intList =newArrayList<int>O;为了给int值创建一个ArrayList对象,必须使用:ArrayList<Integer>intList=newArrayList<Integer>O;可以在intList中加入一个int值。例如。Kintlist.add(5):Java会自动地将5包装为newInteger(5)。这个过程称为自动打包(autoboxing),这是在14.ii节中介绍的。无需类型转换就可以从一个元素类型已指定的列表中获取一个值,因为编译器已经知道了这个元素DG类型。例如,下面的语句创建了一个包含字符串的列表,然后将字符串加人这个列表,最后从这个列表中获取该字符串。1 ArrayList<String> 1ist =new ArrayList<String>O;2 1ist.add("Red");
型·3第21章泛31ist.add("white");4Strings-1ist.get(o);// No castingis needed在JDK1.5之前,由于没有使用泛型,所以必须把返回值的类型转换为String,如下所示:String s = (String)(list.get(0)); // Casting needed prior to JDk 1.5如果元素是包装类型,例如,Integer、Double或character,那么可以直接将这个元素赋给个基本类型的变量。这个过程称为自动解包(autounboxing),这是在14.11节中介绍的。例如,请看下面的代码:1 ArrayList<Double> 1ist -new ArrayList<Double>O;21ist.add(5.5);// 5.5is automatica1ly converted to newDouble(5.5)31ist.add(3.0):.//3.0isautomatica11yconvertedtonewDouble(3.0)4 Double doubleObject-1ist.get(o):// No casting is needed5doubled-1ist.get(1);// Automatically converted to double在第2行和第3行,5.5和3.0自动转换为Doub1e对象,并添加到1ist中。在第4行,1ist中的第一个元素被赋给一个Double变量。在此无需类型转换,因为list被声明为Doub1e对象。在第5行,1ist中的第二个元素被赋给一个double变量。1ist.get(1)中的对象自动转换为一个基本类型的值。21.3定义泛型类和接口修改11.12节中的栈类,将元素类型扩展到泛型类型。新的名为GenericStack的栈类如图21-4所示,在程序清单21.1中实现它。CenerieStack<E>-list: java.utfl.ArrayList<E>一个存储元素的数组列表+enericStackO创建一个空栈+getSizeO:int返回这个栈的元素个数+peekO:E返回这个栈的栈顶元素+popO:E返回并删除这个栈的栈顶元素+push(o:E):void向这个栈的顶端添加一个新元素+isEmptyO:boolean如果栈为空则返回true图21-4Genericstack类封装了栈的存储,并提供对该栈进行操作的动作程序清单21.1Genericstack.java1pub1ic class GenericStack<E>(2private java.uti1.ArrayList<E>1ist=.new java.uti1.ArrayList<E>O;34public intgetSizeO5returnlist.sizeO:6子新7R8pub1icEpeekOf9returniist.get(getSizeO-1);103成11茶12public void push(Eo)(131ist.add(o);14315WPDG16pub1icEpopO(17Eo-1ist.get(getSizeO-1);181ist.remove(getSizeO-1);19return o;20子2122publicbooleanisEmptyO
型4·第21章泛23return 1ist.isEmptyO:24)25下面给出一个例子,它先创建一个存储字符串的栈,然后向这个栈添加三个字符串:GenericStack<String> stack1 new GenericStack<String>O;stackl.push("London");stackl.push("Paris");stackl.push("Berlin");下面给出另一个例子,它先创建一个存储整数的栈,然后向这个栈添加三个整数:GenericStack<Integer>stack2-new GenericStack<Integer>O;stack2.push(1)://auto boxing1to newInteger(1)stack2.push(2);stack2.push(3);也可以不使用泛型,只要将元素类型设置为object,它就可以适应任何对象类型。但是,使用泛型能够提高软件的可靠性和可读性,因为某些错误能在编译时而不是运行时被发现。例如,由于stack1被声明为Genericstack<string>,所以,只可以将字符串添加到这个栈中。如果试图向stack1中添加整数就会产生编译错误。警告为了创建一个字符串堆栈,可以使用newGenericstack<String>()。这可能会误导你认为GenericStack的构造方法应该被定义为public Genericstack<E>()这是错误的。它应该被定义为public Genericstack()注意有时候,泛型类可能会有多个参数,在这种情况下,应将所有参数一起放在尖括号中并用返号分隔开,比如<E1,E2E3>,注意可以定义一个类或一个接口作为泛型或者接口的子类型。例如,在JavaAPI中,java.lang.String类被定义为实现Comparable接口,如下所示:public class String implements Comparable<string>21.4泛型方法从JDK1.5开始,可以定义泛型接口(例如,图21-1b中的Comparable接口)和泛型类(例如,程序清单21.1中的GenericStack类),还可以使用泛型类型来定义泛型方法。例如,程序清单21.2定义了一个泛型方法print(第10~14行)来打印对象数组。第6行传递一个整数对象的数组来调用泛型方法print。第7行用字符串数组调用print。程序清单21.2GenericMethodDemo.java福1pub1ic classGenericMethodDemopublic staticvoid main(String[) args )f23Integer integers =(1,2,3,4,5);64String[J strings-("London",""Paris","NewYork","Austin"}:A56GenericMethodDemo,<Integer>print(integers);7GenericMethodDemo.<String>print(strings);文8JPDG910public static<E>void print(E[) 1ist){11for (int i-O; i<list.length; i++)12System.out.print(list[i]+":13System.out.printInO;14115J
型·5第21章泛为了调用泛型方法,需要将实际类型放在尖括号内作为方法名的前缴。例如,GenericMethodDemo.<Integer>print(integers);GenericMethodDemo.<String>print(strings);可以将泛型指定为另外一种类型的子类型。这样的泛型类型称为受限的(bounded)。例如,程序清单21.3修改了程序清单11.4中的equa1Area方法,以测试两个几何对象是否具有相同的面积。受限的泛型类型<EextendsGeometricobject>(第1o行)将E指定为Geometricobject的泛型子类型。此时必须传递两个Geometricobject的实例来调用equalArea。程序清单21.3BoundedTypeDemo.java1pub1icclassBoundedTypeDemo(2pubiic static void main(Stringargs)3Rectanglerectanglenew Rectangle(2,2)4Circle circle - new Circle(2);56System.out.println("Samearea?"7BoundedTypeDemo.<GeometricObject>equalArea(rectangle,circle));8子910pub1ic static<E extendsGeometricObject>boolean equalArea(11Eobjectl,Eobject2)[12returnobjectl.getAreaOobject2.getArea();13门14)注意非受限泛型类型<E>和<EextendsObject>是一样的。注意为了定义一个类为泛型类型,需要将泛型类型放在类名之后,例如,GenericStack<E>为了定义一个方法为泛型类型,要将泛型类型放在方法返回类型之前,例如,<E>Voidmax(E01.E02).21.55原始类型和向后兼容可以使用泛型类而无需指定具体类型,如下所示:GenericStack stack-new GenericStack:// raw type它大体等价于下面的语句GenericStack<Object>stack =new GenericStack<Object>O;像GenericStack和ArrayList这样不使用类型参数的泛型类称为原始类型(rawtype)在Java的早期版本中,允许使用原始类型向后兼容(backwardcompatibility)。例如,从JDK1.5开始,在java.lang.Comparable中使用了泛型类型,但是,许多代码仍然使用原始类型comparable,如程序请单21.4所示(也可参见14.5节中的Max类):-程序清单21.4Max.java1public classMax (成2/** Return themaximum between two objects */3pub1icstatic Comparablemax(Comparableol,Comparableo2){開4if(ol.compareTo(o2)>0)5return ol:6else>returno2;PDG6丹93Comparableo1和Comparableeo2都是原始类型声明。原始类型是不安全的。例如,我们可能会使用下面的语句调用max方法:Max.max("welcome",23)://23 isautoboxed intonewInteger(23)
型6·第21章泛这可能会引起一个运行时错误,因为不能将字符串与整数对象进行比较。如果在编译时使用了选项-x1int:unchecked,Java编译器就会在第3行显示一条警告,如图21-5所示。-lxC:lbook>javac-xlintruncheckedKex,jauaHax,jaueS:warning:[unchecked]uncheckedtype java.lang CoeparableeareTo(a2)warning图21-5使用编译器选项-x1int:unchecked会显示一条免检的警告一个更好的编写max方法的方式是使用泛型类型,如程序清单21.5所示。程序清单21.5Max1.java1pub1icclassMax1(27*s Return themaximum between two objects */3pub1icstatic<EextendsComparabTe<E>>Emax(Eol,Eo2)(4if(ol.compareTo(o2)>0)5return ol;6else7return02;8193如果使用下面的命令调用max方法:Max1.max("welcome",23);//23 is autoboxed into newInteger(23)就会显示一个编译错误,因为Max1中的max方法的两个参数必须是相同的类型(例如,两个字符串或两个整数对象)。此外,类型E必须是Comparable<E>的子类型。下面的代码是另外一个例子,可以在第1行声明一个原始类型stack,在第2行将newGenericStack<String>赋给它,然后在第3行和第4行将一个字符串和一个整数对象压入栈中。1CenericStackstack;2 stack-new GenericStack<String>;3stack.pushc"welcometo Java"):4 stack.push(new Integer(2));第4行是不安全的:因为该栈是用于存储字符串的,但是一个Integer对象被添加到该栈中。第3行本应是可行的,但是编译器会在第3行和第4行都显示警告,因为它没有遵循程序的语义含义。所有编译器都知道该栈是原始类型的,并且在执行某些操作时会不安全。因此,它会显示警告以提醒你提防潜在的间题。提示由于原始类型是不安全的,所以,本书从此不再使用原始类型21.6通配泛型通配泛型是什么?为什么需要通配泛型?程序清单21.6给出了一个例子,以解释为什么需要通配泛型。该例子定义了一个泛型max方法,该方法可以找出数字栈中的最大数(第12一22行)。main方法创建了一个整数对象栈,然后向该栈添加三个整数,最后调用max方法找出该栈中的最大数字。D程序清单21.6WildCardDemo1.javaPDG1 public classwildCardDemol(2public staticvoid main(String[) args)f3CenericStack<Integer>intStack=newGenericStack<Integer>O:4intStack.push(1): // 1 is autoboxed into newInteger(1)5intStack.push(2);6intStack.push(-2):7