Apache-Commons库详解(2)--Commons Lang

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的常见用法

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。

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的情况处理很得体),到分割子串,到生成格式化的字符串,使用都很简洁,也很直截了当。

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就成了方便而相对安全的一个方案。

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吧。

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。