什么是泛型?
泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它.例如,可以定义一个泛型栈类,它存储的是泛型元素。可以从这个泛型类生成一个包含字符串的栈对象和一个包含数字的栈对象。这里,字符串和数字都是替换泛型的具体类型.
使用泛型的主要优点是能够在编译时而不是在运行时检测出错误。泛型类或方法允许用户指定可以和这些类或方法一起工作的对象类型.如果试图使用一个不相容的对象,编译器就会检测出这个错误。
动机和优点
这里的T表示形式泛型类型(formal generic type),随后可以用一个实际具体类型(actual concrete type)来替换它。替换泛型类型称为泛型实例化(generic instantiation).按照惯例,像E或T这样的单个大写字母用于表示一个形式泛型类型.
例如,下面的语句能过创建一个字符串的线性表:
ArrayList<String> list = new ArrayList<String>();
现在,就只能向该线性表中添加字符串.例如:
list.add("Red");
如果试图向其中添加非字符串,就会产生编译错误。例如,下面的语句就是不合法的,因为list只能包含字符串:
list.add(new Integer(1));//错误
泛型必须是引用类型.不能像用int,double或char这样的基本类型来替换泛型类型.例如:
下面的语句是错误的:
ArrayList<int> intlist = new ArrayList<int>();//错误
为了给int值创建一个ArrayList对象,必须使用:
ArrayList<Integer> intList = new ArrayList<Integer>();
可以向intList中加入一个int值.例如:
intList.add(5);
Java会自动地将5包装为new Integer(5).这个过程称为自动打包(autoboxing)
无需类型转换就可以从一个元素类型已指定的列表中获取一个值,因为编译器已经知道了这个元素类型。例如,下面的语句创建了一个包含字符串的列表,然后将字符串加入这个列表,最后从这个列表中获取该字符串。
ArrayList<String> list = new ArrayList<String>();
list.add("Red");
list.add("White");
String s = list.get(0);//No casting is needed
在JDK1.5之前,由于没有使用泛型,所以必须把返回值的类型转换为String,如下所示:
String s = (String)(list.get(0));//Casting needed prior to JDK 1.5
如果元素类型是包装类型,例如,Integer,Double或Character,那么可以直接将这个元素赋给一个基本类型的变量。这个过程称为自动解包
例如:
ArrayList<Double> list = new ArrayList<Double>();
list.add(5.5);
list.add(3.0);
Double doubleObject = list.get(0);
double d = list.get(1);
定义泛型类和接口
package chapter21;
public class GenericStack<E>
{
private java.util.ArrayList<E> list = new java.util.ArrayList<E>();
public int getSize()
{
return list.size();
}
public E peek()
{
return list.get(getSize() - 1);
}
public void push(E o)
{
list.add(o);
}
public E pop()
{
E o = list.get(getSize() - 1);
list.remove(getSize() - 1);
return o;
}
public boolean isEmpty()
{
return list.isEmpty();
}
}
下面给出一个例子,它先创建一个存储字符串的栈,然后向这个栈添加三个字符串:
GenericStack<String> stack1 = new GenericStack<String>();
stack1.push("London");
stack1.push("Paris");
stack1.push("Berlin");
下面给出另一个例子,它先创建一个存整数的栈,然后向这个栈中添加三个整数:
GenericStack<Integer> stack2 = new GenericStack<Integer>();
stack2.push(1);
stack2.push(2);
stack2.push(3);
泛型方法
下面代码定义了一个泛型方法print来打印对象数组.
GenericMethodDemo.java代码:
package chapter21;
public class GenericMethodDemo
{
public static void main(String[] args)
{
Integer[] integers = {1,2,3,4,5};
String[] strings = {"London","Pairs","New York","Austin"};
GenericMethodDemo.<Integer>print(integers);
GenericMethodDemo.<String>print(strings);
}
public static <E> void print(E[] list)
{
for(int i = 0; i < list.length; i++)
System.out.println(list[i] + " ");
System.out.println();
}
}
为了调用泛型方法,需要将实际类型放在尖括号内作为方法名的前缀。例如:
GenericMethodDemo.<Integer>print(integers);
GenericMethodDemo.<String>print(strings);
可以将泛型指定为另外一种类型的子类型。这样的泛型称为受限的(bounded)
BoundedType.java代码:
package chapter21;
public class BoundedTypeDemo
{
public static void main(String[] args)
{
Rectangle rectangle = new Rectangle(2,2);
Circle circle = new Circle(2);
System.out.println("Same area? " + BoundedTypeDemo.<GeometricObject>equalArea(rectangle,circle));
}
public static <E extends GeometricObject> boolean equalArea(E object1,E object2)
{
return object1.getArea() == object2.getArea();
}
}
GeometircObject.java代码:
package chapter21;
public class BoundedTypeDemo
{
public static void main(String[] args)
{
Rectangle rectangle = new Rectangle(2,2);
Circle circle = new Circle(2);
System.out.println("Same area? " + BoundedTypeDemo.<GeometricObject>equalArea(rectangle,circle));
}
public static <E extends GeometricObject> boolean equalArea(E object1,E object2)
{
return object1.getArea() == object2.getArea();
}
}
Circle.java代码:
package chapter21;
public class Circle extends GeometricObject {
private double radius;
public Circle(){
}
public Circle(double radius){
this.radius = radius;
}
public Circle(double radius,String color,boolean filled){
this.radius = radius;
setColor(color);
setFilled(filled);
}
public double getRadius(){
return radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public double getArea(){
return radius * radius * Math.PI;
}
public double getDiameter(){
return 2 * radius;
}
public double getPerimeter(){
return 2 * radius * Math.PI;
}
public void printCircle(){
System.out.println("The circle is created " + getDateCreated() + " and the radius is " + radius);
}
}
Rectangle.java代码:
package chapter21;
public class Rectangle extends GeometricObject{
private double width;
private double height;
public Rectangle(){
}
public Rectangle(double width,double height){
this.width = width;
this.height = height;
}
public Rectangle(double width,double height,String color,boolean filled){
this.width = width;
this.height = height;
setColor(color);
setFilled(filled);
}
public double getWidth(){
return width;
}
public void setWidth(double width){
this.width = width;
}
public double getHeight(){
return height;
}
public void setHeight(double height){
this.height = height;
}
public double getArea(){
return width * height;
}
public double getPerimeter(){
return 2 * (width + height);
}
}
原始类型和向后兼容
可以使用泛型类而无需指定具体类型,如下所示:
GenericStack<Object> stack = new GenericStack<Object>();//raw type
它大体等价于下面的语句
GenericStack<Object> stack = new GenericStack();
像GenericStack和ArrayList这样不使用类型参数的泛型类称为原始类型(raw type).在Java的早期版本中,允许使用原始类型向后兼容(backward compatibility).例如,从JDK1.5开始,java.lang.Comparable中使用了泛型类型,但是,许多代码仍然使用原始类型Comparable,如下面的代码:
Max.java代码:
public class Max
{
public static Comparable max(Comparable o1,Comparable o2)
{
if(o1.compareTo(o2) > 0)
return o1;
else
return o2;
}
}
Comparable o1和Comparable o2都是原始类型声明。原始类型是不安全的。例如,我们可能使用下面的语句调用max方法:
Max.max("Welcome",23);//23 is autoboxed into new Integer(23)
这可能会引起一个运行时错误,因为不能将字符串与整数对象进行比较。