- 浏览: 2990111 次
- 性别:
- 来自: 河南
文章分类
- 全部博客 (340)
- Java综合 (26)
- 程序人生 (53)
- RIA-ExtJS专栏 (18)
- RIA-mxGraph专栏 (4)
- RIA-Flex4专栏 (43)
- 框架-Spring专栏 (16)
- 框架-持久化专栏 (22)
- 框架-Struts2专栏 (11)
- 框架-Struts专栏 (12)
- SQL/NOSQL (12)
- 报表/图表 (2)
- 工作流 (5)
- XML专栏 (4)
- 日常报错解决方案 (5)
- Web前端-综合 (12)
- Web/JSP (14)
- Web前端-ajax专栏 (14)
- Web前端-JQuery专栏 (9)
- IDE技巧 (6)
- FILE/IO (14)
- 远程服务调用 (2)
- SSO单点登录 (2)
- 资源分享 (22)
- 云计算 (1)
- 项目管理 (3)
- php专栏 (1)
- Python专栏 (2)
- Linux (1)
- 缓存系统 (1)
- 队列服务器 (1)
- 网络编程 (0)
- Node.js (1)
最新评论
-
hui1989106a:
我的也不能解压,360和好压都试了,都不行
《Spring in Action》完整中文版分享下载 -
temotemo:
这些example有些过时了,官方建议使用HBase-1.0 ...
Java操作Hbase进行建表、删表以及对数据进行增删改查,条件查询 -
zy8102:
非常感谢~
HeadFirst系列之七:《深入浅出SQL》原版高清PDF电子书分享下载 -
zy8102:
重命名了一下搞定了
HeadFirst系列之七:《深入浅出SQL》原版高清PDF电子书分享下载 -
zy8102:
为什么下载以后老解压不了呢?
HeadFirst系列之七:《深入浅出SQL》原版高清PDF电子书分享下载
Java泛型简明教程
本文是从 Java Generics Quick Tutorial 这篇文章翻译而来。
泛型是Java SE 5.0中引入的一项特征,自从这项语言特征出现多年来,我相信,几乎所有的Java程序员不仅听说过,而且使用过它。关于Java泛型的教程,免费的,不免费的,有很多。我遇到的最好的教材有:
- The Java Tutorial
- Java Generics and Collections , by Maurice Naftalin and Philip Wadler
- Effective Java中文版(第2版) , by Joshua Bloch.
尽管有这么多丰富的资料,有时我感觉,有很多的程序员仍然不太明白Java泛型的功用和意义。这就是为什么我想使用一种最简单的形式来总结一下程序员需要知道的关于Java泛型的最基本的知识。
Java泛型由来的动机
理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
1
|
List<Apple> box = ...;
|
2
|
Apple apple = box.get(
0
);
|
上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:
1
|
List box = ...;
|
2
|
Apple apple = (Apple) box.get(
0
);
|
很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。
泛型的构成
由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:
- 泛型类声明
- 泛型接口声明
- 泛型方法声明
- 泛型构造器(constructor)声明
泛型类和接口
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:
1
|
public
interface
List<T>
extends
Collection<T> {
|
2
|
...
|
3
|
}
|
简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。
Java类库里的很多类,例如整个Collection框架都做了泛型化的修改。例如,我们在上面的第一段代码里用到的List接口就是一个泛型 类。在那段代码里,box是一个List<Apple>对象,它是一个带有一个Apple类型变量的List接口的类实现的实例。编译器使用 这个类型变量参数在get方法被调用、返回一个Apple对象时自动对其进行类型转换。
实际上,这新出现的泛型标记,或者说这个List接口里的get方法是这样的:
1
|
T get(
int
index);
|
get方法实际返回的是一个类型为T的对象,T是在List<T>声明中的类型变量。
泛型方法和构造器(Constructor)
非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化。
1
|
public
static
<t> T getFirst(List<T> list)
|
这个方法将会接受一个List<T>类型的参数,返回一个T类型的对象。
例子
你既可以使用Java类库里提供的泛型类,也可以使用自己的泛型类。
类型安全的写入数据…
下面的这段代码是个例子,我们创建了一个List<String>实例,然后装入一些数据:
1
|
List<String> str =
new
ArrayList<String>();
|
2
|
str.add(
"Hello "
);
|
3
|
str.add(
"World."
);
|
如果我们试图在List<String>装入另外一种对象,编译器就会提示错误:
1
|
str.add(
1
);
// 不能编译
|
类型安全的读取数据…
当我们在使用List<String>对象时,它总能保证我们得到的是一个String对象:
1
|
String myString = str.get(
0
);
|
遍历
类库中的很多类,诸如Iterator<T>,功能都有所增强,被泛型化。List<T>接口里的iterator()方 法现在返回的是Iterator<T>,由它的T next()方法返回的对象不需要再进行类型转换,你直接得到正确的类型。
1
|
for
(Iterator<String> iter = str.iterator(); iter.hasNext();) {
|
2
|
String s = iter.next();
|
3
|
System.out.print(s);
|
4
|
}
|
使用foreach
“for each”语法同样受益于泛型。前面的代码可以写出这样:
1
|
for
(String s: str) {
|
2
|
System.out.print(s);
|
3
|
}
|
这样既容易阅读也容易维护。
自动封装(Autoboxing)和自动拆封(Autounboxing)
在使用Java泛型时,autoboxing/autounboxing这两个特征会被自动的用到,就像下面的这段代码:
1
|
List<Integer> ints =
new
ArrayList<Integer>();
|
2
|
ints.add(
0
);
|
3
|
ints.add(
1
);
|
4
|
5
|
int
sum =
0
;
|
6
|
for
(
int
i : ints) {
|
7
|
sum += i;
|
8
|
}
|
然而,你要明白的一点是,封装和解封会带来性能上的损失,所有,通用要谨慎的使用。
子类型
在Java中,跟其它具有面向对象类型的语言一样,类型的层级可以被设计成这样:
在Java中,类型T的子类型既可以是类型T的一个扩展,也可以是类型T的一个直接或非直接实现(如果T是一个接口的话)。因为“成为某类型的子类型”是一个具有传递性质的关系,如果类型A是B的一个子类型,B是C的子类型,那么A也是C的子类型。在上面的图中:
- FujiApple(富士苹果)是Apple的子类型
- Apple是Fruit(水果)的子类型
- FujiApple(富士苹果)是Fruit(水果)的子类型
所有Java类型都是Object类型的子类型。
B类型的任何一个子类型A都可以被赋给一个类型B的声明:
1
|
Apple a = ...;
|
2
|
Fruit f = a;
|
泛型类型的子类型
如果一个Apple对象的实例可以被赋给一个Fruit对象的声明,就像上面看到的,那么,List<Apple> 和 a List<Fruit>之间又是个什么关系呢?更通用些,如果类型A是类型B的子类型,那C<A> 和 C<B>之间是什么关系?
答案会出乎你的意料:没有任何关系。用更通俗的话,泛型类型跟其是否子类型没有任何关系。
这意味着下面的这段代码是无效的:
1
|
List<Apple> apples = ...;
|
2
|
List<Fruit> fruits = apples;
|
下面的同样也不允许:
1
|
List<Apple> apples;
|
2
|
List<Fruit> fruits = ...;
|
3
|
apples = fruits;
|
为什么?一个苹果是一个水果,为什么一箱苹果不能是一箱水果?
在某些事情上,这种说法可以成立,但在类型(类)封装的状态和操作上不成立。如果把一箱苹果当成一箱水果会发生什么情况?
1
|
List<Apple> apples = ...;
|
2
|
List<Fruit> fruits = apples;
|
3
|
fruits.add(
new
Strawberry());
|
如果可以这样的话,我们就可以在list里装入各种不同的水果子类型,这是绝对不允许的。
另外一种方式会让你有更直观的理解:一箱水果不是一箱苹果,因为它有可能是一箱另外一种水果,比如草莓(子类型)。
这是一个需要注意的问题吗?
应该不是个大问题。而程序员对此感到意外的最大原因是数组和泛型类型上用法的不一致。对于泛型类型,它们和类型的子类型之间是没什么关系的。而对于数组,它们和子类型是相关的:如果类型A是类型B的子类型,那么A[]是B[]的子类型:
1
|
Apple[] apples = ...;
|
2
|
Fruit[] fruits = apples;
|
可是稍等一下!如果我们把前面的那个议论中暴露出的问题放在这里,我们仍然能够在一个apple类型的数组中加入strawberrie(草莓)对象:
1
|
Apple[] apples =
new
Apple[
1
];
|
2
|
Fruit[] fruits = apples;
|
3
|
fruits[
0
] =
new
Strawberry();
|
这样写真的可以编译,但是在运行时抛出ArrayStoreException 异常。因为数组的这特点,在存储数据的操作上,Java运行时需要检查类型的兼容性。这种检查,很显然,会带来一定的性能问题,你需要明白这一点。
重申一下,泛型使用起来更安全,能“纠正”Java数组中这种类型上的缺陷。
现在估计你会感到很奇怪,为什么在数组上会有这种类型和子类型的关系,我来给你一个《Java Generics and Collections》 这本书上给出的答案:如果它们不相关,你就没有办法把一个未知类型的对象数组传入一个方法里(不经过每次都封装成Object[]),就像下面的:
1
|
void
sort(Object[] o);
|
泛型出现后,数组的这个个性已经不再有使用上的必要了(下面一部分我们会谈到这个),实际上是应该避免使用。
通配符
在本文的前面的部分里已经说过了泛型类型的子类型的不相关性。但有些时候,我们希望能够像使用普通类型那样使用泛型类型:
- 向上造型一个泛型对象的引用
- 向下造型一个泛型对象的引用
向上造型一个泛型对象的引用
例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将C<A>类型的实例赋给一个C<B>类型的声明。
为了完成这种操作,我们需要使用带有通配符的扩展声明,就像下面的例子里那样:
1
|
List<Apple> apples =
new
ArrayList<Apple>();
|
2
|
List<?
extends
Fruit> fruits = apples;
|
“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List<Apple> 是 List<? extends Fruit> 的子类型。
向下造型一个泛型对象的引用
现在我来介绍另外一种通配符:? super。如果类型B是类型A的超类型(父类型),那么C<B> 是 C<? super A> 的子类型:
1
|
List<Fruit> fruits =
new
ArrayList<Fruit>();
|
2
|
List<?
super
Apple> = fruits;
|
为什么使用通配符标记能行得通?
原理现在已经很明白:我们如何利用这种新的语法结构?
? extends
让我们重新看看这第二部分使用的一个例子,其中谈到了Java数组的子类型相关性:
1
|
Apple[] apples =
new
Apple[
1
];
|
2
|
Fruit[] fruits = apples;
|
3
|
fruits[
0
] =
new
Strawberry();
|
就像我们看到的,当你往一个声明为Fruit数组的Apple对象数组里加入Strawberry对象后,代码可以编译,但在运行时抛出异常。
现在我们可以使用通配符把相关的代码转换成泛型:因为Apple是Fruit的一个子类,我们使用? extends 通配符,这样就能将一个List<Apple>对象的定义赋到一个List<? extends Fruit>的声明上:
1
|
List<Apple> apples =
new
ArrayList<Apple>();
|
2
|
List<?
extends
Fruit> fruits = apples;
|
3
|
fruits.add(
new
Strawberry());
|
这次,代码就编译不过去了!Java编译器会阻止你往一个Fruit list里加入strawberry。在编译时我们就能检测到错误,在运行时就不需要进行检查来确保往列表里加入不兼容的类型了。即使你往list里加入Fruit对象也不行:
1
|
fruits.add(
new
Fruit());
|
你没有办法做到这些。事实上你不能够往一个使用了? extends的数据结构里写入任何的值。
原因非常的简单,你可以这样想:这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类 型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:
1
|
Fruit get = fruits.get(
0
);
|
? super
使用 ? super 通配符一般是什么情况?让我们先看看这个:
1
|
List<Fruit> fruits =
new
ArrayList<Fruit>();
|
2
|
List<?
super
Apple> = fruits;
|
我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道 Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:
1
|
fruits.add(
new
Apple());
|
2
|
fruits.add(
new
GreenApple());
|
如果我们想往里面加入Apple的超类,编译器就会警告你:
1
|
fruits.add(
new
Fruit());
|
2
|
fruits.add(
new
Object());
|
因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。
从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。
存取原则和PECS法则
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
- 如果你想从一个数据类型里获取数据,使用 ? extends 通配符
- 如果你想把对象写入一个数据结构里,使用 ? super 通配符
- 如果你既想存,又想取,那就别用通配符。
这就是Maurice Naftalin在他的《Java Generics and Collections》 这本书中所说的存取原则,以及Joshua Bloch在他的《Effective Java》 这本书中所说的PECS法则。
Bloch提醒说,这PECS是指”Producer Extends, Consumer Super”,这个更容易记忆和运用。
发表评论
-
eclipse版本发布图
2013-11-06 21:11 9534作为一个java开发者, ... -
JAVA版本发布图
2013-08-30 15:15 2285整理修改了下JAVA版本发布图如下: 从这个表中我们可 ... -
HttpClient之Get请求和Post请求示例
2013-04-01 15:10 41397HttpClient的支持在HTTP/1.1规范中定义的所 ... -
byte[]与各种数据类型互相转换示例
2013-04-01 15:00 4147在socket开发过程中,通常需要将一些具体的值(这些值可 ... -
Jackson优化使用实例
2012-12-11 18:20 7155JSON的三种处理方式 Jackson提供了三种可选的JS ... -
Java日志管理:Logger.getLogger()和LogFactory.getLog()的区别(详解Log4j)
2011-07-29 10:28 62972第一、Logger.getLogger()和LogFact ... -
什么是REST?
2011-06-03 18:01 1713什么是REST? 本文是从 What ... -
理解JSON:3分钟课程
2011-06-01 18:26 1812本文是从 Understanding JSON: the ... -
公告:博客今后要长期免费提供JAVA电子书下载喽
2011-04-28 16:06 2996JavaEye各位童鞋,自五一过后,我的博客将长期提供免费的J ... -
几种常用的Java数据源解决方案
2010-10-25 09:39 4439Java中的数据源就是javax.sql.DataSourc ... -
配置ANT
2010-10-22 17:20 2146下载 http://www.apache.org/dist/ ... -
JAVA开发中相对路径,绝对路径全面总结
2010-09-10 09:55 45111.基本概念的理解 绝对路径:绝对路径就是你的主页上的文 ... -
JNDI全面总结
2010-09-09 14:49 78229原理: 在DataSource中事先建立多 ... -
Java关键字synchronized详解
2010-09-09 11:28 25585synchronized 关键 ... -
JAVA利用properties实现的一个小功能
2010-08-04 16:59 2393这次说的小功能呢,其实是跟项目中用到的知识有关,虽然对别人可能 ... -
让机器猫来测试下你的浏览器
2010-07-21 11:40 2189本页面用于测试各个浏览器对CSS3的解释效果,文章中的机器猫“ ... -
Quartz使用示例总结
2010-05-25 22:56 27882任务调度在目前的JAVA应用程序中运用的十分普遍,故掌握Q ... -
TimerTask定时任务使用示例
2010-05-23 17:44 2804public class TimerTaskTest ex ... -
52 个超实用网站
2010-04-29 21:29 1610商务 GenBook : 帮助你的客户安排 ... -
Java源代码的折行规则
2010-04-29 00:24 1358一些企业在招聘程序员的时候,总会特意提出一个要求,即要求具 ...
相关推荐
Java泛型编程指南.pdf 此文章译自SUN的泛型编程指南
Java Generics and Collections 英文版,详细描述java 泛型技术
JAVA泛型教程(帮你解决学习泛型的苦恼). Java 泛型编程可能会碰到很多问题,本教程可能会对你有帮助哦。
Java泛型简明教程 泛型是Java SE 5.0中引入的一项特征,自从这项语言特征出现多年来,我相信,几乎所有的Java程序员不仅听说过,而且使用过它。关于Java泛型的教程,免费的,不免费的,有很多。我遇到的最好的教材有...
这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...
java 泛型接口示例 java 泛型接口示例 java 泛型接口示例
java 泛型类的类型识别示例 java 泛型类的类型识别示例 java 泛型类的类型识别示例
java 泛型方法使用示例 java 泛型方法使用示例 java 泛型方法使用示例
1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....
java泛型技术之发展,学习JAVA 泛型的不错东东
4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip...
主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
深入理解java泛型,包括类名泛型的定义,方法泛型定义,泛型的返回
java,学习java泛型,java培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptx
很好的Java泛型的总结,看完之后你一定会知道java泛型的底层机制,你一定会学会Java泛型!
java泛型详解.pdf
思维导图之Java泛型详解
Java泛型使用详细分析.pdf