“泛型”是在JDK1.5之后才引入的,那么到底什么是泛型?什么情况下需要使用泛型?它解决了什么问题?
一、为了解决什么?
【引子示例】来看这样一个例子,在JDK1.5之前,java.lang.Comparable接口是这样定义的:
1
2
3
4
5
|
package
java.lang;
public
interface
Comparable{
public
int
compareTo(Object o);
}
|
我们可以这样使用:
1
2
|
Comparable c =
new
Integer(
1
);
System.out.println(c.compareTo(
2
));
|
输出的结果是:-1,因为很明显1<2。
【潜在问题】
这里,Comparable接口的compareTo方法定义还不够完美。考虑下,为什么?
关键在于传入参数类型定义为Object类!可以想象的是当初写Comparable接口的作者考虑到“通用性”的问题,将传入参数类型定义所有类型的父类——Object类,这意味着实现该接口的类根据实际需求定义特定的传入参数类型。比如,在实现了该接口的java.lang.Integer类中,compareTo方法是这样定义:
1
2
3
|
public
int
compareTo(Integer anotherInteger) {
//这里你无需了解具体实现
}
|
这意味着,对Integer对象调用compareTo方法时,你只能传入Integer类型来完成“比较”。如果你强行传入一个非Integer类型呢?如:
1
2
|
Comparable c =
new
Integer(
1
);
System.out.println(c.compareTo(
"mok"
));
|
能编译通过!但是运行时会报错:Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer.
对于一个程序来说,如果能在编译时就报告错误是不是要比产生运行时错误要可靠呢?答案是肯定的。这就是为了兼顾“通用性”使用父类类型却带来的运行时错误这样的“不能兼得”的“不完美”情况。怎么解决呢?泛型!
二、怎么解决?
引入泛型后,即JDK1.5之后,Comparable是这样定义:
1
2
3
4
5
6
7
8
|
/**
*代码4
*/
package
java.lang;
public
interface
Comparable<T>{
public
int
compareTo(T o);
}
|
可以这样来使用:
1
2
3
4
5
6
|
/**
*代码5
*/
Comparable<Integer> c =
new
Integer(
1
);
System.out.println(c.compareTo(
2
));
//编译通过,运行结果为-1
System.out.println(c.compareTo(
"mok"
));
//无法编译通过!
|
可以看到,在声明Comparable时,我们用具体的Integer类“替代”了定义中的“T”所在的位置。在这里<T>表示形式泛型类型(formal generic type),而Integer则是实际具体类型(actual concrete type)。而在代码4Comparable类的定义中,compareTo的传入参数类型同样使用了“T”,这意味着对泛型类型进行声明使用时如果指定了实际具体类型(如此处的Integer),编译器会在编译时自动检查所有泛型类型并使用你声明时指定的实际具体类型来替换。你可以简单地看做,在编译代码5时,编译器会将你的Comparable类编译为如下:
1
2
3
4
5
6
7
8
9
|
/**
*代码6
*/
package
java.lang;
public
interface
Comparable<Integer>{
public
int
compareTo(Integer o);
}
//注:当然,实际应该是字节码且还涉及到Integer到Comparable的“上溯造型”,故本段只是用于帮助理解不具备实际意义
|
也就是代码5中对象c调用的是参数类型为Integer的compareTo方法,故传入其他参数(如此处的String类“mok”)并无法通过编译。
三、总结
-
使用泛型能定义带泛型类型的类或方法,之后编译器会用具体类型来替换泛型类型。
-
泛型的主要作用是能够在编译时而不是运行时发现错误。