1.概述
泛型是JAVA SE 1.5的新特性。
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:
public interface List
extends Collection {
泛型的本质:将操作的数据类型被指定为一个参数。
List list=…
List接口里的get方法是这样的:
T get(int index);
通过泛型,就可以让编译器对类型进行检查
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
2.为什么要使用泛型
理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
1 List box = ...;
2 Apple apple = box.get(0);
上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。
没有泛型,上面的代码需要写成这样:
1 List box = ...;
2 Apple apple = (Apple) box.get(0);
泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。
如果我们试图在List装入另外一种对象,编译器就会提示错误:
list.add(apple); // 不能编译
类型安全的读取数据…
当我们在使用List对象时,它总能保证我们得到的是一个Apple对象:
Apple apple = list.get(0);
3.泛型与Object
没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
举例
public class Gen< T> {
private T ob; //定义泛型成员变量
public Gen( T ob) {
this. ob = ob;
}
public T getOb( ) {
return ob;
}
public void setOb( T ob) {
this. ob = ob;
}
public void showTyep( ) {
System. out. println ( "T的实际类型是: " + ob. getClass ( ) . getName ( ) ) ;
}
}
测试代码
public class GenDemo {
public static void main( String [ ] args) {
//定义泛型类Gen的一个Integer版本
Gen< Integer > intOb= new Gen< Integer > ( 88) ;
intOb. showTyep( ) ;
int i= intOb. getOb( ) ;
System . out. println ( "value= " + i) ;
System . out. println ( "----------------------------------" ) ;
//定义泛型类Gen的一个String版本
Gen< String > strOb= new Gen< String > ( "Hello Gen!" ) ;
strOb. showTyep( ) ;
String s= strOb. getOb( ) ;
System . out. println ( "value= " + s) ;
}
}
运行结果:
T的实际类型是: java.lang.Integer
value= 88
----------------------------------
T的实际类型是: java.lang.String
value= Hello Gen!
未使用泛型举例
public class Gen2 {
private Object ob; //定义一个通用类型成员
public Gen2( Object ob) {
this . ob = ob;
}
public Object getOb( ) {
return ob;
}
public void setOb( Object ob) {
this . ob = ob;
}
public void showTyep( ) {
System . out. println ( "T的实际类型是: " + ob. getClass ( ) . getName ( ) ) ;
}
}
测试代码
public class GenDemo2 {
public static void main( String [ ] args) {
//定义类Gen2的一个Integer版本
Gen2 intOb = new Gen2( new Integer ( 88) ) ;
intOb. showTyep( ) ;
int i = ( Integer ) intOb. getOb( ) ;
System. out. println ( "value= " + i) ;
System. out. println ( "----------------------------------" ) ;
//定义类Gen2的一个String版本
Gen2 strOb = new Gen2( "Hello Gen!" ) ;
strOb. showTyep( ) ;
String s = ( String ) strOb. getOb( ) ;
System. out. println ( "value= " + s) ;
}
}
运行结果:
T的实际类型是: java.lang.Integer
value= 88
----------------------------------
T的实际类型是: java.lang.String
value= Hello Gen!
4.泛型与继承
假设有父类Fruit<-Apple,Strawberry,Apple<-FujiApple
以下代码无效:
List apples;
List fruits = ...;
Fruits= apples;
为什么一箱苹果就不是一箱水果了呢?现在假设以上操作允许,那么下面的操作会出错误
fruits.add(new Strawberry());
一个List中装入了各种不同类型的子类水果,这显然是不可以的,因为我们在取出List中的水果对象时,就分不清楚到底该转型为苹果还是草莓了。
所以,泛型在使用时并不考虑类与类之间的继承关系, List 并不是 List的子类。
5.泛型与通配符?
不使用泛型,打印集合中所有元素的代码
void printCollection( Collection c) {
Iterator i= c. iterator ( ) ;
for ( k= 0; k < c. size ( ) ; k+ + ) {
System . out. println ( i. next ( ) ) ;
}
}
使用泛型,打印集合中所有元素的代码
void printCollection( Collection < Object > c) {
for ( Object e: c) {
System . out. println ( e) ;
}
}
使用泛型的版本只能接受元素类型为Object类型的集合如ArrayList();如果是ArrayList,则会编译时出错。
因为我们前面说过,Collection并不是所有集合的超类。而老版本可以打印任何类型的集合,那么如何改造新版本以便它能接受所有类型的集合呢?这个问题可以通过使用通配符来解决。修改后的代码如下所示:
//使用通配符?,表示可以接收任何元素类型的集合作为参数
void printCollection( Collection < ? > c) {
for ( Object e: c) {
System . out. println ( e) ;
}
}
注意:
通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素 。
Collection < ? > c= new ArrayList < String > ( ) ;
c. add ( newObject( ) ) ; //compile time error,不管加入什么对象都出错,除了null外。
c. add ( null ) ;
另一方面,我们可以从List> lists中获取对象,虽然不知道List中存储的是什么类型,但是可以肯定的是存储的类型一定是Object的子类型,所以可以用Object类型来获取值。如for(Object obj: lists),这是合法的。
6. ?extends通配 符,向上造型一个泛型对象的引用
? extends XX,XX 类是用来限定通配符的上界,XX 类是能匹配的最顶层的类,它只能匹配 XX 类以及 XX 类的子类。
假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次:
public abstract class Shape {
public abstract void draw ( Canvas c) ;
}
public class Circle extends Shape {
private int x, y, radius;
public void draw ( Canvas c) { . . . }
}
public class Rectangle extends Shape
private int x, y, width , height ;
public void draw ( Canvasc) { . . . }
}
为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。
但是不幸的是,我们只能接收元素类型为Shape的List对象,而不能接收类型为List的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。
这里可以采用public void drawAll(List extends Shape> shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。
//原始版本
public void drawAll( List < Shape > shapes) {
for ( Shapes: shapes) {
s. draw ( this ) ;
}
}
//使用边界通配符的版本
public void drawAll( List < ?exends Shape > shapes) {
for ( Shapes: shapes) {
s. draw ( this ) ;
}
}
注意:
最好不要这样定义变量List<?exends Shape> list,因为以下语句会出错
list.add(0, new Rectangle()); //compile-time error
原因在于:我们只知道list中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚
但是, 以下操作是可行的
List apples = new ArrayList();
List extends Fruit> fruits = apples;
7.? super通配符,向下造型一个泛型对象的引用
? super XX,XX 类是用来限定通配符的下界,XX 类是能匹配的最底层的类,它只能匹配 XX 类及子类。
List < Shape > shapes = new ArrayList < Shape > ( ) ;
List < ? super Cicle> cicleSupers = shapes;
cicleSupers. add ( new Cicle( ) ) ; //OK, subclass of Cicle also OK
cicleSupers. add ( new Shape ( ) ) ;//Error
这表示cicleSupers列表存储的元素为Cicle的超类,因此我们可以往其中加入Cicle对象或者Cicle的子类对象,但是不能加入Shape对象 。
? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符。
8.泛型方法
考虑实现一个方法,该方法拷贝一个数组中的所有对象到集合中。下面是初始的版本:
static void fromArrayToCollection( Object [ ] a, Collection < ? > c) {
for ( Object o: a) {
c. add ( o) ; //compile time error
}
}
可以看到显然会出现编译错误,原因在之前有讲过,因为集合c中的类型未知,所以不能往其中加入任何的对象(当然,null除外)。解决该问题的一种比较好的办法是使用泛型方法,如下所示:
static < T> void fromArrayToCollection( T[ ] a, Collection < T> c) {
for ( T o : a) {
c. add ( o) ; // correct
}
}
注意:泛型方法的格式,类型参数 需要放在函数返回值之前。
调用举例
Number[] na = new Number[100];
Collection cn = new ArrayList();
fromArrayToCollection(ia, cn);
在某些情况下需要指定传递类型参数,比如当存在与泛型方法相同的方法的时候(方法参数类型不一致),如下面的一个例子:
public < T> void go( T t) {
System . out. println ( "generic function" ) ;
}
public void go( String str) {
System . out. println ( "normal function" ) ;
}
public static void main( String [ ] args) {
FuncGenric fg = new FuncGenric( ) ;
fg. go( "haha" ) ; //打印normal function
fg. < String > go( "haha" ) ; //打印generic function
fg. go( new Object ( ) ) ; //打印generic function
fg. < Object > go( new Object ( ) ) ; //打印generic function
}
当不指定类型参数时,调用的是普通的方法,如果指定了类型参数,则调用泛型方法。
深入学习泛型,再看文献《 Effective Java (泛型) 》
参考文献
1.Java泛型编程最全总结.http://qiemengdao.iteye.com/blog/1525624
2.java 泛型 深入.http://www.blogjava.net/fancydeepin/archive/2012/08/25/386241.html
3.Java泛型简明教程.
阅读(5246) | 评论(0) | 转发(0) |