回顾之前我们已经用烂了的arraylist,它可以根据元素的个数自动增加长度,它还可以储存不同种类的数据,相较于普通的数组,这是arraylist两个主要的优势,下面我们看一个例子:
import java.util.ArrayList;
public class UnsafeArrayList
{
public static void main(String[] args)
{
// Create an ArrayList object.
ArrayList list = new ArrayList();
// Store a variety of objects in the list.
list.add("Java is fun!"); // Add a String
list.add(new Double(2.5)); // Add a Double
list.add(new Rectangle(10, 12)); // Add a Rectangle
// Retrieve a reference to each object in the list.
// Note that the reference returned from the get
// method must be cast to the correct type.
String s = (String)list.get(0);
Double d = (Double)list.get(1);
Rectangle r = (Rectangle)list.get(2);
}
}
注意看调用get方法的那几行,在每一次调用get方法是时候,都将实例强制转化了,这是必须的,如果不进行强制转化java就会在编译的时候报错。
不过即使你加上了强制转化,程序虽然在编译的时候不报错了,但是在运行的时候依旧有可能报错,将上面的代码稍作更改:
// Create an ArrayList object.
ArrayList list = new ArrayList();
// Store a variety of objects in the list.
list.add("Java is fun!"); // Store a String at element 0
list.add(new Double(2.5)); // Store a Double at element 1
list.add(new Rectangle(10, 12)); // Store a Rectangle at element 2
// Retrieve a reference to each object in the list.
Double d = (Double)list.get(0); // Error! Element 0 is a String
String s = (String)list.get(1);
Rectangle r = (Rectangle)list.get(2);
这时强制转化的类型出错了,在运行的时候程序报错,一个ClassCastException异常会被扔出。
幸运的是,Java提供了一个被称为泛型的特性,通常被称为泛型类,泛型允许编译器执行类型检查和报告类型不兼容性。与其在运行时发现代码中的类型不匹配,在编译的时候发现异常显然是更好的选择。
泛型类或方法是一种 指定该类或方法使用时 面向的对象类型 的方法。如果尝试将类或方法用于不允许类型的对象,则编译时发生错误。在array list中,它就可以指定你储存在array list中的数据的类型,比如只允许储存string类的对象到arraylist中:
ArrayListmyList = new ArrayList ();
这时,编译器就已经知道array list中储存的数据类型,所以再调用get方法指定对象的引用的时候,就不需要强制转化了:
ArrayListmyList = new ArrayList (); myList.add("Java is fun!"); String str = myList.get(0);
当我们使用泛型的时候,我们可以让编译器自己推断数据的类型,要达到这个目的我们可以使用 "<>"符号,这个符号作为一个整体。比如原来array list这么写:
ArrayListlist = new ArrayList ();
现在可以省略右侧的string:
ArrayListlist = new ArrayList<>();
string两边都有。左侧用来声明引用变量的类型,右侧用来调用ArrayList构造函数的部分。
二、创建自己的泛型类类似于array list,java提供了一些自带的泛型类,当然你也可以自己写一个泛型类:
public class Point{ private T xCoordinate; // The X coordinate private T yCoordinate; // The Y coordinate public Point(T x, T y) { xCoordinate = x; yCoordinate = y; } public void setX(T x) { xCoordinate = x; } public void setY(T y) { yCoordinate = y; } public T getX() { return xCoordinate; } public T getY() { return yCoordinate; } }
Point类的实例有两个值,分别存储两个坐标。注意到这个类的头,出现了“
Integer intX = new Integer(10); Integer intY = new Integer(20); PointmyPoint = new Point<>(intX, intY);
对于Point类中的实例,T就变成了Integer:
如果我们将整数作为类型参数传递给T,这些声明实际上会变成如下所示的样子:
public Point(Integer x, Integer y) public void setX(Integer x) public void setY(Integer y) public Integer getX() public Integer getY()
下面的代买创建了两个Point实例,在第一个实例中,Double传递给类型参数T,在第二个实例中,Integer类型传递给T:
public class TestPoint
{
public static void main(String[] args)
{
// Create two Double objects to use as coordinates.
Double dblX = new Double(1.5);
Double dblY = new Double(2.5);
// Create a Point object that can hold Doubles.
Point dPoint = new Point<>(dblX, dblY);
// Create two Integer objects to use as coordinates.
Integer intX = new Integer(10);
Integer intY = new Integer(20);
// Create a Point object that can hold Integers.
Point iPoint = new Point<>(intX, intY);
// Display the Double values stored in dPoint.
System.out.println("Here are the values in dPoint.");
System.out.println("X Coordinate: " + dPoint.getX());
System.out.println("Y Coordinate: " + dPoint.getY());
System.out.println();
// Display the Integer values stored in iPoint.
System.out.println("Here are the values in iPoint.");
System.out.println("X Coordinate: " + iPoint.getX());
System.out.println("Y Coordinate: " + iPoint.getY());
}
}
输出如下:
Here are the values in dPoint. X Coordinate: 1.5 Y Coordinate: 2.5 Here are the values in iPoint. X Coordinate: 10 Y Coordinate: 20
当“Point
public Point(Double x, Double y)
此时如果x,y不是Double类型,编译器将报错。
三、类型参数 1、autoboxing和unboxing当你创建了一个泛型类的时候,你只能将引用类型当作变量传入类型参数,把基础数据类型传入参数是不允许的。之前的例子中,我们使用了Double和Integer而非double and int。
不过虽然泛型只能接受引用类型作为自己的类型参数,我们依旧可以使用java中的autoboxing来简化我们的代码,之前的例子也可以写成这样:
PointdPoint = new Point<>(1.5, 2.5);
这里的1.5和2.5就由double被autoboxing成Double,所以这里的语句和合法的。
与autoboxing相对的是Unboxing,看下面的例子:
// Use autoboxing to pass doubles to the constructor. PointdPoint = new Point<>(1.5, 2.5); // Use unboxing to retrieve the X and Y coordinates // and assign them to double variables. double x = dPoint.getX(); double y = dPoint.getY();
最后的两句话中,get方法返回的应该是一个Double类型的对象,但是变量类型却是double,所以java自动将Double Unboxing成double。
2、不指定类型参数实例化泛型类虽然不建议使用,但可以不提供类型参数地创建一个泛型类的实例。
Point myPoint = new Point(x, y);
在这里并没有提供类型参数,当泛型类实例化提时没有供类型参数时,Java默认情况下将提供Object作为类型参数,所以实际上,myPoint类中的代码是这样:
private Object xCoordinate; // The X coordinate private Object yCoordinate; // The Y coordinate
同样地,在类方法中出现的每一次T都将成为Object。
当这样使用时,编译器将显式如下警告:
TestPoint2.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
所以不写参数类型是非常不推荐的,因为这样放弃了本来代码中类型的安全性 。但是这样做也有一个好处,即你可以让变量引用不同类型的实例。因为根据多态性,Object类可以引用任何类型的变量。
同时,如果不写参数类型,当你返回泛型类的实例的时候,你需要将Object类的实例强制转化成你希望的类:
// Create an Integer and a Double. Integer x = new Integer(1); Double y = new Double(7.5); // Create a Point object with no type argument. Point myPoint = new Point(x, y); // Retrieve the X and Y coordinates. Integer xx = (Integer) myPoint.getX(); Double yy = (Double) myPoint.getY();3、常用的类型参数名
本例中所用的T并非确定的或唯一的,你可以用任何合法的标识符来表示类型参数名,以下是常用的类型参数名称:
四、将泛型对象传递给方法假设我们有一个方法用来打印Point的x坐标和y坐标,方法如下:
public static void printPoint(Pointpoint) { System.out.println("X Coordinate: " + point.getX()); System.out.println("Y Coordinate: " + point.getY()); }
这个方法接受一个Point
// Will this do what we want? public static void printPoint(Pointpoint) { System.out.println("X Coordinate: " + point.getX()); System.out.println("Y Coordinate: " + point.getY()); }
既然不管什么类型的数字包装类都继承自Number类,那我们直接把参数类型改成Number不久行了吗?遗憾的是,如果这么写,编译器将只接受Number类的对象,而尝试将其子类当作参数传入都会报错。
那我们到底应该怎么办呢?解决方法如下:
public static void printPoint(Point> point)
{
System.out.println("X Coordinate: " + point.getX());
System.out.println("Y Coordinate: " + point.getY());
}
注意到参数的类型是Point>,在括号里的是?通配符,现在我们就可以向方法里传递任何参数了:
PointiPoint = new Point<>(1, 2); Point dPoint = new Point<>(1.5, 2.5); printPoint(iPoint); printPoint(dPoint);
但是新的问题出现了:原来的方法太过限制,而现在的方法又太开放了,它可以接受任何类型的参数作为其变量,这显然也不是我们想要的。我们想要的是这个方法可以接受任何数字类型的参数作为变量,那我们如何限制参数类型的范围呢?这时我们就需要extends关键字了。
public static void printPoint(Point extends Number> point)
{
System.out.println("X Coordinate: " + point.getX());
System.out.println("Y Coordinate: " + point.getY());
}
在这个版本的方法中,参数的类型是Point extends Number>。这意味着点对象的类型参数可以是“Number”,或扩展“Number”的任何子类,所以前两个方法调用都是合法的,第三个方法调用会报错:
PointiPoint = new Point<>(1, 2); Point dPoint = new Point<>(1.5, 2.5); printPoint(iPoint); printPoint(dPoint); Point sPoint = new Point<>("1", "2"); printPoint(sPoint); // Error!
如果一个方法有很多参数,那么按之前的写法写起来就会非常麻烦:
public static void doSomething(Point extends Number> arg1,
Point extends Number> arg2,
Point extends Number> arg3)
不过Java提供了另外一种规定参数类型的语法:
public staticvoid doSomething(Point arg1, Point arg2, Point arg3)
注意,在static和void的单词之间,出现了
当使用在泛型中使用extends关键词时,实际上是在为类型参数限制一个上界。例如
除了extends关键字外,还可以使用关键字super来限制类型参数。下面是一个例子:
public static void doSomething(Point super Integer> arg)
和之前的extends关键词相反,这里的super设定了下界,这里的参数可以是任何Integer类之上的 类型的参数。



