论文部分内容阅读
摘要:本文阐述了Java中泛型的概念和特点,介绍了简单泛型创建和使用,探讨了泛型应用中存在的陷井。泛型最重要的特点是类型安全,泛型还可以消除类型转换等。泛型的创建和使用比较简单,但在使用中存在一定的陷阱,需要引起程序员的注意。
关键词:泛型;类型安全;类型通配符
中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)06-11612-03
1 前言
1.1泛型概念
Java 程序员经常会遇到使人头疼的问题――将表达式向下类型转换为比其静态类型更为具体的数据类型,这种类型转换给编程人员带来了极大的不便,同时也容易引入错误。比如,集合(Collection)中元素的类型可以是多种多样的,有些元素是Boolean 类型的,而有些则可能是Integer类型的,等等。Java 语言之所以支持多种类型元素的集合,是因为它允许程序员构建一个元素类型为Object 的 Collection。当使用Collection 时,程序员经常要做的一件事情就是强制类型转换,当转换成所需的类型以后,再对它们进行处理。在很多Java 应用中,上述情况非常普遍,为了解决这个问题,使Java 语言变得更加安全好用,Sun公司在其发布的JDK 5.0版本中引入了“泛型”这一新特性。
泛型其实就是参数化类型,也就是把类型作为参数进行定义。泛型可以很好地解决Java中的类型转换失败的问题。我们来看两个例子。
例1:不用泛型技术的例子。
(1)import java.util.ArrayList;
(2)public class MyCollection{
(3)public static void main(String[]args){
(4)ArrayList myStr=new ArrayList();
(5)myStr.add("一个字符串");
(6)String str=(String)myStr.get(0);//强制类型转换
(7)System.out.println(str);
(8) }
(9) }
例2:使用泛型技术的例子。
(1)import java.util.ArrayList;
(2)public class myCollection{
(3)public static void main(String[]args){
(4)ArrayList myStr=new ArrayList();
(5)myStr.add("一个字符串");
(6)String str=myStr.get(0);//没有强制类型转换。
(7)System.out.println(str);
(8) }
(9)}
在例2中,生成了一个只能包含字符串类型的ArrayList。如果你要在这个集合中添加其它类型的对象,哪么编译器会报错。这样,至少不会在运行时出现类型错误,基于大型的应用程序提高了安全性。另外,在从这个集合中取出的对像直接就是String类型,不必进行强类型转换。
1.2泛型的特点
(1)类型安全。 泛型的主要目标是提高Java程序的类型安全。通过使用泛型定义的变量的类型限制,编译器可以在编译时检验类型合法性。如果没有泛型,那么类型的安全性主要由程序员来把握,这显然不如带有泛型的程序安全性高。
由于Java中所有定义的类,都以Object类为顶层类,所以JDK5.0之前,Java程序员为了让定义出来的类可以更加通用,传入的值或返回的实例都是以Object类型为主,当要取出这些实例来使用时,必须记得将之转换为原来的类型或适当的接口,这样才能使用对象上的方法。然而,对于粗心的程序员往往会忘了转换,或者转换类型或接口时用错了类型,但由于语法上是允许的,所以编译器检查不出错误,因而执行时期就会发生ClassCastException。泛型可以将类型检查从运行时挪到编译时,这有助于程序员更容易找到错误,从而提高程序的可靠性。
(2)消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
(3)性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换插入生成的字节码中(如果没有泛型,程序员要指定这些强制类型转换)。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。
(4)擦除。泛型的实现方式采用了“擦除”方式,Java 语言中的泛型基本上完全在编译器中实现,编译器生成的代码与手工编写的不用泛型、检查程序的类型安全后进行强制类型转换所得到的代码基本相同,泛型只是更能确保类型安全。
2 简单泛型的定义和使用
2.1简单泛型类的定义
在JDK1.5以后,Sun提出了针对泛型设计的解决方案,创建一个简单的泛型是非常容易的。首先,在一对尖括号(< >)中声明类型持有者名称,以逗号间隔类型持有者名列表。在类的实例变量和方法中,可以在任何类型的位置使用那些类型持有者名称。下面以一个简单的例子开始这部分的内容介绍。
例3:简单泛型的定义。
(1)import java.util.*;
(2)public class Tree {
(3)V value;
(4)public Tree(V value) {this.value = value; }// 构造方法,标识类型V的使用
(5)V getValue() {return value; }
(6)void setValue(V value) { this.value = value; }
(7) }
类型持有者的命名一般使用一个大写字母,不同字母一般代表不同的含义。如T(Type)、E(Element)、V(Value)、A(Annotation)等。
当一个变量被声明为泛型时,只能被实例变量和方法以及内部类调用,而不能被静态变量和方法调用。
可以通过以下方式来使用泛型类。
Tree s=new Tree("hello");
String str=s.getValue();
System.out.println (str);
2.2 限制泛型可用类型
定义泛型类时,默认可以使用任何类型来实例化泛型类中的类型持有者,但假设想要限制使用泛型类时,如何用某个特定类型或其子类型来实例化类型持有者呢?可以在定义类型持有者时,同时使用extends指定这个类型持有者实例化,实例化的对象必须继承于某个类型或实现某接口。如例3中的Tree中的类型持有者V是不受约束的,Tree可以被参数化为任何类型。如果需要强制一个类型参数实现一个或多个接口,或是一个特定类的子类,可以通过例4方式来实现。
例4:限制泛型可用类型实例。
(1)import java.util.*;
(2)public class Tree> implements Comparable >{
(3)V value;
(4)public Tree(V value) { this.value = value; }
(5)V getValue() { return value; }
(6)void setValue(V value) { this.value = value; }
(7)public int compareTo(Tree that) {
(8)if (this.value == null && that.value == null) return 0;
(9)if (this.value == null) return -1;
(10)if (that.value == null) return 1;
(11)return this.value.compareTo(that.value);
(12) }
(13)}
2.3类型通配符
仍以例3所定义的Tree来进行说明,假设使用Tree类来进行下面的声明:
Tree tree1=null;
Tree tree2=null;
那么名称tree1就只能参考Tree类型的Tree实例,而tree2就只能参考Tree类型的实例。即下面形式是可行的:
tree1=new Tree(new Integer(10));
tree2=new Tree("ten");
如果希望有一个变量tree可以像下面这样接受所指定的实例:
tree=new Tree(new ArrayList());
tree=new Tree(new LinkedList());
即如果想要有一个tree泛型对象,其对象持有者实例化的对象是List接口的类及其子类。要声明这么一个变量,可以使用通配符(?)代表未知类型,并使用extends关键字来修饰。例如:
Tree<? extends List> tree=null;
tree=new Tree(new ArrayList());
…
tree=new Tree(new LinkedList());
<? extends List>表示未知类型,只知道是List接口的类,所以,如果类型持有者实例化的对象不是实现List接口的类,则编译器报告错误。
使用<?>或<? extends SomeClass>的声明方式,意味着只能通过该名称来取得泛型对象变量的信息,或者是删除某些信息,但不能增加它的信息。
3 泛型使用中的陷阱
JDK5.0增加的泛型是对类型安全的一次重大改进,泛型的创建和使用也相当简单,但是对于初次使用泛型类型的用户来说,泛型的某些方面看起来可能不容易明白,甚至非常奇怪,泛型的本身也存在一定的陷井,我们在使用中应该尽量避免。
(1)泛型不是协变(covariant)的
Java语言中的数组是协变的,也就是说,如果Number是Integer的超类型,那么Number[]也是Integer[]的超类型。泛型类型不是协变的,即List是List的超类型,但不能在需要 List 的地方传递 List。数组能够协变而泛型不能协变的另一个后果是,不能实例化泛型类型的数组,也就是说new List[3] 是不合法的,除非类型参数是一个未绑定的通配符,如:new List<?>[3] 是合法的。数组协变会破坏泛型的类型安全,所以不允许实例化泛型类型的数组。
(2)构造延迟
因为可以擦除功能,所以 List 和 List 是同一个类,编译器在编译 List 时只生成一个类。因此,在编译 List 类时,编译器不知道 V 所表示的类型,所以它就不能像知道类所表示的具体类型那样处理 List 类定义中的类型参数(List 中的 V)。
因为运行时不能区分 List 和 List(运行时都是 List),用泛型类型参数标识类型的变量的构造就成了问题。运行时缺乏类型信息,这给泛型容器类和希望创建保护性副本的泛型类提出了难题。
(3)类库的泛化问题
在转化现有的库类来使用泛型方面没有多少技巧,但与平常的情况相同,向后兼容性不会凭空而来,其中向后兼容性限制了类库的泛化。
(4)擦除方式引出的问题
擦除意味着一个类不能同时实现 Comparable 和 Comparable,因为事实上两者都在同一个接口中,指定同一个compareTo()方法。声明 DecimalString 类以便与 String 与 Number比较似乎是明智的,但对于 Java 编译器来说,这相当于对同一个方法进行了两次声明。擦除的另一个后果是,对泛型类型参数是用强制类型转换或者instanceOf 毫无意义。擦除也是造成不能创建泛型类型的对象的原因,因为编译器不知道要调用什么构造函数。
(5)类型受限问题
Java 语言中的泛型不能接受基本类型作为类型参数,它只能接受引用类型。这意味着可以定义 List,但是不可以定义 List。
4 小结
本文阐述了JDK1.5中新增泛型的概念和特点,介绍了简单泛型创建和使用,探讨了泛型应用中存在的陷阱。泛型解决的不只是让程序员少写几个类的程序代码,还在于让程序员定义安全的泛型类。泛型提供编译时期检查,使程序员不会因为将对象置入某个容器而失去其类型。扩展虚拟机指令集来支持泛型被认为是无法接受的,因为这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。
参考资料:
[1]Eric(美).诊断Java代码: 轻松掌握Java泛型[M].developerWorks,2003,5.
[2]Brian Goetz(美).Java 理论和实践:了解泛型[M].developerWorks,2005,1.
[3]Brian Goetz(美).Introduction to generic types in JDK 5.0[M].developerWorks,2004,12.
[4]良葛格.Java学习笔记[M].北京:清华大学出版社,2006.8.
[5]Java 2 Platform Standard Edition 5.0 的 API 规范文档.
本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。
关键词:泛型;类型安全;类型通配符
中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)06-11612-03
1 前言
1.1泛型概念
Java 程序员经常会遇到使人头疼的问题――将表达式向下类型转换为比其静态类型更为具体的数据类型,这种类型转换给编程人员带来了极大的不便,同时也容易引入错误。比如,集合(Collection)中元素的类型可以是多种多样的,有些元素是Boolean 类型的,而有些则可能是Integer类型的,等等。Java 语言之所以支持多种类型元素的集合,是因为它允许程序员构建一个元素类型为Object 的 Collection。当使用Collection 时,程序员经常要做的一件事情就是强制类型转换,当转换成所需的类型以后,再对它们进行处理。在很多Java 应用中,上述情况非常普遍,为了解决这个问题,使Java 语言变得更加安全好用,Sun公司在其发布的JDK 5.0版本中引入了“泛型”这一新特性。
泛型其实就是参数化类型,也就是把类型作为参数进行定义。泛型可以很好地解决Java中的类型转换失败的问题。我们来看两个例子。
例1:不用泛型技术的例子。
(1)import java.util.ArrayList;
(2)public class MyCollection{
(3)public static void main(String[]args){
(4)ArrayList myStr=new ArrayList();
(5)myStr.add("一个字符串");
(6)String str=(String)myStr.get(0);//强制类型转换
(7)System.out.println(str);
(8) }
(9) }
例2:使用泛型技术的例子。
(1)import java.util.ArrayList;
(2)public class myCollection{
(3)public static void main(String[]args){
(4)ArrayList
(5)myStr.add("一个字符串");
(6)String str=myStr.get(0);//没有强制类型转换。
(7)System.out.println(str);
(8) }
(9)}
在例2中,生成了一个只能包含字符串类型的ArrayList。如果你要在这个集合中添加其它类型的对象,哪么编译器会报错。这样,至少不会在运行时出现类型错误,基于大型的应用程序提高了安全性。另外,在从这个集合中取出的对像直接就是String类型,不必进行强类型转换。
1.2泛型的特点
(1)类型安全。 泛型的主要目标是提高Java程序的类型安全。通过使用泛型定义的变量的类型限制,编译器可以在编译时检验类型合法性。如果没有泛型,那么类型的安全性主要由程序员来把握,这显然不如带有泛型的程序安全性高。
由于Java中所有定义的类,都以Object类为顶层类,所以JDK5.0之前,Java程序员为了让定义出来的类可以更加通用,传入的值或返回的实例都是以Object类型为主,当要取出这些实例来使用时,必须记得将之转换为原来的类型或适当的接口,这样才能使用对象上的方法。然而,对于粗心的程序员往往会忘了转换,或者转换类型或接口时用错了类型,但由于语法上是允许的,所以编译器检查不出错误,因而执行时期就会发生ClassCastException。泛型可以将类型检查从运行时挪到编译时,这有助于程序员更容易找到错误,从而提高程序的可靠性。
(2)消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
(3)性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换插入生成的字节码中(如果没有泛型,程序员要指定这些强制类型转换)。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。
(4)擦除。泛型的实现方式采用了“擦除”方式,Java 语言中的泛型基本上完全在编译器中实现,编译器生成的代码与手工编写的不用泛型、检查程序的类型安全后进行强制类型转换所得到的代码基本相同,泛型只是更能确保类型安全。
2 简单泛型的定义和使用
2.1简单泛型类的定义
在JDK1.5以后,Sun提出了针对泛型设计的解决方案,创建一个简单的泛型是非常容易的。首先,在一对尖括号(< >)中声明类型持有者名称,以逗号间隔类型持有者名列表。在类的实例变量和方法中,可以在任何类型的位置使用那些类型持有者名称。下面以一个简单的例子开始这部分的内容介绍。
例3:简单泛型的定义。
(1)import java.util.*;
(2)public class Tree
(3)V value;
(4)public Tree(V value) {this.value = value; }// 构造方法,标识类型V的使用
(5)V getValue() {return value; }
(6)void setValue(V value) { this.value = value; }
(7) }
类型持有者的命名一般使用一个大写字母,不同字母一般代表不同的含义。如T(Type)、E(Element)、V(Value)、A(Annotation)等。
当一个变量被声明为泛型时,只能被实例变量和方法以及内部类调用,而不能被静态变量和方法调用。
可以通过以下方式来使用泛型类。
Tree
String str=s.getValue();
System.out.println (str);
2.2 限制泛型可用类型
定义泛型类时,默认可以使用任何类型来实例化泛型类中的类型持有者,但假设想要限制使用泛型类时,如何用某个特定类型或其子类型来实例化类型持有者呢?可以在定义类型持有者时,同时使用extends指定这个类型持有者实例化,实例化的对象必须继承于某个类型或实现某接口。如例3中的Tree
例4:限制泛型可用类型实例。
(1)import java.util.*;
(2)public class Tree
(3)V value;
(4)public Tree(V value) { this.value = value; }
(5)V getValue() { return value; }
(6)void setValue(V value) { this.value = value; }
(7)public int compareTo(Tree
(8)if (this.value == null && that.value == null) return 0;
(9)if (this.value == null) return -1;
(10)if (that.value == null) return 1;
(11)return this.value.compareTo(that.value);
(12) }
(13)}
2.3类型通配符
仍以例3所定义的Tree来进行说明,假设使用Tree类来进行下面的声明:
Tree
Tree
那么名称tree1就只能参考Tree
tree1=new Tree
tree2=new Tree
如果希望有一个变量tree可以像下面这样接受所指定的实例:
tree=new Tree
tree=new Tree
即如果想要有一个tree泛型对象,其对象持有者实例化的对象是List接口的类及其子类。要声明这么一个变量,可以使用通配符(?)代表未知类型,并使用extends关键字来修饰。例如:
Tree<? extends List> tree=null;
tree=new Tree
…
tree=new Tree
<? extends List>表示未知类型,只知道是List接口的类,所以,如果类型持有者实例化的对象不是实现List接口的类,则编译器报告错误。
使用<?>或<? extends SomeClass>的声明方式,意味着只能通过该名称来取得泛型对象变量的信息,或者是删除某些信息,但不能增加它的信息。
3 泛型使用中的陷阱
JDK5.0增加的泛型是对类型安全的一次重大改进,泛型的创建和使用也相当简单,但是对于初次使用泛型类型的用户来说,泛型的某些方面看起来可能不容易明白,甚至非常奇怪,泛型的本身也存在一定的陷井,我们在使用中应该尽量避免。
(1)泛型不是协变(covariant)的
Java语言中的数组是协变的,也就是说,如果Number是Integer的超类型,那么Number[]也是Integer[]的超类型。泛型类型不是协变的,即List
(2)构造延迟
因为可以擦除功能,所以 List
因为运行时不能区分 List
(3)类库的泛化问题
在转化现有的库类来使用泛型方面没有多少技巧,但与平常的情况相同,向后兼容性不会凭空而来,其中向后兼容性限制了类库的泛化。
(4)擦除方式引出的问题
擦除意味着一个类不能同时实现 Comparable
(5)类型受限问题
Java 语言中的泛型不能接受基本类型作为类型参数,它只能接受引用类型。这意味着可以定义 List
4 小结
本文阐述了JDK1.5中新增泛型的概念和特点,介绍了简单泛型创建和使用,探讨了泛型应用中存在的陷阱。泛型解决的不只是让程序员少写几个类的程序代码,还在于让程序员定义安全的泛型类。泛型提供编译时期检查,使程序员不会因为将对象置入某个容器而失去其类型。扩展虚拟机指令集来支持泛型被认为是无法接受的,因为这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。
参考资料:
[1]Eric(美).诊断Java代码: 轻松掌握Java泛型[M].developerWorks,2003,5.
[2]Brian Goetz(美).Java 理论和实践:了解泛型[M].developerWorks,2005,1.
[3]Brian Goetz(美).Introduction to generic types in JDK 5.0[M].developerWorks,2004,12.
[4]良葛格.Java学习笔记[M].北京:清华大学出版社,2006.8.
[5]Java 2 Platform Standard Edition 5.0 的 API 规范文档.
本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。