泛型即指将类型参数化,使用泛型的目的是让编译器在编译时就检测到错误,而不是要等到运行时才发现,因此可以提高软件的可靠性和可读性。
从JDK1.5开始可以定义泛型类,接口和方法。
JDK 1.5 之前
package java.lang;
public interface Comparable {
public int compareTo(Object o)
}
JDK 1.5 之后:
package java.lang;
public interface Comparable<T> {
public int compareTo(T o)
}
如果没有泛型,必须用强制类型转换,否则不需要:
1 ArrayList<String> list = new ArrayList<>();
2 list.add("Red");
3 list.add("White");
String s = (String)(list.get(0)); // 可以简写成 String s = list.get(0);
泛型类型必须是引用类型,不能用原生类型,下例语句是错的:
ArrayList<int> intList = new ArrayList<>(); // Error! int 必须改成Integer
定义泛型类和接口
ArrayList
类就是一个泛型类,Comparable
接口就是一个泛型接口。
泛型栈:
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();
}
@Override
public String toString() {
return "stack: " + list.toString();
}
}
用法:
GenericStack<String> stack1 = new GenericStack<>();
stack1.push("London");
stack1.push("Paris");
stack1.push("Berlin");
GenericStack<Integer> stack2 = new GenericStack<>();
stack2.push(1); // autoboxing 1 to new Integer(1)
stack2.push(2);
stack2.push(3);
但是泛型类的构造函数不带类型:
public GenericStack<E>() // 错误! 构造函数不能带类型
正确的写法如下:
public GenericStack()
如果泛型类的参数多于1个,要写成下面这样,用逗号分隔:
<E1, E2, E3>
可以将一个接口或类定义为泛型类或接口的子类型,例如java.lang.String
类的定义:
public class String implements Comparable <String>;
泛型方法
静态方法可以定义泛型类型, 不明白为什么一定要是静态类型??
public class GenericMethodDemo {
public static void main(String[] args ) {
Integer[] integers = {1, 2, 3, 4, 5};
String[] strings = {"London", "Paris", "New York", "Austin"};
GenericMethodDemo.<Integer>print(integers);
GenericMethodDemo.<String>print(strings);
// 或简写为:
print(integers);
print(strings);
}
public static <E> void print(E[] list) {
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
System.out.println();
}
}
有界的泛型类型(bounded generic type):泛型类型为另一种类型的子类型。下面的例子:
绑定的泛型类型<E extends GeometricObject>
指定E
是GeometricObject
的泛型子类型,调用equalArea
时,必须传递两个GeometricObject
对象的实例。
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? " + equalArea(rectangle, circle));
}
public static <E extends GeometricObject> boolean equalArea(E object1, E object2) {
return object1.getArea() == object2.getArea();
}
}
而无界的泛型类型(unbounded generic type)<E>
等价于 <E extends Object>
。
为类定义泛型类型时, 将泛型类型放在类名后, 例如 GenericStack<E>
, 而为方法定义泛型类型时,将泛型类型放在方法返回类型之前,例如<E> void max(E o1, E o2)
。
原始类型和向后兼容
泛型类如果在使用时不指定具体类型,就成为原始类型(raw type), 使用原始类型的原因是,需要实现向后兼容,因为Java的早期版本不支持泛型。
但是使用原始类型是不安全的。
GenericStack stack = new GenericStack(); // raw type
大致等价于:
GenericStack<Object> stack = new GenericStack<Object>();
public class Max {
/** Return the maximum of two objects */
public static Comparable max(Comparable o1, Comparable o2) {
if (o1.compareTo(o2) > 0)
return o1;
else
return o2;
}
}
o1 和 o2 都被声明为原始类型,但是原始类型不安全,例如,写出这样的代码是有可能的:Max.max("Welcome", 23);
通配符泛型类型
可以使用无界的通配符,有界的通配符或下界的通配符来指定泛型类型的范围。
泛型的擦除及限制
有关泛型的信息由编译器使用,但在运行时不可用。这称为类型擦除。
泛型是使用称为类型擦除的方法实现的:编译器使用泛型类型信息来编译代码,但随后将其擦除。 因此,泛型信息在运行时不可用。 使用此方法,泛型代码能够与使用原始类型的遗产代码向后兼容。
泛型在编译时存在, 一旦编译器确认泛型类型的使用是安全的,它就会将泛型类型转换为原始类型。 例如,编译器检查以下的(a)中的代码是否正确地使用了泛型,然后将其翻译为等效的(b)中的代码以供运行时使用, (b)中的代码使用的是原始类型。…
// (a)
ArrayList<String> list = new ArrayList<>();
list.add("Oklahoma");
String state = list.get(0);
// (b)
ArrayList list = new ArrayList();
list.add("Oklahoma");
String state = (String)(list.get(0));
编译泛型类,接口和方法时,编译器将使用Object
类型替换泛型类型。 例如,编译器会将(a)中的以下方法转换为(b):
// (a)
public static <E> void print(E[] list) {
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
System.out.println();
}
// (b)
public static void print(Object[] list) {
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
System.out.println();
}
如果泛型类型有界,则编译器将其替换为有界类型。 例如,编译器会将下面的(a)中的方法转换为(b)。
// (a)
public static <E extends GeometricObject>
boolean equalArea(E object1, E object2) {
return object1.getArea() == object2.getArea();
}
// (b)
public static boolean equalArea(GeometricObject object1,
GeometricObject object2) {
return object1.getArea() == object2.getArea();
}
值得注意的是,泛型类由其所有实例共享,而不管其实际具体类型如何。 假设list1
和list2
创建如下:
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
虽然ArrayList <String>
和ArrayList <Integer>
在编译时是两种类型,在运行时,只有一个ArrayList
类被加载到JVM中。list1
和list2
都是ArrayList
的实例,因此以下语句显示为true
:
System.out.println(list1 instanceof ArrayList);
System.out.println(list2 instanceof ArrayList);
但是,表达式list1 instanceof ArrayList <String>
是错误的, 由于ArrayList <String>
没有作为单独的类存储在JVM中,因此在运行时使用它是没有意义的。 由于泛型类型在运行时被擦除,因此对泛型类型的使用存在某些限制。 以下是一些限制:
限制 1: 不能使用 new E()
不能使用泛型类型参数创建实例。 例如,以下语句是错误的:
E object = new E(); // Error
原因是new E()
在运行时执行,但泛型类型E
在运行时不可用。
限制 2: 不能使用 new E[]
不能使用泛型类型参数创建数组。 例如,以下语句是错误的:
E[] elements = new E[capacity]; // Error
你可以通过创建Object
类型的数组然后将其转换为E []
来规避此限制,如下所示:
E[] elements = (E[])new Object[capacity];
但是,转换为(E [])
会导致非检查的编译警告。 发生警告是因为编译器不确定在运行时能否成功转换。 例如,如果E
是String
而new Object []
是Integer
对象的数组,(String [])(new Object [])
将导致ClassCastException
。 这种类型的编译警告是Java泛型的限制,是不可避免的。
也不允许使用泛型类创建泛型数组。 例如,以下代码是错误的:
ArrayList<String>[] list = new ArrayList<String>[10];
可以使用以下代码来规避此限制:
ArrayList<String>[] list = (ArrayList<String>[])new ArrayList[10];
但是,你仍会收到编译警告。
限制 3: 静态上下文中不允许使用类的泛型类型参数
因为泛型类的所有实例都具有相同的运行时类,因此泛型类的静态变量和方法将由其所有实例共享。(不是很明白,不能解释为运行时类型被擦除吗?因此不能使用),因此,在静态方法,字段或initializer中引用类的泛型类型参数是非法的。 例如,以下代码是非法的:
public class Test<E> {
public static void m(E o1) { // 非法!
}
public static E o1; // 非法!
static {
E o2; // 非法!
}
}
Restriction 4: 异常类不能是泛型的
泛型类不能继承java.lang.Throwable
,因此以下类声明将是非法的:
public class MyException<T> extends Exception {
}
为什么? 如果允许,你将有一个MyException <T>
的catch
语句块,如下所示:
try {
...
}
catch (MyException<T> ex) {
...
}
JVM必须检查从try
语句块抛出的异常,以查看它是否与catch
语句块中指定的类型匹配。 这是不可能的,因为类型信息在运行时不存在。
对象数组排序:
public class GenericSort {
public static <E extends Comparable<E>> void sort(E[] list) {
E currentMin;
int iMinIndex;
for (int i = 0; i < list.length; i++) {
currentMin = list[i];
iMinIndex = i;
for (int j = i + 1; j < list.length; j++) {
if (currentMin.compareTo(list[j]) > 0) {
currentMin = list[j];
iMinIndex = j;
}
}
if (iMinIndex != i) {
list[iMinIndex] = list[i];
list[i] = currentMin;
}
}
System.out.println("\nObjects:");
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
System.out.println();
}
public static void main(String[] args) {
Integer[] integers = {new Integer(4), new Integer(3), new Integer(2)};
Double[] doubles = {new Double(1.3), new Double(3.4), new Double(-22.1)};
Character[] chars = {new Character('J'), new Character('a'), new Character('r')};
String[] strings = {new String("Susan"), new String("Kim"), new String("Tom")};
sort(integers);
sort(doubles);
sort(chars);
sort(strings);
}
}
[1] Introduction to Java Programming Chapter 19. Generic Types