原文地址http://www.tuicool.com/articles/iyEbquE
1.1. 开篇
在Java的世界,有很多(成千上万)开源的框架,有成功的,也有不那么成功的,有声名显赫的,也有默默无闻的。在我看来,成功而默默无闻的那些框架值得我们格外的尊敬和关注,Jakarta Commons就是这样的一个框架。如果你至少参与了一个中型规模的Java项目,那么我想有超过一大半的机会你都接触和使用到了Jakarta Commons,不管你自己有没有察觉。就我所知,除了Apache Jakarta其他许多开源框架之外,不少所谓的商业框架其实内部有些模块是借用Commons的,甚至有一些完全就是对Commons的类进行了简单的封装。如果真的没有接触过也不要紧,当你看到它时,你自然会被它的简单而强大所吸引。
要提高Java编程水平,一条可以走的路就是学习优秀的开源框架。这又要分两个层面:应用层面和源码层面。从应用来说,开源的框架大都可以给你带来生产力和/或代码质量的大幅提升;从源码来说,Java开源框架,尤其是那些大型的优秀的框架,其源码对广大Java爱好者来说都是一笔巨大的财富,你可以从中学到许多课本上学不到的东西:编码习惯、代码组织、注释、文档、如何用Java解决实际问题、特定问题的算法,等等。而这些对于我们的作为软件开发者的实际工作而言,相当有意义。
熟悉Jakarta Commons的朋友可能会觉得现在是不是有点过时,因为有很多功能在J2SE 5.0中已经包含了。其实这个问题看你怎么去看,一方面,J2SE 5.0毕竟是刚出现不久的Java版本,实际应用中,很多时候我们需要把代码兼容等级维持在1.3或者1.4,所以很多5.0的功能我们暂时还不能放开手脚去使用;另一方面,鉴于Jakarta在一定程度上反映了一线Java开发人员的实际需求,而目前5.0已经采纳了其中许多特性,我们当然也有理由相信未来的Java版本还会继续参照Jakarta Commons的内容。有这么一套开发源码、免费使用、商业友好的优秀API作为Java自带API的补充,何乐而不为呢?
我打算在近期陆续做一些Jakarta Commons的学习笔记放上来,供大家参考。
有关Jakarta的最新动态和详细信息,可以参考:
http://jakarta.apache.org/commons/index.html
1.2.简介
Apache Commons是一个非常有用的工具包,解决各种实际的通用问题,下面是一个简述表,详细信息访问 http://jakarta.apache.org/commons/index.html
BeanUtils
Commons-BeanUtils 提供对 Java 反射和自省API的包装
Betwixt
Betwixt提供将 JavaBean 映射至 XML 文档,以及相反映射的服务.
Chain
Chain 提供实现组织复杂的处理流程的“责任链模式”.
CLI
CLI 提供针对命令行参数,选项,选项组,强制选项等的简单API.
Codec
Codec 包含一些通用的编码解码算法。包括一些语音编码器, Hex, Base64, 以及URL encoder.
Collections
Commons-Collections 提供一个类包来扩展和增加标准的 Java Collection框架
Configuration
Commons-Configuration 工具对各种各式的配置和参考文件提供读取帮助.
Daemon
一种 unix-daemon-like java 代码的替代机制
DBCP
Commons-DBCP 提供数据库连接池服务
DbUtils
DbUtils 是一个 JDBC helper 类库,完成数据库任务的简单的资源清除代码.
Digester
Commons-Digester 是一个 XML-Java对象的映射工具,用于解析 XML配置文件.
Discovery
Commons-Discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。.
EL
Commons-EL 提供在JSP2.0规范中定义的EL表达式的解释器.
FileUpload
FileUpload 使得在你可以在应用和Servlet中容易的加入强大和高性能的文件上传能力
HttpClient
Commons-HttpClient 提供了可以工作于HTTP协议客户端的一个框架.
IO
IO 是一个 I/O 工具集
Jelly
Jelly是一个基于 XML 的脚本和处理引擎。 Jelly 借鉴了 JSP 定指标签,Velocity, Cocoon和Xdoclet中的脚本引擎的许多优点。Jelly 可以用在命令行, Ant 或者 Servlet之中。
Jexl
Jexl是一个表达式语言,通过借鉴来自于Velocity的经验扩展了JSTL定义的表达式语言。.
JXPath
Commons-JXPath 提供了使用Xpath语法操纵符合Java类命名规范的 JavaBeans的工具。也支持 maps, DOM 和其他对象模型。.
Lang
Commons-Lang 提供了许多许多通用的工具类集,提供了一些java.lang中类的扩展功能
Latka
Commons-Latka 是一个HTTP 功能测试包,用于自动化的QA,验收和衰减测试.
Launcher
Launcher 组件是一个交叉平台的Java 应用载入器。Commons-launcher 消除了需要批处理或者Shell脚本来载入Java 类。.原始的 Java 类来自于Jakarta Tomcat 4.0 项目
Logging
Commons-Logging 是一个各种 logging API实现的包裹类.
Math
Math 是一个轻量的,自包含的数学和统计组件,解决了许多非常通用但没有及时出现在Java标准语言中的实践问题.
Modeler
Commons-Modeler 提供了建模兼容JMX规范的Mbean的机制.
Net
Net 是一个网络工具集,基于 NetComponents 代码,包括 FTP 客户端等等。
Pool
Commons-Pool 提供了通用对象池接口,一个用于创建模块化对象池的工具包,以及通常的对象池实现.
Primitives
Commons-Primitives提供了一个更小,更快和更易使用的对Java基本类型的支持。当前主要是针对基本类型的 collection。.
Validator
The commons-validator提供了一个简单的,可扩展的框架来在一个XML文件中定义校验器 (校验方法)和校验规则。支持校验规则的和错误消息的国际化。
1.3. Commons Lang
跟java.lang这个包的作用类似,Commons Lang这一组API也是提供一些基础的、通用的操作和处理,如自动生成toString()的结果、自动实现hashCode()和equals()方法、数组操作、枚举、日期和时间的处理等等。目前这组API的版本是2.1,下载地址如下:
http://apache.justdn.org/jakarta/commons/lang/binaries/commons-lang-2.1.zip
http://apache.justdn.org/jakarta/commons/lang/source/commons-lang-2.1-src.zip
其中后一个是源代码。
这一组API的所有包名都以org.apache.commons.lang开头,共有如下8个包:
org.apache.commons.lang
org.apache.commons.lang.builder
org.apache.commons.lang.enum
org.apache.commons.lang.enums
org.apache.commons.lang.exception
org.apache.commons.lang.math
org.apache.commons.lang.mutable
org.apache.commons.lang.time
其中的lang.enum已不建议使用,替代它的是紧随其后的lang.enums包。 lang包主要是一些可以高度重用的Util类;lang.builder包包含了一组用于产生每个Java类中都常使用到的toString()、hashCode()、equals()、compareTo()等等方法的构造器;lang.enums包顾名思义用于处理枚举;lang.exception包用于处理Java标准API中的exception,为1.4之前版本提供Nested Exception功能;lang.math包用于处理数字;lang.mutable用于包装值型变量;lang.time包提供处理日期和时间的功能。
由于Commons的包和类实在很多,不可能一个一个讲了,在接下来的专题文章中我就只分别过一下lang、lang.builder、lang.math和lang.time这几个包和常见的用法,其他的我们可以在用到时临时参考一下Javadoc。位置就在安装路径的
…\commons-lang-2.1\docs\api\index.html
我们首先来看org.apache.commons.lang包,这个包提供了一些有用的包含static方法的Util类。除了6个Exception类和2个已经deprecated的数字类之外,commons.lang包共包含了17个实用的类:
ArrayUtils – 用于对数组的操作,如添加、查找、删除、子数组、倒序、元素类型转换等;
BitField – 用于操作位元,提供了一些方便而安全的方法;
BooleanUtils – 用于操作和转换 boolean 或者 Boolean 及相应的数组;
CharEncoding – 包含了 Java 环境支持的字符编码,提供是否支持某种编码的判断;
CharRange – 用于设定字符范围并做相应检查;
CharSet – 用于设定一组字符作为范围并做相应检查;
CharSetUtils – 用于操作 CharSet ;
CharUtils – 用于操作 char 值和 Character 对象;
ClassUtils – 用于对 Java 类的操作,不使用反射;
ObjectUtils – 用于操作 Java 对象,提供 null 安全的访问和其他一些功能;
RandomStringUtils – 用于生成随机的字符串;
SerializationUtils – 用于处理对象序列化,提供比一般 Java 序列化更高级的处理能力;
StringEscapeUtils – 用于正确处理转义字符,产生正确的 Java 、 JavaScript 、 HTML 、 XML 和 SQL 代码;
StringUtils – 处理 String 的核心类,提供了相当多的功能;
SystemUtils – 在 java.lang.System 基础上提供更方便的访问,如用户路径、 Java版本、时区、操作系统等判断;
Validate – 提供验证的操作,有点类似 assert 断言;
WordUtils – 用于处理单词大小写、换行等。
接下来我准备用两个例子来分别说明ArrayUtils和StringUtils的常见用法。
1.3.1. org.apache.commons.lang.ArrayUtils
数组是我们经常需要使用到的一种数据结构,但是由于Java本身并没有提供很好的API支持,使得很多操作实际上做起来相当繁琐,以至于我们实际编码中甚至会不惜牺牲性能去使用Collections API,用Collection当然能够很方便的解决我们的问题,但是我们一定要以性能为代价吗?ArrayUtils帮我们解决了处理类似情况的大部分问题。来看一个例子:
package sean.study.jakarta.commons.lang;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
public class ArrayUtilsUsage {
public static void main(String[] args) {
// data setup
int [] intArray1 = { 2, 4, 8, 16 };
int [][] intArray2 = { { 1, 2 }, { 2, 4 }, { 3, 8}, { 4, 16 } };
Object[][] notAMap = {
{ "A" , new Double(100) },
{ "B" , new Double(80) },
{ "C" , new Double(60) },
{ "D" , new Double(40) },
{ "E" , new Double(20) }
};
// printing arrays
System.out.println( "intArray1: " +ArrayUtils.toString(intArray1));
System.out.println( "intArray2: " +ArrayUtils.toString(intArray2));
System.out.println( "notAMap: " +ArrayUtils.toString(notAMap));
// finding items
System.out.println( "intArray1 contains '8'? "
+ ArrayUtils.contains(intArray1,8));
System.out.println( "intArray1 index of '8'? "
+ ArrayUtils.indexOf(intArray1,8));
System.out.println( "intArray1 last index of '8'? "
+ArrayUtils.lastIndexOf(intArray1, 8));
// cloning and resversing
int[] intArray3 =ArrayUtils.clone(intArray1);
System.out.println( "intArray3: " +ArrayUtils.toString(intArray3));
ArrayUtils.reverse(intArray3);
System.out.println( "intArray3 reversed: "
+ArrayUtils.toString(intArray3));
// primitive to Object array
Integer[] integerArray1 =ArrayUtils.toObject(intArray1);
System.out.println( "integerArray1: "
+ArrayUtils.toString(integerArray1));
// build Map from two dimensional array
Map map = ArrayUtils.toMap(notAMap);
Double res = (Double) map.get( "C" );
System.out.println( "get 'C' from map: " + res);
}
}
以下是运行结果:
intArray1:{2,4,8,16}
intArray2:{{1,2},{2,4},{3,8},{4,16}}
notAMap:{{A,100.0},{B,80.0},{C,60.0},{D,40.0},{E,20.0}}
intArray1 contains'8'? true
intArray1 index of'8'? 2
intArray1 last indexof '8'? 2
intArray3:{2,4,8,16}
intArray3 reversed:{16,8,4,2}
integerArray1:{2,4,8,16}
get 'C' from map:60.0
这段代码说明了我们可以如何方便的利用ArrayUtils类帮我们完成数组的打印、查找、克隆、倒序、以及值型/对象数组之间的转换等操作。如果想了解更多,请参考Javadoc。
1.3.2. org.apache.commons.lang.StringUtils
处理文本对Java应用来说应该算是家常便饭了,在1.4出现之前,Java自身提供的API非常有限,如String、StringTokenizer、StringBuffer,操作也比较单一。无非就是查找substring、分解、合并等等。到1.4的出现可以说Java的文字处理上了一个台阶,因为它支持regular expression了。这可是个重量级而方便的东东啊,缺点是太复杂,学习起来有一定难度。相较而言,Jakarta Commons提供的StringUtils和WordUtils至今还维持着那种简洁而强大的美,使用起来也很顺手。来看一个例子:
package sean.study.jakarta.commons.lang;
import org.apache.commons.lang.StringUtils;
public class StringUtilsAndWordUtilsUsage {
public static void main(String[] args) {
// data setup
String str1 = "" ;
String str2 = "" ;
String str3 = "\t" ;
String str4 = null ;
String str5 = "123" ;
String str6 = "ABCDEFG" ;
String str7 = "Itfeels good to use JakartaCommons.\r\n" ;
// check for empty strings
System.out.println( "==============================" );
System.out.println( "Is str1 blank? " +StringUtils.isBlank(str1));
System.out.println( "Is str2 blank? " +StringUtils.isBlank(str2));
System.out.println( "Is str3 blank? " +StringUtils.isBlank(str3));
System.out.println( "Is str4 blank? " +StringUtils.isBlank(str4));
// check for numerics
System.out.println( "==============================" );
System.out.println( "Is str5 numeric? " +StringUtils.isNumeric(str5));
System.out.println( "Is str6 numeric? " +StringUtils.isNumeric(str6));
// reverse strings / whole words
System.out.println( "==============================" );
System.out.println( "str6: " + str6);
System.out.println( "str6reversed: " + StringUtils.reverse(str6));
System.out.println( "str7: " + str7);
String str8 = StringUtils.chomp(str7);
str8 =StringUtils.reverseDelimited(str8, ' ' );
System.out.println( "str7 reversed whole words : \r\n" + str8);
// build header (useful to print logmessages that are easy to locate)
System.out.println( "==============================" );
System.out.println( "print header:" );
String padding = StringUtils.repeat( "=" , 50);
String msg = StringUtils.center( " Customised Header " , 50, "%" );
Object[] raw = new Object[]{padding, msg,padding};
String header = StringUtils.join(raw, "\r\n" );
System.out.println(header);
}
}
输出的结果如下:
==============================
Is str1 blank? true
Is str2 blank? true
Is str3 blank? true
Is str4 blank? true
==============================
Is str5 numeric?true
Is str6 numeric?false
==============================
str6: ABCDEFG
str6 reversed:GFEDCBA
str7: It feels goodto use Jakarta Commons.
str7 reversed wholewords :
Commons. Jakarta use to good feelsIt
==============================
print header:
==================================================
%%%%%%%%%%%%%%%Customised Header %%%%%%%%%%%%%%%%
==================================================
从代码中我们可以大致了解到这个StringUtils类简单而强大的处理能力,从检查空串(对null的情况处理很得体),到分割子串,到生成格式化的字符串,使用都很简洁,也很直截了当。
1.3.3. org.apache.commons.lang.builder
在前面的专题文章中,我们一起过了一遍org.apache.commons.lang包,接下来我们继续看org.apache.commons.lang.builder这个包。在这里面我们可以找到7个类,用于帮助我们实现Java对象的一些基础的共有方法。这7个类分别是:
CompareToBuilder – 用于辅助实现 Comparable.compareTo(Object) 方法;
EqualsBuilder – 用于辅助实现 Object.equals() 方法;
HashCodeBuilder – 用于辅助实现 Object.hashCode() 方法;
ToStringBuilder – 用于辅助实现 Object.toString() 方法;
ReflectionToStringBuilder – 使用反射机制辅助实现 Object.toString() 方法;
ToStringStyle – 辅助 ToStringBuilder 控制输出格式;
StandardToStringStyle – 辅助 ToStringBuilder 控制标准格式。
我们知道,在实际应用中,其实经常需要在运行过程中判定对象的知否相等、比较、取hash、和获取对象基本信息(一般是产生log日志)。然而实现这些compareTo、equals、hashCode、toString其实并非那么直截了当,甚至稍有不注意就可能造成难以追踪的bug,而且这些方法手工维护的话,比较繁琐,也容易出错。于是Commons Lang在builder这个包中提供了上述辅助类,为我们简化这些方法的实现和维护。
来看一个例子:
package sean.study.jakarta.commons.lang;
import java.util.Date;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
public class BuilderUsage {
public static void main(String[] args) {
Staff staff1 = new Staff(123, "John Smith" , new Date());
Staff staff2 = new Staff(456, "Jane Smith" , new Date());
System.out.println( "staff1's info: " + staff1);
System.out.println( "staff2's info: " + staff2);
System.out.println( "staff1's hash code: " +staff1.hashCode());
System.out.println( "staff2's hash code: " +staff2.hashCode());
System.out.println( "staff1 equals staff2? " +staff1.equals(staff2));
}
}
class Staff implements Comparable {
private long staffId;
private String staffName;
private Date dateJoined;
public Staff() {
}
public Staff( long staffId, String staffName, Date dateJoined){
this.staffId = staffId;
this.staffName = staffName;
this.dateJoined = dateJoined;
}
public int compareTo(Object o) {
int res = -1;
if (o != null &&Staff.class.isAssignableFrom(o.getClass())) {
Staff s = (Staff) o;
res = new CompareToBuilder()
.append(dateJoined,s.getDateJoined())
.append(staffName,s.getStaffName()).toComparison();
}
return res;
}
public boolean equals(Object o) {
boolean res = false;
if (o != null &&Staff.class.isAssignableFrom(o.getClass())) {
Staff s = (Staff) o;
res = new EqualsBuilder()
.append(staffId,s.getStaffId())
.isEquals();
}
return res;
}
public int hashCode() {
return new HashCodeBuilder(11,23).append(staffId).toHashCode();
}
public String toString() {
return new ToStringBuilder( this ,ToStringStyle.MULTI_LINE_STYLE)
.append( "staffId" ,staffId)
.append( "staffName" ,staffName)
.append( "dateJoined" ,dateJoined)
.toString();
}
public Date getDateJoined() {
return dateJoined;
}
public void setDateJoined(Date dateJoined) {
this .dateJoined = dateJoined;
}
public long getStaffId() {
return staffId;
}
public void setStaffId(long staffId) {
this .staffId = staffId;
}
public String getStaffName() {
return staffName;
}
public void setStaffName(String staffName) {
this .staffName = staffName;
}
}
以下是运行结果:
staff1's info:sean.study.jakarta.commons.lang.Staff@190d11[
staffId=123
staffName=John Smith
dateJoined=Sat Jul 30 13:18:45 CST 2005
]
staff2's info:sean.study.jakarta.commons.lang.Staff@1fb8ee3[
staffId=456
staffName=Jane Smith
dateJoined=Sat Jul 30 13:18:45 CST 2005
]
staff1's hash code:376
staff2's hash code:709
staff1 equals staff2? false
这些builder使用起来都很简单,new一个实例,append需要参与的信息,最后加上toComparison、isEquals、toHashCode、toString这样的结尾即可。相应的,如果你不需要这样级别的控制,也可以使用利用反射机制的版本自动化实现需要的功能,如:
public int compareTo(Object o) {
return CompareToBuilder.reflectionCompare( this , o);
}
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals( this , o);
}
public int hashCode() {
return HashCodeBuilder.reflectionHashCode( this );
}
public String toString() {
return ReflectionToStringBuilder.toString( this );
}
尤其当我们在项目中不希望过多的参与到对这些对象方法的维护时,采用Commons提供的利用反射的这些API就成了方便而相对安全的一个方案。
1.3.4. org.apache.commons.lang.math
在Jakarta Commons中,专门处理数学计算的类分别可以在两个地方找到:一是Commons Lang的org.apache.commons.lang.math包中,二是在Commons Math这个单独的子项目中。由于后者主要是处理复数、矩阵等,相对使用比较少,在我的笔记中就只简单讲讲Commons Lang中的math包。对后者感兴趣的可以看看
http://jakarta.apache.org/commons/math/
org.apache.commons.lang.math包中共有10个类,这些类可以归纳成四组:
1- 处理分数的 Fraction 类;
2- 处理数值的 NumberUtils 类;
3- 处理数值范围的 Range 、 NumberRange 、 IntRange 、 LongRange 、 FloatRange 、 DoubleRange 类;
4- 处理随机数的 JVMRandom 和 RandomUtils 类。
下面我举个例子分别说明上述四组的使用方法:
package sean.study.jakarta.commons.lang;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.DoubleRange;
import org.apache.commons.lang.math.Fraction;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.math.Range;
public class LangMathUsage {
public static void main(String[] args) {
demoFraction();
demoNumberUtils();
demoNumberRange();
demoRandomUtils();
}
public static void demoFraction() {
System.out.println(StringUtils.center( " demoFraction " , 30, "=" ));
Fraction myFraction = Fraction.getFraction(144,90);
// FractionmyFraction = Fraction.getFraction("1 54/90");
System.out.println( "144/90 as fraction: " + myFraction);
System.out.println( "144/90 to proper: " +myFraction.toProperString());
System.out.println( "144/90 as double: " +myFraction.doubleValue());
System.out.println( "144/90 reduced: " + myFraction.reduce());
System.out.println( "144/90 reduced proper: "
+myFraction.reduce().toProperString());
System.out.println();
}
public static void demoNumberUtils() {
System.out.println(StringUtils.center( " demoNumberUtils " , 30, "=" ));
System.out.println( "Is 0x3Fa number? "
+StringUtils.capitalize(BooleanUtils.toStringYesNo(NumberUtils
.isNumber( "0x3F" )))+ "." );
double [] array = { 1.0, 3.4, 0.8, 7.1, 4.6 };
double max = NumberUtils.max(array);
double min = NumberUtils.min(array);
String arrayStr =ArrayUtils.toString(array);
System.out.println( "Max of " + arrayStr + " is: " + max);
System.out.println( "Min of " + arrayStr + " is: " + min);
System.out.println();
}
public static void demoNumberRange() {
System.out.println(StringUtils.center( " demoNumberRange " , 30, "=" ));
Range normalScoreRange = newDoubleRange(90, 120);
double score1 = 102.5;
double score2 = 79.9;
System.out.println( "Normal score rangeis: " + normalScoreRange);
System.out.println( "Is "
+ score1
+ "a normal score? "
+ StringUtils
.capitalize(BooleanUtils.toStringYesNo(normalScoreRange
.containsDouble(score1)))+ "." );
System.out.println( "Is "
+ score2
+ "a normal score? "
+ StringUtils
.capitalize(BooleanUtils.toStringYesNo(normalScoreRange
.containsDouble(score2)))+ "." );
System.out.println();
}
public static void demoRandomUtils() {
System.out.println(StringUtils.center( " demoRandomUtils " , 30, "=" ));
for (int i = 0; i < 5; i++) {
System.out.println(RandomUtils.nextInt(100));
}
System.out.println();
}
}
以下是运行结果:
========demoFraction ========
144/90 as fraction:144/90
144/90 to proper: 154/90
144/90 as double:1.6
144/90 reduced: 8/5
144/90 reducedproper: 1 3/5
======demoNumberUtils =======
Is 0x3F a number? Yes.
Max of{1.0,3.4,0.8,7.1,4.6} is: 7.1
Min of{1.0,3.4,0.8,7.1,4.6} is: 0.8
======demoNumberRange =======
Normal score rangeis: Range[90.0,120.0]
Is 102.5 a normal score? Yes.
Is 79.9 a normal score? No.
======demoRandomUtils =======
75
63
40
21
92
以上就是Commons Lang的math包通常的用法。提一下NumberUtils.inNumber(String)方法,它会正确判断出给定的字符串是否是合法的数值,而我在前面的笔记中提到的StringUtils.isNumeric(String)只能判断一个字符串是否是由纯数字字符组成。Commons Lang的math包的各个类都还有很多实用的方法,远不止我在例子中用到的这些,如果你感兴趣,参照一下Commons Lang的Javadoc吧。
1.3.5. org.apache.commons.lang.time
好了,来看我在Common Lang中最后要讲的一个包:org.apache.commons.lang.time。这个包里面包含了如下5个类:
DateFormatUtils – 提供格式化日期和时间的功能及相关常量;
DateUtils – 在 Calendar 和 Date 的基础上提供更方便的访问;
DurationFormatUtils – 提供格式化时间跨度的功能及相关常量;
FastDateFormat – 为 java.text.SimpleDateFormat 提供一个的线程安全的替代类;
StopWatch – 是一个方便的计时器。
我们还是在一个例子中来看上述各个类的用法吧:
package sean.study.jakarta.commons.lang;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.commons.lang.time.StopWatch;
public class DateTimeUsage {
public static void main(String[] args) {
demoDateUtils();
demoStopWatch();
}
public static void demoDateUtils() {
System.out.println(StringUtils.center( " demoDateUtils " , 30, "=" ));
Date date = new Date();
String isoDateTime =DateFormatUtils.ISO_DATETIME_FORMAT.format(date);
String isoTime =DateFormatUtils.ISO_TIME_NO_T_FORMAT.format(date);
FastDateFormat fdf = FastDateFormat.getInstance( "yyyy-MM" );
String customDateTime =fdf.format(date);
System.out.println( "ISO_DATETIME_FORMAT: " + isoDateTime);
System.out.println( "ISO_TIME_NO_T_FORMAT: " + isoTime);
System.out.println( "Custom FastDateFormat: " +customDateTime);
System.out.println( "Default format: " + date);
System.out.println( "Round HOUR: " + DateUtils.round(date,Calendar.HOUR));
System.out.println( "Truncate HOUR: " +DateUtils.truncate(date, Calendar.HOUR));
System.out.println();
}
public static void demoStopWatch() {
System.out.println(StringUtils.center( " demoStopWatch " , 30, "=" ));
StopWatch sw = new StopWatch();
sw.start();
operationA();
sw.stop();
System.out.println( "operationA used " + sw.getTime() + " milliseconds." );
System.out.println();
}
public static void operationA() {
try {
Thread.sleep(999);
}
catch (InterruptedException e) {
// do nothing
}
}
}
以下是运行结果:
=======demoDateUtils ========
ISO_DATETIME_FORMAT:2005-08-01T12:41:51
ISO_TIME_NO_T_FORMAT:12:41:51
CustomFastDateFormat: 2005-08
Default format: MonAug 01 12:41:51 CST 2005
Round HOUR: Mon Aug01 13:00:00 CST 2005
Truncate HOUR: MonAug 01 12:00:00 CST 2005
=======demoStopWatch ========
operationA used 1000milliseconds.
具体的调用细节和完整的API请参阅Commons Lang的Javadoc。
1.4. Commons BeanUtils
Jakarta Commons项目提供了相当丰富的API,我们之前了解到的Commons Lang只是众多API的比较核心的一小部分而已。Commons下面还有相当数量的子项目,用于解决各种各样不同方向的实际问题,BeanUtils就是其中的一个,用于处理JavaBeans。它利用Java的反射机制,从动态的生成对bean的getter和setter的调用代码,到模拟创建一个动态的bean,等等。这个包看似简单,却是很多开源项目的基石:如在著名的Struts和Spring Framework中,我们都能找到BeanUtils的影子。大家猜猜看,有哪位名人是BeanUtils的作者之一?没错,就是Struts的创始人Craig McClanahan。
BeanUtils最核心的好处在于:我们在编码时,并不需要知道我们处理的JavaBeans具体是什么类型,有哪些属性,这些信息是可以动态获取的,甚至我们都可以不必去关心事实上是否存在这样一个具体的JavaBean类。我们只需要知道有一个JavaBean的实例,我们需要从中取得某个属性,设定某个属性的值,或者仅仅是需要一个属性表。要做到这些,依靠Sun提供的JavaBean规范似乎找不到一个很直接的方式,除非硬编码,将getXxxx()和setXxxx()直接写进我们的程序。但是这样就大大增加了代码的复杂度、耦合性和维护成本。还好Commons BeanUtils对这个问题提供了一种优雅的解决方案。
我们有两种途径获取Commons BeanUtils的binary:
1- 从Struts、Spring或者任何依赖BeanUtils的开源产品的发行包中找到相应的jar文件;
2- 从 http://www.apache.org/dist/jakarta/commons/beanutils/binaries/ 下载。
Commons BeanUtils的源码下载地址:
http://www.apache.org/dist/jakarta/commons/beanutils/source/
Commons BeanUtils一共包括如下5个包:
org.apache.commons.beanutils – 核心包,定义一组 Utils 类和需要用到的接口规范
org.apache.commons.beanutils.converters – 转换 String 到需要类型的类,实现 Converter 接口
org.apache.commons.beanutils.locale –beanutils 的 locale 敏感版本
org.apache.commons.beanutils.locale.converters– converters 的 locale 敏感版本
org.apache.commons.collections – beanutils 使用到的 Collection 类
其中需要我们特别关注的是这个org.apache.commons.beanutils包,其他包都是起辅助作用的。接下来我们就仔细看一看这个包都有些什么东东:
[4个接口]
Converter
该接口定义了如下方法:
publicjava.lang.Object convert(java.lang.Class type, java.lang.Object value);
只要实现了这个Converter接口并注册到ConvertUtils类即可被我们的BeanUtils包所使用,它的主要目的是提供将给定的Object实例转换为目标类型的算法。我们可以在beanutils.converters包中找到相当多的已经实现的转换器。
DynaBean
该接口定义的是一个动态的JavaBean,它的属性类型、名称和值都是可以动态改变的。
DynaClass
该接口定义的是针对实现了DynaBean接口的类的java.lang.Class对象,提供如getName()、newInstance()等方法。
MutableDynaClass
该接口是对DynaClass的扩展,使得动态bean的属性可以动态增加或删除。
[24个类]
BasicDynaBean
DynaBean接口的最精简实现
BasicDynaClass
DynaClass接口的最精简实现
BeanUtils
提供通过反射机制填写JavaBeans属性的工具/静态方法
BeanUtilsBean
BeanUtils类的实例化实现,区别于BeanUtils的静态方法方式,使得自定义的配置得以保持
ConstructorUtils
同MethodUtils类似,不过专注于构造方法
ContextClassLoaderLocal
针对每个classloader的唯一标识
ConvertingWrapDynaBean
包含了标准JavaBean实例的DynaBean实现,使得我们可以使用DynaBean的API来访问起属性,同时提供设定属性时的类型转换,继承自并区别于WrapDynaBean
ConvertUtils
提供工具/静态方法,用于将String对象及其数组转换为指定的类型的对象及其数组。
ConvertUtilsBean
ConvertUtils类的实例化实现,区别于ConvertUtils的静态方法方式,使得自定义的配置得以保持
DynaProperty
用于描述DynaBean的属性
JDBCDynaClass
为DynaClass的JDBC实现提供公用的逻辑
LazyDynaBean
懒载入DynaBean,自动往DynaClass添加属性并提供懒载入List和懒载入Map的功能
LazyDynaClass
实现MutableDynaClass接口的类
LazyDynaMap
为Map实例提供一个轻量级的DynaBean包装
MappedPropertyDescriptor
用于描述映射的属性
MethodUtils
包含了针对一般意义上的方法而非特定属性的反射工具/静态方法
MethodUtils.MethodDescriptor
描述通过反射查找某个方法所使用的键值
PropertyUtils
提供利用Java反射API调用具体对象的getter和setter的工具/静态方法
PropertyUtilsBean
PropertyUtils类的实例化实现,区别于PropertyUtils的静态方法方式,使得自定义的配置得以保持
ResultSetDynaClass
包装java.sql.ResultSet中的java.sql.Row实例的DynaBean所对应的DynaClass实现
ResultSetIterator
针对ResultSetDynaClass的java.util.Iterator实现
RowSetDynaClass
DynaClass的一种实现,用于在内存中创建一组表示SQL查询结果的DynaBeans,区别于ResultSetDynaClass,它不需要保持ResultSet打开
WrapDynaBean
DynaBean的一种实现,包含一个标准的JavaBean实例,以便我们可以使用DynaBean的API去访问它的属性,区别于ConvertingWrapDynaBean,它不做专门的类型转换
WrapDynaClass
DynaClass的一种实现,针对那些包装标准JavaBean实例的DynaBeans
[3个Exception]
(略)
看到这么多东西是不是有点头晕?不要慌,看几个例子就明白了。只要把握好BeanUtils本身要完成的事,就不难理解这些类存在的道理。我们不妨把BeanUtils的基础应用分解成:访问JavaBean的属性、设定JavaBean的属性、以及创建和使用DynaBeans。这样来看BeanUtils,你就会觉得简单清晰得多。
1.4.1. BeanUtils代码范例
假定我们有如下两个标准的JavaBean:
/**Address.java */
package sean.study.commons.beanutils;
public class Address {
private String zipCode;
private String addr;
private String city;
private String country;
public Address() {
}
public Address(String zipCode, String addr,String city, String country) {
this .zipCode = zipCode;
this .addr = addr;
this .city = city;
this .country = country;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this .addr = addr;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this .city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this .country = country;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this .zipCode = zipCode;
}
}
/**Customer.java */
package sean.study.commons.beanutils;
public class Customer {
private long id;
private String name;
private Address[] addresses;
public Customer() {
}
public Customer( long id, String name, Address[]addresses) {
this .id = id;
this .name = name;
this .addresses = addresses;
}
public Address[] getAddresses() {
return addresses;
}
public void setAddresses(Address[] addresses) {
this .addresses = addresses;
}
public long getId() {
return id;
}
public void setId( long id) {
this .id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
}
我们来看看通常我们是怎样利用Commons BeanUtils来完成一些基本的JavaBean和DynaBean操作:
package sean.study.commons.beanutils;
import org.apache.commons.beanutils.BasicDynaBean;
import org.apache.commons.beanutils.BasicDynaClass;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
public class BeanUtilsUsage {
public static void main(String[] args) throws Exception {
demoNormalJavaBeans();
demoDynaBeans();
}
public static void demoNormalJavaBeans() throws Exception {
System.out.println(StringUtils.center( " demoNormalJavaBeans " , 40, "=" ));
// data setup
Address addr1 = new Address( "CA1234" , "xxx" , "Los Angeles" , "USA" );
Address addr2 = new Address( "100000" , "xxx" , "Beijing" , "China" );
Address[] addrs = new Address[2];
addrs[0] = addr1;
addrs[1] = addr2;
Customer cust = new Customer(123, "John Smith" , addrs);
// accessing the city of first address
String cityPattern = "addresses[0].city" ;
String name = (String)PropertyUtils.getSimpleProperty(cust, "name" );
String city = (String)PropertyUtils.getProperty(cust, cityPattern);
Object[] rawOutput1 = new Object[] { "The city of customer " ,name,
"'sfirst address is " , city, "." };
System.out.println(StringUtils.join(rawOutput1));
// setting the zipcode of customer'ssecond address
String zipPattern = "addresses[1].zipCode" ;
if (PropertyUtils.isWriteable(cust, zipPattern)){
System.out.println( "Setting zipcode ..." );
PropertyUtils.setProperty(cust,zipPattern, "200000" );
}
String zip = (String)PropertyUtils.getProperty(cust, zipPattern);
Object[] rawOutput2 = new Object[] { "The zipcode of customer " ,name,
"'ssecond address is now " , zip, "." };
System.out.println(StringUtils.join(rawOutput2));
System.out.println();
}
public static void demoDynaBeans() throws Exception {
System.out.println(StringUtils.center( " demoDynaBeans " , 40, "=" ));
// creating a DynaBean
DynaProperty[] dynaBeanProperties = new DynaProperty[] {
new DynaProperty( "name" , String.class),
new DynaProperty( "inPrice" , Double.class),
new DynaProperty( "outPrice" , Double.class),
};
BasicDynaClass cargoClass = new BasicDynaClass( "Cargo" ,BasicDynaBean.class, dynaBeanProperties);
DynaBean cargo =cargoClass.newInstance();
// accessing a DynaBean
cargo.set( "name" , "Instant Noodles" );
cargo.set( "inPrice" ,new Double(21.3));
cargo.set( "outPrice" ,new Double(23.8));
System.out.println( "name: " + cargo.get( "name" ));
System.out.println( "inPrice: " + cargo.get( "inPrice" ));
System.out.println( "outPrice: " + cargo.get( "outPrice" ));
System.out.println();
}
}
上述代码运行结果如下:
=========demoNormalJavaBeans ==========
The city of customerJohn Smith's first address is Los Angeles.
Setting zipcode ...
The zipcode ofcustomer John Smith's second address is now 200000.
============demoDynaBeans =============
name: InstantNoodles
inPrice: 21.3
outPrice: 23.8
以上代码简单说明了一下BeanUtils常见的基本用法,还有很多高阶或者更具体的应用及原理,这里无法一一讲到,而且有很多笔者也不熟悉、不了解,对BeanUtils的讲解就到此吧。如果你有兴趣,或者还不是很清楚为什么像这样动态的或者说松散的访问JavaBean是有必要的,可以把Struts的源码拿下来研究一下,看看FormBean以及DynaActionForm这些是如何被动态创建的,一定会有收获。
1.5. Commons Collections
Commons Collections,又是一个重量级的东西,为Java标准的Collections API提供了相当好的补充。我不知道其他人,就我自己而言,让我用java.util.Collection及其子类,加上java.util.Collections类提供的操作方法,处理一些简单的数据结构问题还可以,稍微复杂一点的就觉得有点头痛,很多细节的地方需要我插入这样那样的小逻辑,或者感觉它太死板,不够灵活,再或者确实有点晦涩吧。再说了,如果我只是处理一般的数据结构问题,为什么不自己用数组或者自定义的链表来做,再加上Jakarta Commons的Lang提供的ArrayUtils、StringUtils等,已经基本够了,性能可以保证,那么还要这个Collections API干嘛。当然,说到这里有些偏激了,Collections当然有它存在的道理,能够把常用的数据结构归纳起来,以通用的方式去维护和访问,这应该说是一种进步,但是用起来似乎不够友好。这个时候我就会想,如果Java比现在做得更好用些,或者有一套第三方的API把我的这些需求抽象出来,实现了,该多好。Commons Collections就是这样一套API。
在这里可以找到下载链接:(binary和src都有)
http://jakarta.apache.org/site/downloads/downloads_commons-collections.cgi
目前Commons Collection发布的最新版本是3.1。建议下载这个3.1版本,页面上出现的2.1.1是针对2.1不兼容3.0而发布的升级维护版。
我们先来浏览一下它的包结构。一共是12个:
org.apache.commons.collections – CommonsCollections 自定义的一组公用的接口和工具类
org.apache.commons.collections.bag – 实现 Bag 接口的一组类
org.apache.commons.collections.bidimap – 实现 BidiMap 系列接口的一组类
org.apache.commons.collections.buffer – 实现 Buffer 接口的一组类
org.apache.commons.collections.collection – 实现 java.util.Collection 接口的一组类
org.apache.commons.collections.comparators– 实现 java.util.Comparator 接口的一组类
org.apache.commons.collections.functors –Commons Collections 自定义的一组功能类
org.apache.commons.collections.iterators – 实现 java.util.Iterator 接口的一组类
org.apache.commons.collections.keyvalue – 实现集合和键 / 值映射相关的一组类
org.apache.commons.collections.list – 实现 java.util.List 接口的一组类
org.apache.commons.collections.map – 实现 Map 系列接口的一组类
org.apache.commons.collections.set – 实现 Set 系列接口的一组类
用过Java Collections API的朋友大概或多或少会同意我如下的划分:在Java的Collections API中,不狭义的区分语法上的接口和类,把它们都看作是类的话,大致我们可以发现三种主要的类别:
1- 容器类:如Collection、List、Map等,用于存放对象和进行简单操作的;
2- 操作类:如Collections、Arrays等,用于对容器类的实例进行相对复杂操作如排序等;
3- 辅助类:如Iterator、Comparator等,用于辅助操作类以及外部调用代码实现对容器类的操作,所谓辅助,概括而通俗的来讲,就是这些类提供一种算法,你给它一个对象或者一组对象,或者仅仅是按一定的规则调用它,它给你一个运算后的答案,帮助你正确处理容器对象。比如Iterator会告诉你容器中下一个对象有没有、是什么,而Comparator将对象大小/先后次序的算法逻辑独立出来。
同样,Jakarta Commons Collections我们细细看来,也能够找出类似的划分:
1- 作为容器类的补充,我们可以找到Bag、Buffer、BidiMap、OrderedMap等等;
2- 作为操作类的补充,我们可以找到CollectionUtils、IteratorUtils、ListUtils、SetUtils等等;
3- 作为辅助类的补充,我们可以找到MapIterator、Closure、Predicate、Transformer等等;
对于这样的一个大包,当然不可能一个类一个类的讲了,找一些常用的和有用的当做接下来讨论的话题吧。大概列个清单:
Bag
HashBag
BagUtils
Buffer
BlockingBuffer
BoundedFifoBuffer
PriorityBuffer
BufferUtils
MultiMap
BidiMap
CaseInsensitiveMap
LazyMap
MapUtils
TypedCollection
CollectionUtils
ReverseComparator
ComparatorChain
NullComparator
FixedOrderComparator
ComparatorUtils
Predicate
AndPredicate
OrPredicate
AllPredicate
OnePredicate
NonePredicate
PredicateUtils
Transformer
ChainedTransformer
SwitchTransformer
TransformerUtils
Closure
ChainedClosure
IfClosure
WhileClosure
ClosureUtils
LoopingIterator
ArrayListIterator
FilterIterator
UniqueFilterIterator
IteratorUtils
总共9组,在接下来的笔记中我们一起慢慢的看。
1.5.1. Bag组
首先来看Bag组。
Bag
HashBag
BagUtils
Bag是在org.apache.commons.collections包中定义的接口,它extends java.util.Collection,而它的实现类都被放在下面的bag包中。之所以有这样一组类型,是因为我们有时候需要在Collection中存放多个相同对象的拷贝,并且需要很方便的取得该对象拷贝的个数。需要注意的一点是它虽然extends Collection,但是如果真把它完全当作java.util.Collection来用会遇到语义上的问题,详细信息参考Javadoc。
HashBag是Bag接口的一个标准实现。而BagUtils提供一组static的方法让调用者获取经过不同装饰后的Bag实例。
还是举例子来看:
/**Book.java */
package sean.study.commons.collections;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
public class Book {
private String name;
private String isbn;
private double retailPrice;
public Book() {
}
public Book(String name, String isbn, double retailPrice) {
this .name = name;
this .isbn = isbn;
this .retailPrice = retailPrice;
}
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append( "name" ,name)
.append( "ISBN" ,isbn)
.append( "retailPrice" ,retailPrice)
.toString();
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this .isbn = isbn;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public double getRetailPrice() {
return retailPrice;
}
public void setRetailPrice( double retailPrice) {
this .retailPrice = retailPrice;
}
}
/**BagUsage.java */
package sean.study.commons.collections;
import org.apache.commons.collections.Bag;
import org.apache.commons.collections.BagUtils;
import org.apache.commons.collections.bag.HashBag;
import org.apache.commons.lang.StringUtils;
public class BagUsage {
public static void main(String[] args) {
demoBagUsage();
}
public static void demoBagUsage() {
System.out.println(StringUtils.center( " demoBagUsage " , 40, "=" ));
// data setup
Book book1 = new Book( "RefactoringWorkbook" , "7-5083-2208-8" ,29.8);
Book book2 = new Book( "J2EEDesign Patterns" , "7-5083-3099-4" ,45);
Book book3 = new Book( "AgileSoftware Development" , "7-5083-1503-0" ,59);
// create a bag
Bag myBag = BagUtils.typedBag( new HashBag(), Book. class );
myBag.add(book1, 360);
myBag.add(book2, 500);
myBag.add(book3, 170);
// calculations for a bag
double price1 = book1.getRetailPrice();
double price2 = book2.getRetailPrice();
double price3 = book3.getRetailPrice();
int book1Count = myBag.getCount(book1);
int book2Count = myBag.getCount(book2);
int book3Count = myBag.getCount(book3);
double totalValue = (price1 * book1Count) +(price2 * book2Count)
+ (price3 * book3Count);
// dispaly results
System.out.println( "There are " + book1Count + " copies of "
+ book1.getName() + "." );
System.out.println( "There are " + book2Count + " copies of "
+ book2.getName() + "." );
System.out.println( "There are " + book3Count + " copies of "
+ book3.getName() + "." );
System.out.println( "The total value of these books is: " +totalValue);
System.out.println();
}
}
以下是运行结果:
=============demoBagUsage =============
There are 360 copiesof Refactoring Workbook.
There are 500 copiesof J2EE Design Patterns.
There are 170 copiesof Agile Software Development.
The total value ofthese books is: 43258.0
需要说明的是,以上的代码仅仅为了演示如何使用Bag,实际应用不建议像这样硬编码。
1.5.2. Buffer组
来看Buffer组。
Buffer
BlockingBuffer
BoundedFifoBuffer
PriorityBuffer
UnboundedFifoBuffer
BufferUtils
Buffer是定义在org.apache.commons.collections包下面的接口,用于表示按一定顺序除去成员对象的collection如队列等。具体的实现类在org.apache.commons.collections.buffer包下可以找到。
BufferUtils提供很多静态/工具方法装饰现有的Buffer实例,如将其装饰成BlockingBuffer、执行类型检查的TypedBuffer、或者不可改变的UnmodifiableBuffer等等。
最简单直接的Buffer实现类是UnboundedFifoBuffer,提供先进先出的大小可变的队列。而BoundedFifoBuffer则是对其大小进行了限制,是固定大小的先进先出队列。BlockingBuffer要在多线程的环境中才能体现出它的价值,尤其是当我们需要实现某种流水线时这个BlockingBuffer很有用:每个流水线上的组件从上游的BlockingBuffer获取数据,处理后放到下一个BlockingBuffer中依次传递。BlockingBuffer的核心特色通俗点说就是如果你向它要东西,而它暂时还没有的话,你可以一直等待直至拿到为止。PriorityBuffer则提供比一般的先进先出Buffer更强的控制力:我们可以自定义Comparator给它,告诉它怎么判定它的成员的先后顺序,优先级最高的最先走。
为了方便和清晰的需要,我在这里只举一个BoundedFifoBuffer,包装成TypedBuffer,看看在具体的代码中通常如何使用Buffer:(还是沿用上次的Book类)
package sean.study.commons.collections;
import java.util.Iterator;
import org.apache.commons.collections.Buffer;
import org.apache.commons.collections.BufferUtils;
import org.apache.commons.collections.buffer.BoundedFifoBuffer;
import org.apache.commons.lang.StringUtils;
public class BufferUsage {
public static void main(String[] args) {
demoBufferUsage();
}
public static void demoBufferUsage() {
System.out.println(StringUtils.center( " demoBagUsage " , 40, "=" ));
// data setup
Book book1 = new Book( "RefactoringWorkbook" , "7-5083-2208-8" ,29.8);
Book book2 = new Book( "J2EEDesign Patterns" , "7-5083-3099-4" ,45);
Book book3 = new Book( "AgileSoftware Development" , "7-5083-1503-0" ,59);
Book book4 = new Book( "ProfessionalJSP" , "7-5053-8005-2" ,100);
// create a Buffer
Buffer buffer =
BufferUtils.typedBuffer( new BoundedFifoBuffer(3), Book. class );
buffer.add(book1);
buffer.add(book2);
buffer.add(book3);
Book removed = (Book) buffer.remove();
System.out.println( "Removed:" );
System.out.println(removed);
buffer.add(book4);
// get items in buffer
for ( int i = 0; i < 3; i++) {
System.out.println(buffer.get());
buffer.remove();
}
System.out.println(StringUtils.repeat( "=" , 40));
}
}
以下是运行结果:
=============demoBagUsage =============
Removed:
sean.study.commons.collections.Book@e09713[
name=Refactoring Workbook
ISBN=7-5083-2208-8
retailPrice=29.8
]
Remaining:
sean.study.commons.collections.Book@e09713[
name=J2EE Design Patterns
ISBN=7-5083-3099-4
retailPrice=45.0
]
sean.study.commons.collections.Book@47b480[
name=Agile Software Development
ISBN=7-5083-1503-0
retailPrice=59.0
]
sean.study.commons.collections.Book@19b49e6[
name=Professional JSP
ISBN=7-5053-8005-2
retailPrice=100.0
]
========================================
我们可以看到,Buffer的add和remove方法分别添加新成员和删除最先加入的成员。由于我们的Buffer定义为只能装3个Book类的实例,所以不论我们试图加入其他类型的对象,或者加入超过3个,操作都将失败。如果我们在遍历时使用get()而不调用remove(),那么我们将得到3个相同的拷贝,而这正是我们期望的FIFO队列的行为。假如你需要遍历并保留数据,可以使用标准的Iterator机制。
1.5.3. Map组
接下来看Map组。
BidiMap
MultiMap
LazyMap
MapUtils
Commons Collections在java.util.Map的基础上扩展了很多接口和类,比较有代表性的是BidiMap、MultiMap和LazyMap。跟Bag和Buffer类似,Commons Collections也提供了一个MapUtils。
所谓BidiMap,直译就是双向Map,可以通过key找到value,也可以通过value找到key,这在我们日常的代码-名称匹配的时候很方便:因为我们除了需要通过代码找到名称之外,往往也需要处理用户输入的名称,然后获取其代码。需要注意的是BidiMap当中不光key不能重复,value也不可以。
所谓MultiMap,就是说一个key不在是简单的指向一个对象,而是一组对象,add()和remove()的时候跟普通的Map无异,只是在get()时返回一个Collection,利用MultiMap,我们就可以很方便的往一个key上放数量不定的对象,也就实现了一对多。
所谓LazyMap,意思就是这个Map中的键/值对一开始并不存在,当被调用到时才创建,这样的解释初听上去是不是有点不可思议?这样的LazyMap有用吗?我们这样来理解:我们需要一个Map,但是由于创建成员的方法很“重”(比如数据库访问),或者我们只有在调用get()时才知道如何创建,或者Map中出现的可能性很多很多,我们无法在get()之前添加所有可能出现的键/值对,或者任何其它解释得通的原因,我们觉得没有必要去初始化一个Map而又希望它可以在必要时自动处理数据生成的话,LazyMap就变得很有用了。
我们还是通过一个具体的例子来说明:
package sean.study.commons.collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.Factory;
import org.apache.commons.collections.MultiHashMap;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.lang.StringUtils;
public class MapUsage {
public static void main(String[] args) {
demoBidiMap();
demoMultiMap();
demoLazyMap();
}
public static void demoBidiMap() {
System.out.println(StringUtils.center( " demoBidiMap " , 40, "=" ));
BidiMap bidiMap = new DualHashBidiMap();
bidiMap.put( "BJ" , "Beijing" );
bidiMap.put( "SH" , "Shanghai" );
bidiMap.put( "GZ" , "Guangzhou" );
bidiMap.put( "CD" , "Chengdu" );
System.out.println( "Key-Value: BJ = " + bidiMap.get( "BJ" ));
System.out.println( "Value-Key: Chengdu =" + bidiMap.getKey( "Chengdu" ));
System.out.println(StringUtils.repeat( "=" , 40));
}
public static void demoMultiMap() {
System.out.println(StringUtils.center( " demoMultiMap " , 40, "=" ));
MultiMap multiMap = new MultiHashMap();
multiMap.put( "Sean" , "C/C++" );
multiMap.put( "Sean" , "OO" );
multiMap.put( "Sean" , "Java" );
multiMap.put( "Sean" , ".NET" );
multiMap.remove( "Sean" , "C/C++" );
System.out.println( "Sean's skill set: " + multiMap.get( "Sean" ));
System.out.println(StringUtils.repeat( "=" , 40));
}
public static void demoLazyMap() {
System.out.println(StringUtils.center( " demoLazyMap " , 40, "=" ));
// borrowed from Commons Collection'sJavadoc
Factory factory = new Factory() {
public Object create() {
return new Date();
}
};
Map lazy = LazyMap.decorate( new HashMap(), factory);
System.out.println(lazy.get( "NOW" ));
System.out.println(StringUtils.repeat( "=" , 40));
}
}
以下是运行结果:
=============demoBidiMap ==============
Key-Value: BJ = Beijing
Value-Key: Chengdu = CD
========================================
=============demoMultiMap =============
Sean's skill set:[OO, Java, .NET]
========================================
=============demoLazyMap ==============
Wed Aug 03 12:44:56CST 2005
========================================
简单说一下这个Factory,它是定义在org.apache.commons.collections包下面的一个接口,用于自定义对象的创建过程。这个有点像是后面我们要讲的Transformer的简化版本,但是也更直接也很好用,至少Commons Collections通过它向开发人员开放了一个可以方便控制对象创建细节的接口。
1.5.4. Collection组
接下来看看Collection组。
TypedCollection
CollectionUtils
首先就是这个TypedCollection,它实际上的作用就是提供一个decorate方法,我们传进去一个Collection和需要的类型甄别信息java.lang.Class,它给我们创建一个全新的强类型的Collection。我们其实在bag、buffer、list、map、set这些子包中都可以找到分别对应Bag、Buffer、List、Map、Set接口的TypedXxxx版本。
方法签名:
public static Collection decorate(Collection coll, Classtype)
当它执行时,它会判断coll是否为null,同时如果coll包含数据,它会对数据进行验证,看是否满足指定的type条件。最后它返回一个强类型的Collection,当我们对这个强类型的Collection进行add操作时,它会帮我们确保添加的是正确的类型。
而这个CollectionUtils可能大家都已经想到了,就是提供一组针对Collection操作的工具/静态方法。比较有意思的是对Collection的转型、合并、减等操作。
由于这两个类的功能和作用都比较清晰,我就不举例说明了,需要进一步了解的请看Javadoc。
1.5.5. Comparator组
接下来我们会讲到辅助类,首先看Comparator组。
ReverseComparator
ComparatorChain
NullComparator
FixedOrderComparator
ComparatorUtils
其实Comparator这个概念并不是Commons Collections引入的,在标准的Java Collections API中,已经明确定了一个java.util.Comparator接口,只是有很多人并不了解,Commons Collections也只是扩展了这个接口而已。这个java.util.Comparator定义如下核心方法:
public int compare(Object arg0, Object arg1)
传给它两个对象,它要告诉我们这两个对象哪一个在特定的语义下更“大”,或者两者相等。如果arg0 > arg1,返回大于0的整数;如果arg0 = arg1,返回0;如果arg0 < arg2,返回小于0的整数。
我们看看Commons Collections给我们提供了哪些Comparator的实现类(都在org.apache.commons.collections.comparators包下面):
BooleanComparator – 用于排序一组 Boolean 对象,指明先 true 还是先 false ;
ComparableComparator – 用于排序实现了 java.lang.Comparable 接口的对象(我们常用的 Java 类如 String 、 Integer 、 Date 、 Double 、 File 、 Character 等等都实现了 Comparable 接口);
ComparatorChain – 定义一组 Comparator 链,链中的 Comparator 对象会被依次执行;
FixedOrderComparator – 用于定义一个特殊的顺序,对一组对象按照这样的自定义顺序进行排序;
NullComparator – 让 null 值也可参与比较,可以设定为先 null 或者后 null ;
ReverseComparator – 将原有的 Comparator 效果反转;
TransformingComparator – 将一个 Comparator 装饰为具有 Transformer 效果的 Comparator 。
// 有关 Transformer 的内容会在以后的笔记中讲到。
以上除了ComparatorChain之外,似乎都是实现一些很基本的比较方法,但是当我们用ComparatorChain将一组Comparator串起来之后,就可以实现非常灵活的比较操作。那么这些Comparator在实际代码中如何使用呢?看例子:
/**Issue.java */
package sean.study.commons.collections;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
public class Issue {
private long id;
private String severity;
private String owner;
public Issue() {
}
public Issue( long id, String severity, String owner) {
this .id = id;
this .severity = severity;
this .owner = owner;
}
public String toString() {
return new ToStringBuilder( this ,ToStringStyle.SHORT_PREFIX_STYLE)
.append( "id" ,id)
.append( "severity" ,severity)
.append( "owner" ,owner)
.toString();
}
public long getId() {
return id;
}
public void setId( long id) {
this .id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this .owner = owner;
}
public String getSeverity() {
return severity;
}
public void setSeverity(String severity) {
this .severity = severity;
}
}
/**ComparatorUsage.java */
package sean.study.commons.collections;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.ComparatorChain;
import org.apache.commons.collections.comparators.FixedOrderComparator;
import org.apache.commons.lang.StringUtils;
public class ComparatorUsage {
public static void main(String[] args) {
demoComparator();
}
public static void demoComparator() {
System.out.println(StringUtils.center( " demoComparator " , 40, "=" ));
// data setup
Issue[] issues = new Issue[] {
new Issue(15102, "Major" , "John" ),
new Issue(15103, "Minor" , "Agnes" ),
new Issue(15104, "Critical" , "Bill" ),
new Issue(15105, "Major" , "John" ),
new Issue(15106, "Major" , "John" ),
new Issue(15107, "Critical" , "John" ),
new Issue(15108, "Major" , "Agnes" ),
new Issue(15109, "Minor" , "Julie" ),
new Issue(15110, "Major" , "Mary" ),
new Issue(15111, "Enhancement" , "Bill" ),
new Issue(15112, "Minor" , "Julie" ),
new Issue(15113, "Major" , "Julie" )
};
// comparators setup
String[] severityOrder = { "Critical" , "Major" , "Minor" , "Enhancement" };
Comparator severityComparator = new FixedOrderComparator(severityOrder);
ComparatorChain compChain = new ComparatorChain();
compChain.addComparator(newBeanComparator( "owner" ));
compChain.addComparator(newBeanComparator( "severity" ,severityComparator));
compChain.addComparator(newBeanComparator( "id" ));
// sort and display
Arrays.sort(issues, compChain);
for ( int i = 0; i < issues.length; i++) {
System.out.println(issues[i]);
}
System.out.println(StringUtils.repeat( "=" , 40));
}
}
输出结果为:
============demoComparator ============
Issue[id=15108,severity=Major,owner=Agnes]
Issue[id=15103,severity=Minor,owner=Agnes]
Issue[id=15104,severity=Critical,owner=Bill]
Issue[id=15111,severity=Enhancement,owner=Bill]
Issue[id=15107,severity=Critical,owner=John]
Issue[id=15102,severity=Major,owner=John]
Issue[id=15105,severity=Major,owner=John]
Issue[id=15106,severity=Major,owner=John]
Issue[id=15113,severity=Major,owner=Julie]
Issue[id=15109,severity=Minor,owner=Julie]
Issue[id=15112,severity=Minor,owner=Julie]
Issue[id=15110,severity=Major,owner=Mary]
========================================
我们可以看到,ComparatorChain中的Comparator被依次执行,先按name,再按我们自定义的severity次序,再按id,最终我们得到了重新排列的数组。
1.5.6. Predicate组
接下来看Predicate组
Predicate
AndPredicate
OrPredicate
AllPredicate
OnePredicate
NonePredicate
PredicateUtils
Predicate是Commons Collections中定义的一个接口,可以在org.apache.commons.collections包中找到。其中定义的方法签名如下:
public boolean evaluate(Object object)
它以一个Object对象为参数,处理后返回一个boolean值,检验某个对象是否满足某个条件。其实这个Predicate以及上一篇笔记提到的Comparator还有我们即将看到的Transformer和Closure等都有些类似C/C++中的函数指针,它们都只是提供简单而明确定义的函数功能而已。
跟其他组类似,Commons Collections也提供了一组定义好的Predicate类供我们使用,这些类都放在org.apache.commons.collections.functors包中。当然,我们也可以自定义Predicate,只要实现这个Predicate接口即可。在Commons Collections中我们也可以很方便使用的一组预定义复合Predicate,我们提供2个或不定数量个Predicate,然后交给它,它可以帮我们处理额外的逻辑,如AndPredicate处理两个Predicate,只有当两者都返回true它才返回true;AnyPredicate处理多个Predicate,当其中一个满足就返回true,等等。
看看具体的代码中如何使用这些Predicate吧:
package sean.study.commons.collections;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
import org.apache.commons.collections.functors.InstanceofPredicate;
import org.apache.commons.collections.functors.NotNullPredicate;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
public class PredicateUsage {
public static void main(String[] args) {
demoPredicates();
}
public static void demoPredicates() {
System.out.println(StringUtils.center( " demoPredicates " , 40, "=" ));
Predicate p1 = new InstanceofPredicate(String.class);
Predicate p2 =NotNullPredicate.getInstance();
Predicate p3 = new Predicate() {
public boolean evaluate(Object obj) {
String str = (String) obj;
return StringUtils.isAlphanumeric(str)
&& str.length()>= 6
&& str.length()<= 10;
}
};
Predicate p4 =PredicateUtils.allPredicate( new Predicate[]{p1, p2, p3}); Stringinput = "ABCD1234" ;
Object[] raw = new Object[] {
"Is'" ,
input,
"' avalid input? " ,
BooleanUtils.toStringYesNo(p4.evaluate(input)),
"."
};
System.out.println(StringUtils.join(raw));
System.out.println(StringUtils.repeat( "=" , 40));
}
}
输出结果如下:
============demoPredicates ============
Is 'ABCD1234' avalid input? yes.
========================================
这里面我首先定义了3个简单的Predicate,p1判断对象是否为String的实例,p2判断是否对象为null,p3是自定义的,判断是否为数字字母的组合,并且长度在6~10字符。然后我用AllPredicate将它们组合到一起,成为p4,当p1、p2、p3都满足时,p4的evaluate方法才返回true。利用Predicate我们可以把判断true或false的逻辑从特定的业务代码分离出来,以后我们重用也好,重新组装也好,都是很方便的。
1.5.7. Transformer组
接下来看Transformer组。
Transformer
ChainedTransformer
SwitchTransformer
TransformerUtils
我们有时候需要将某个对象转换成另一个对象供另一组方法调用,而这两类对象的类型有可能并不是出于同一个继承体系的,或者说出了很基本的Object之外没有共同的父类,或者我们根本不关心他们是不是有其他继承关系,甚至就是同一个类的实例只是对我们而言无所谓,我们为了它能够被后续的调用者有意义的识别和处理,在这样的情形,我们就可以利用Transformer。除了基本的转型Transformer之外,Commons Collections还提供了Transformer链和带条件的Transformer,使得我们很方便的组装出有意义的转型逻辑。
假定我们在处理员工聘用时,需要将原来的Applicant对象转换为Employee对象,而Applicant类和Employee类无论继承关系、字段内容、具体业务职能等等都不是同一派系的,只是某些字段是相关的,且要求必要的转换,那么这个时候我们使用Transformer就可以比较方便的实现这项功能,并且由于它的实现是灵活的、模块化的,使得今后的维护也变得清晰和易于处理。代码如下:
/**Applicant.java */
package sean.study.commons.collections;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
public class Applicant {
private String name;
private int age;
private String applyFor;
public Applicant() {
}
public Applicant(String name, int age, String applyFor) {
this .name = name;
this .age = age;
this .applyFor = applyFor;
}
public String toString() {
return new ToStringBuilder( this ,ToStringStyle.SHORT_PREFIX_STYLE)
.append( "name" ,name)
.append( "age" ,age)
.append( "applyFor" ,applyFor)
.toString();
}
public int getAge() {
return age;
}
public void setAge( int age) {
this .age = age;
}
public String getApplyFor() {
return applyFor;
}
public void setApplyFor(String applyFor) {
this .applyFor = applyFor;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
}
/**Employee.java */
package sean.study.commons.collections;
import java.util.Date;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.time.DateFormatUtils;
public class Employee {
private String name;
private int age;
private Date dateJoined;
private String grade;
private double salary;
public Employee() {
}
public Employee(String name, int age, Date dateJoined, Stringgrade, double salary) {
this .name = name;
this .age = age;
this .dateJoined = dateJoined;
this .grade = grade;
this .salary = salary;
}
public String toString() {
return new ToStringBuilder( this , ToStringStyle.SHORT_PREFIX_STYLE)
.append( "name" ,name)
.append( "age" ,age)
.append( "dateJoined" ,DateFormatUtils.format(dateJoined, "yyyy-MM-dd" ))
.append( "grade" ,grade)
.append( "salary" ,salary)
.toString();
}
public int getAge() {
return age;
}
public void setAge( int age) {
this .age = age;
}
public Date getDateJoined() {
return dateJoined;
}
public void setDateJoined(Date dateJoined) {
this .dateJoined = dateJoined;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this .grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public double getSalary() {
return salary;
}
public void setSalary( double salary) {
this .salary = salary;
}
}
/**TransformerUsage.java */
package sean.study.commons.collections;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.SwitchTransformer;
import org.apache.commons.lang.StringUtils;
public class TransformerUsage {
public static void main(String[] args) {
demoTransformerUsage();
}
public static void demoTransformerUsage() {
System.out.println(StringUtils.center( " demoTransformerUsage " , 40, "=" ));
// data setup
Applicant[] applicants = new Applicant[] {
new Applicant( "Tony" ,26, "Developer" ),
new Applicant( "Michelle" ,24, "Tester" ),
new Applicant( "Jack" ,28, "Project Manager" )
};
List appList =Arrays.asList(applicants);
// predicate setup
Predicate isDeveloper = new Predicate() {
public boolean evaluate(Object obj) {
Applicant app = (Applicant) obj;
return "Developer" .equalsIgnoreCase(app.getApplyFor());
}
};
Predicate isTester = new Predicate() {
public boolean evaluate(Object obj) {
Applicant app = (Applicant) obj;
return "Tester" .equalsIgnoreCase(app.getApplyFor());
}
};
Predicate isPM = new Predicate() {
public boolean evaluate(Object obj) {
Applicant app = (Applicant) obj;
return "ProjectManager" .equalsIgnoreCase(app.getApplyFor());
}
};
Predicate[] checkApplyFor = new Predicate[] {
isDeveloper,
isTester,
isPM
};
// transformer setup
Transformer developerTransformer = new Transformer() {
public Object transform(Object obj) {
Applicantapp = (Applicant) obj;
return new Employee(
app.getName(), app.getAge(), new Date(), "E4" , 2000
);
}
};
Transformer testerTransformer = new Transformer() {
public Object transform(Object obj) {
Applicant app = (Applicant) obj;
return new Employee(
app.getName(), app.getAge(), new Date(), "E4" , 2000
);
}
};
Transformer pmTransformer = new Transformer() {
public Object transform(Object obj) {
Applicant app = (Applicant) obj;
return new Employee(
app.getName(), app.getAge(), new Date(), "E5" , 3000
);
}
};
Transformer[] transformers = new Transformer[] {
developerTransformer,
testerTransformer,
pmTransformer
};
// transform
Transformer employTransformer = new SwitchTransformer(
checkApplyFor, transformers, null
);
Collection employed =CollectionUtils.collect(appList, employTransformer);
// output
System.out.println( "Applicants: " );
Iterator iter1 = appList.iterator();
while (iter1.hasNext()) {
System.out.println(iter1.next());
}
System.out.println( "Employed: " );
Iterator iter2 = employed.iterator();
while (iter2.hasNext()) {
System.out.println(iter2.next());
}
System.out.println(StringUtils.repeat( "=" , 40));
}
}
以下是运行结果:
=========demoTransformerUsage =========
Applicants:
Applicant[name=Tony,age=26,applyFor=Developer]
Applicant[name=Michelle,age=24,applyFor=Tester]
Applicant[name=Jack,age=28,applyFor=ProjectManager]
Employed:
Employee[name=Tony,age=26,dateJoined=2005-08-05,grade=E4,salary=2000.0]
Employee[name=Michelle,age=24,dateJoined=2005-08-05,grade=E4,salary=2000.0]
Employee[name=Jack,age=28,dateJoined=2005-08-05,grade=E5,salary=3000.0]
========================================
我们首先定义一组Predicate,用于在SwitchTransformer中判断采用那个具体的Transformer,这个具体的Transformer也是通过数组同时传递给SwitchTransformer的构造方法的。不同的Predicate可以有不同的实现,不同的Transformer也可以有不同的实现,因为它们之间实际上完全是相互独立的。这就使我们有效的分离了逻辑和具体业务。
1.5.8. Closure组
Closure组。
Closure
ChainedClosure
IfClosure
WhileClosure
ClosureUtils
Closure这一组接口和类提供一个操作对象的execute方法,为我们在处理一系列对象时可以将处理逻辑分离出来。理论上讲,使用Transformer也可以达到类似的效果,只要输出对象和输入对象是同一个对象就好,但是Closure接口定义的execute方法返回void,并且从效果和功能区分上,Closure可以更好的诠释对象处理或执行的意思。而事实上,ClosureUtils中也提供了一个asClosure方法包装一个现成的Transformer。
沿用前面的Emploee类,我们来给一组员工涨工资:
package sean.study.commons.collections;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
public class ClosureUsage {
public static void main(String[] args) {
demoClosureUsage();
}
public static void demoClosureUsage() {
System.out.println(StringUtils.center( " demoClosureUsage " , 40, "=" ));
// data setup
Employee[] employees = new Employee[] {
new Employee( "Tony" ,26, new Date(), "E4" , 2000),
new Employee( "Michelle" ,24, new Date(), "E4" , 2000),
new Employee( "Jack" ,28, new Date(), "E5" , 3000)
};
Collection empColl =Arrays.asList(employees);
printColl( "Beforesalary increase:" , empColl);
// closure setup
Closure salaryIncreaseClosure = new Closure() {
public void execute(Object obj) {
Employee emp = (Employee) obj;
emp.setSalary(emp.getSalary() *1.20);
}
};
// salary increase
CollectionUtils.forAllDo(empColl,salaryIncreaseClosure);
printColl( "Aftersalary increase:" , empColl);
System.out.println(StringUtils.repeat( "=" , 40));
}
public static void printColl(String label, Collection c) {
if (StringUtils.isNotBlank(label)) {
System.out.println(label);
}
Iterator iter = c.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}
以下是运行结果:
===========demoClosureUsage ===========
Before salaryincrease:
Employee[name=Tony,age=26,dateJoined=2005-08-05,grade=E4,salary=2000.0]
Employee[name=Michelle,age=24,dateJoined=2005-08-05,grade=E4,salary=2000.0]
Employee[name=Jack,age=28,dateJoined=2005-08-05,grade=E5,salary=3000.0]
After salaryincrease:
Employee[name=Tony,age=26,dateJoined=2005-08-05,grade=E4,salary=2400.0]
Employee[name=Michelle,age=24,dateJoined=2005-08-05,grade=E4,salary=2400.0]
Employee[name=Jack,age=28,dateJoined=2005-08-05,grade=E5,salary=3600.0]
========================================
我这里举的是一个相对简单的例子,在Closure这一组还有一些很方便的类,如ChainedClosure可以包装一组Closure作为整体执行;IfClosure在创建时需要提供给它一个Predicate和两个Closure,执行时先做Predicate判定再决定执行哪一个Closure;SwitchClosure跟SwitchTransformer类似,根据创建时传入的Predicate组和Closure组对应执行;WhileClosure则根据创建时传入的Predicate做判断,如果为true则执行Closure,直到Predicate返回false;等等。
具体用法请参考Javadoc。
1.5.9. Iterator组
来看最后一组 – Iterator。
LoopingIterator
ArrayListIterator
FilterIterator
UniqueFilterIterator
IteratorUtils
java.util.Iterator接口定义了标准的Collection遍历方法,但是如果不做改变的使用它,我们得到的是从头到尾一次性的遍历。假如我们需要循环遍历,假如我们需要遍历某一段,假如我们需要遍历满足某些条件的元素,等等等等,我们就不能完全依赖于这个Iterator的标准实现了。除非我们宁可在此基础上在调用的代码中多加一些判断,不过这样的话代码就会显得混乱,时间长了就容易变得难以维护。Commons Collections的这一组Iterator为我们带来了便利。
这些Iterator使用都很一目了然,直接看例子吧:
package sean.study.commons.collections;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.ArrayListIterator;
import org.apache.commons.collections.iterators.FilterIterator;
import org.apache.commons.collections.iterators.LoopingIterator;
import org.apache.commons.lang.StringUtils;
public class IteratorUsage {
public static void main(String[] args) {
demoIteratorUsage();
}
public static void demoIteratorUsage() {
System.out.println(StringUtils.center( " demoClosureUsage " , 40, "=" ));
// data setup
String[] weekDays = {
"Monday" , "Tuesday" , "Wednesday" ,
"Thursday" , "Friday" , "Saturday" , "Sunday"
};
List weekDayList =Arrays.asList(weekDays);
// workdays
Iterator iter1 = new ArrayListIterator(weekDays, 0,5);
printColl( "Partial:" ,iter1, 5);
// loop
Iterator iter2 = new LoopingIterator(weekDayList);
printColl( "Loop:" , iter2, 10);
// looping workdays
Predicate notWeekendPredicate = new Predicate() {
public boolean evaluate(Object obj) {
String str = (String) obj;
if ( "Saturday" .equalsIgnoreCase(str)){
return false;
}
if ( "Sunday" .equalsIgnoreCase(str)){
return false;
}
return true;
}
};
Iterator iter3 = new FilterIterator(
new LoopingIterator(weekDayList),
notWeekendPredicate
);
printColl( "NoWeekends loop:" , iter3, 12);
System.out.println(StringUtils.repeat( "=" , 40));
}
public static void printColl(String label, Iterator iter, int maxCount) {
if (StringUtils.isNotBlank(label)) {
System.out.println(label);
}
int i = 0;
while (iter.hasNext() && i < maxCount){
System.out.println( "# " + iter.next() + " #" );
i++;
}
}
}
运行结果如下:
===========demoClosureUsage ===========
Partial:
# Monday #
# Tuesday #
# Wednesday #
# Thursday #
# Friday #
Loop :
# Monday #
# Tuesday #
# Wednesday #
# Thursday #
# Friday #
# Saturday #
# Sunday #
# Monday #
# Tuesday #
# Wednesday #
No Weekends loop:
# Monday #
# Tuesday #
# Wednesday #
# Thursday #
# Friday #
# Monday #
# Tuesday #
# Wednesday #
# Thursday #
# Friday #
# Monday #
# Tuesday #
========================================
有了这些实用的Iterator类,我们就可以轻松的实现可配置的遍历行为了。
1.6. 结语
在前面的随笔中,我们一起过了一遍Jakarta Commons这个类库中非常重要的三个子项目:Commons Lang、Commons BeanUtils和Commons Collections,这些工具包在无数开源或商业框架中都可以找到,可以说应用范围非常广。
当然,Jakarta Commons提供的API远不止我们提到的这些,除了上述三个核心项目之外,还有读取和映射XML文档的Digester/Betwixt、处理命令行参数的CLI、提供常用编码算法的Codec、用于读取多种格式的配置文件的Configuration、发送电子邮件的Email、上传文件的FileUpload、模拟HTTP客户端的HttpClient、处理日志的Logging等等等等,实在是相当的丰富。
Jakarta Commons官网上可以找到完整的组件列表:
http://jakarta.apache.org/commons/
今后如果发现特别有价值需要跟大家分享的,我还会贴出来,只是不会像这样有条理和规律了。希望我的这一组笔记对大家了解和认识Jakarta Commons有所帮助,也希望这些优秀的类库及其源码能够给大家带来工作效率和编程功底上的提升。