之前我们已经了解了如何创建一个泛型类和如何将一个泛型类的对象传入方法。但是之前我们将泛型对象传入方法的时候,这些方法都没有返回值,这些方法的类型都是void,但是如果方法有返回值呢?如果方法的返回值是一个泛型呢?这时候就需要泛型方法了。
public class GenericMethodDemo
{
public static void main(String[] args)
{
String[] names = { "Alfonso", "Beatrice", "Celine" };
displayArray(names);
}
public static void displayArray(E[] array)
{
for (E element : array)
System.out.println(element);
}
}
注意到这个方法有一个类型变量E,当调用泛型方法时,编译器将自动确定要使用哪种类型。在这里,因为传入的数组是string类型的数组,所以编译器将自动将E变为string。
你也可以像之前一样对泛型方法的类型参数作一个限定:
public static2、泛型方法与泛型类比较void displayArray(E[] array) { for (E element : array) System.out.println(element); }
我们为什么要创建一个泛型类呢?是因为在这个确定的类中,某些字段的类型是不确定的,比如之前的point类,这个类的x,y坐标的类型是不确定的,可能是int,可能是double,而这个类永远都是一个point类。所以我们说这个确定的类中,某些字段的类型是不确定的。
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; } ... ... ... ... ...
而当我们写一个泛型方法的时候,这个方法的类型并不是确定的(不像泛型类的类型被确定为point
继承也可以用于泛型类,假设我们有一个Point3D类,这个类继承自之前的point类,但是多了一个z坐标。
public class Point3Dextends Point { private T zCoordinate; // The z coordinate public Point3D(T x, T y, T z) { // Call the Point class constructor. super(x, y); // Assign the Z coordinate. zCoordinate = z; } public void setZ(T z) { zCoordinate = z; } public T getZ() { return zCoordinate; } }
首先我们关注的是类的头:public class Point3D
public class MyClass{ class code here... }
第二点需要注意的地方是子类的构造器是如何调用超类构造器以及是如何实例化这个类的。并且我们发现这个类只有对z轴的方法,因为对x,y的方法已经写在超类中了,这里并不需要重复。
同样Point3D和Point有一个“is-a” relationship,所以我们可以用一个Point变量来引用Point3D对象:
Point3Dpoint = new Point3D<>(10, 20, 30);
最后,泛型类和非泛型类是可以相互继承与被继承的。
三、定义多个类型变量之前的例子中,我们定义的泛型类只有一个类型变量,当我们的泛型类中有多种类型变量的时候,情况是相似的:
public class MyClass{ class code here... }
下面我们看一个例子,这个例子接受两个参数,这两个参数的参数类型都是不确定的:
public class Pair{ private T first; // The first item private S second; // The second item public Pair(T firstArg, S secondArg) { first = firstArg; second = secondArg; } public T getFirst() { return first; } public S getSecond() { return second; } }
下面我们用这个类将两个不同类型的变量拼接起来:
public class PairTest
{
public static void main(String[] args)
{
// Create an Integer to hold an ID number.
Integer idNumber = new Integer(475);
// Create a String to hold a name.
String name = "Smith, Sally";
// Create a Pair object to hold the ID
// number and the name.
Pair myPair =
new Pair<>(idNumber, name);
// Display the pair of items.
System.out.println("ID Number: " +
myPair.getFirst());
System.out.println("Name: " +
myPair.getSecond());
}
}
得到的输出:
ID Number: 475 Name: Smith, Sally
这个例子中,pair类中的两个参数类型是任意的。
四、接口与泛型 1、comparable接口在java中,除了类可以是泛型的之外,接口也可以是泛型的,比如在java API中有一个接口叫做Comparable,定义如下:
public interface Comparable{ int compareTo(T o); }
此接口定义了一个名为compareTo的方法,它用于将调用对象与作为参数传递到方法中的另一个对象进行比较。接口定义了一个类型参数T,T是o参数的类型。
在java中,很多类都实现了这个接口,比如string类以及一些包装类。如果调用对象小于传递给o的对象,则该方法返回一个负数。如果调用对象等于传递给o的对象,则该方法返回0。如果调用对象大于传递给o的对象,则该方法返回一个正数。
假设我们正在编写一个名为Tree的类,并且我们希望Tree类能够实现Comparable接口:
public class Tree implements Comparable{ public int compareTo(Tree o) { Method code here... } Other class code here... }
我们把Tree当作类型参数传入了Comparable接口,所以实际上接口实际上变成了:
int compareTo(Tree o)2、将类型变量限定在实现过某个接口的类中
方法通常的参数是对接口的引用,而不是对类的引用。例如,假设我们希望编写一个名为greatest的泛型方法,它将比较两个对象,并确定哪一个是这两个对象中的“更大”。当然,“更大”的概念完全取决于正在被比较的对象。如果我们正在比较两个豪华车,最昂贵的一个可能会被认为是更大的一个。然而,如果我们比较两个赛车,最高时速最高的物体可能被认为是更大的。任何作为参数传递给greatest方法的对象都必须是一个支持标准比较方法的类型,这样我们就可以确定比较的对象哪个更大。
Comparable接口就是用来确定这个类是不是支持标准的比较方法的,如果这个某个类实现了Comparable接口,那么这个类必然有一个compareTo方法,而对于这个类,这个特定的compareTo方法一定是符合实际情况的(对赛车就比较最高速度,对豪华车就比较价格),所以我们用T extends Comparable
这个类将用compareTo方法来搜索一个数组(如果有,根据compareTo方法就会返回0,没找到就返回默认的-1):
public class GenericSearchArray
{
public static void main(String[] args)
{
int position; // To hold a string's position in the array
// Array of strings to search
String[] names = { "Jack", "Kelly", "Beth",
"Chris", "Kenny", "Britainy" };
// Search the array for Chris.
position = sequentialSearch(names, "Chris");
// Determine whether Chris was found.
if (position == −1)
System.out.println("Chris is not in the array. ");
else
System.out.println("Chris is at position " + position);
}
public static < E extends Comparable >
int sequentialSearch(E[] array, E value)
{
int index; // Loop control variable
int position; // Position the value is found at
boolean found; // Flag indicating search results
// Position 0 is the starting point of the search.
index = 0;
// Store the default values for position and found.
position = −1;
found = false;
// Search the array.
while (!found && index < array.length)
{
if (array[index].compareTo(value) == 0)
{
found = true;
position = index;
}
index++;
}
return position;
}
}
五、Erasure
当java编译器在遇到泛型的时候,编译器会根据实际情况,将泛型转换为真正的类型参数,当泛型没有指定上限的时候,java会将泛型自动转换为Object:
public class Point{ private T xCoordinate; private T yCoordinate; public Point(T x, T y) { code... }
实际上是:
public class Point
{
private Object xCoordinate;
private Object yCoordinate;
public Point(Object x, Object y)
{ code... }
当指定上限的时候,java会将泛型转换为上限的类型:
public class Point{ private T xCoordinate; private T yCoordinate; public Point
实际上是:
public class Point
{
private Number xCoordinate;
private Number yCoordinate;
public Point(Number x, Number y)
{ code... }
这种操作被叫做Erasure。
当我们使用泛型的时候,很多时候编译器会自动帮你强制转换:
Integer x = new Integer(1); Integer y = new Integer(2); PointmyPoint = new Point<>(x, y);
我们将一个Integer类型的泛型对象传递给Integer类型的变量时:
Integer tempX = myPoint.getX();
而因为在遇到泛型的时候,实际上是将泛型转变为objetc类型进行处理的,所以当我们返回的时候,实际上是这样的:
Integer tempX = (Integer)myPoint.getX();六、使用泛型的一些限制 1、不能创建泛型类型的实例对象
public class MyClass{ public MyClass() { // The following statement causes an ERROR. T myObject = new T(); } }
表达式newT()意味着T是构造函数的名称,但它不是。
2、不能创建通用类对象的数组下面这行语句不会过编:
ArrayList[] a = new ArrayList<>[100];
这里是希望创建一个Arraylist<>类型的数组(注意不是创建一个长为100的Arraylist),在java中数组会包含数组的元素类型,但是由于erasure,泛型对象并不会包含类型,或者说泛型对象的类型要在实例化之后才会被确定,所以在实例化之前,编译器并不知道存在数组里的泛型对象的具体类型,所以编译器不会通过编译。
3、静态字段的类型不能是泛型,也不能将静态方法的类型设置为泛型public class MyClass{ // The following statement will cause an ERROR. private static T value; }
因为所有泛型类最终映射到同一个原始类型类,而静态属性是类级别的,类和实例共同拥有它的一份存储,因此一份存储无法安放多个类型的属性。静态方法也是如此。
4、不能在泛型中使用异常让我们想象一下,为了让Java应用程序使用泛型异常类,需要发生什么。 首先,必须能够在catch子句中使用泛型类型表示法。 然后,当抛出一个泛型异常时,系统必须检查用于创建异常类的类型参数,看它是否与catch子句中指定的类型匹配。 但这无法实现,因为在运行时,泛型类型信息已经被编译器擦除,所以编译器在编译时无法知道异常从哪里来,或者用什么类型参数来创建异常,所以不允许创建泛型异常类。 具体来说,你不允许创建Throwable的泛型子类。 也不允许在catch子句中使用泛型类型表示法。



