近段时间,对apache commons lang的源码做了深入的了解,在此把一些见解与大家分享。
首先我选择了大部分框架还依赖的3.2版本而不是最新的3.4版本进行源码的研读,今天就简介一下commons lang的创建者模式部分。
首先我们来看一下该模块类调用关系:
创建者模式部分主要是使用创建者模式完成一些Object类中的共用方法,比如说toString()方法、equals()方法、hashCode()方法等。其出彩之处在于使用了创建者模式使其功能的扩展十分灵活,并且通过反射获取实体字段也让功能更加便捷。
大体来说创建者模式部分分为Builder接口、ToString部分、Compare部分、Equals部分。
Builder接口:
org.apache.commons.lang3.builder.Builder
Builder接口是设计来指定一个类作为创建者模式的构建对象的。创建者类有创建和配置对象或结果的能力,它分多步进行构造,或进行复合组成。
Builder接口定义了一个build()方法,每个实现了都必须实现。该方法的返回结果在创建操作执行完毕之后应该是配置完成的。
此处的最佳实践是该方法提供用于配置的对象或者在执行创建时返回一个创建者的引用使一套配置可多次创建。
创建者案例:
class FontBuilder implements Builder<Font> {
private Font font;
public FontBuilder(String fontName) {
this.font = new Font(fontName, Font.PLAIN, 12);
}
public FontBuilder bold() {
this.font = this.font.deriveFont(Font.BOLD);
return this; // Reference returned so calls can be chained
}
public FontBuilder size(float pointSize) {
this.font = this.font.deriveFont(pointSize);
return this; // Reference returned so calls can be chained
}
// Other Font construction methods
public Font build() {
return this.font;
}
}
创建者使用案例:
Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
.size(14.0f)
.build();
ToString部分:
org.apache.commons.lang3.builder.ToStringStyle
该类为ToStringBuilder控制字符串格式的控制器。其最重要的接口Builder是由ToStringBuilder另外实现的。
该类内部定义了许多使用单例模式的默认StringStyle内部类。因此用户无需实例化新style。用户应用程序使用该类中预定义的各种常量即可。如果用户希望自定义style只需使用StandardToStringStyle即可。但该类实现了大部分style因此无需再创建子类。
如果需要,子类可按需重写或多或少该类中的方法。每一个对象类型(从基本数据类型到对象数据类型,甚至数组)都有它自身的输出方法。大部分有两种输出方法,一种是详情,一种是概要。
例如,数组类的详情基础方法会输出整个数组,然而概要方法仅输出数组长度。
如果你想格式化输出一个已知的对象,例如时间,你必须创建一个子类并重写方法。
public class MyStyle extends ToStringStyle {
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (value instanceof Date) {
value = new SimpleDateFormat("yyyy-MM-dd").format(value);
}
buffer.append(value);
}
}
该类只是处理由ToStringBuilder传入的单个字段的显示样式。
其内部主要实现了弱引用,以免在生成字符串的过程中循环引用,对象无法回收。在处理对象内容时其将内容分为前后两部分,前部分包括类名以及哈希码,对应start部分;后部分这是传入的字段以及值了。后部分主要通过appendInternal()进行处理,然后其调用底层具体方法加入到字符串中。
该类还通过调用ClassUtils.getShortClassName来获取类短名。
该类还通过调用ObjectUtils.identityToString来将内存地址哈希码变为字符串形式。
org.apache.commons.lang3.builder.ToStringStyle#DefaultToStringStyle
服务于ToStringBuilder来创建toString方法。
该类使用单例模式,因此用户无需实例化新对象。
一旦实例化,就会存在于内存中,供用户进行调用。
该类实现了[name=John Doe,age=33,smoker=false]样式的输出
org.apache.commons.lang3.builder.ToStringStyle#NoFieldNameToStringStyle
服务于ToStringBuilder来创建toString方法。
该类使用单例模式,因此用户无需实例化新对象。
一旦实例化,就会存在于内存中,供用户进行调用。
该类实现的样式不显示字段名。
org.apache.commons.lang3.builder.ToStringStyle#ShortPrefixToStringStyle
服务于ToStringBuilder来创建toString方法。
该类使用单例模式,因此用户无需实例化新对象。
一旦实例化,就会存在于内存中,供用户进行调用。
该类实现的样式类名显示为短名。
org.apache.commons.lang3.builder.ToStringStyle#MultiLineToStringStyle
服务于ToStringBuilder来创建toString方法。
该类使用单例模式,因此用户无需实例化新对象。
一旦实例化,就会存在于内存中,供用户进行调用。
该类实现了每个字段显示一行的样式。
该类使用SystemUtils.LINE_SEPARATOR获取系统的行分隔符,以提高可移植性。
org.apache.commons.lang3.builder.StandardToStringStyle
服务于ToStringBuilder来创建toString方法。
该类使用单例模式,因此用户无需实例化新对象。
一旦实例化,就会存在于内存中,供用户进行调用。
如果用户希望自定义样式,可继承该类进行扩展。
org.apache.commons.lang3.builder.ToStringBuilder
当重写Object.toString()方法时,可用该类进行辅助实现。
该类可为任何类或对象创建良好且一致的toString()方法。该类通过以下过程简化操作:
a. 处理字段名
b. 一致处理所有类型
c. 一致处理null
d. 可输出数组或多维数组
e. 对对象以及集合的细节程度可控制
f. 处理类继承结构
可编写如下代码使用该类:
public class Person {
String name;
int age;
boolean smoker;
...
public String toString() {
return new ToStringBuilder(this).
append("name", name).
append("age", age).
append("smoker", smoker).
toString();
}
}
此代码可产生字符串如下
[email protected][name=Stephen,age=29,smoker=false]
使用appendSuper可增加父类的toString()结果。
还可使用appendToString()添加其他对象的toString()结果。
此外,还有使用反射确定字段以进行文本设置的方法。因为这些字段通常是私有的,reflectionToString方法使用AccessibleObject.setAccessible来改变字段的访问权限。在安全管理下它将会失败,除非适当的权限被正确地设置。可以肯定,它将比明确字段的文本设置要慢。
该方法典型的调用应该像如下:
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
你也可以使用builder来调试第三方对象:
System.out.println("An object: " + ToStringBuilder.reflectionToString(anObject));
toString的具体格式由ToStringStyle在构造方法中确定。
org.apache.commons.lang3.builder.ReflectionToStringBuilder
当使用反射重写Object.toString()方法时,可用该类进行辅助实现。
该类使用反射来确定要打印的字段。由于实体字段通常为pricate权限,该类使用AccessibleObject.setAccessible()改变字段的访问权限。在安全管理下它将会失败,除非适当的权限被正确地设置。
使用反射来访问(私有)字段可在回避同步保护机制的情况下访问这些字段。如果toString方法不能安全地读取字段,你排除调用其来辅助该类toString操作,或使用其同步机制利用其类的锁来管理整个请求过程。需要格外小心地排除线程不安全的集合类,因为这些类在修改字段时可能抛出ConcurrentModificationException。
该方法典型的调用应该像如下:
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
你也可以使用builder来调试第三方对象:
System.out.println(“An object:“ + ReflectionToStringBuilder.toString(anObject));
子类可通过重写以下方法来控制字段的输出:
accept(java.lang.reflect.Field)
getValue(java.lang.reflect.Field)
例如,该方法的返回字符串则不包括password字段:
public String toString() {
return (new ReflectionToStringBuilder(this) {
protected boolean accept(Field f) {
return super.accept(f) == !f.getName().equals(“password”);
}
}).toString();
}
toString的具体格式由ToStringStyle在构造方法中确定。
在处理过程中,可以设置父类边界,然后会循环读取父类的字段,直至边界为止。取出字段后会修改访问权限,然后获取字段值进行toString()处理。
Compare部分:
org.apache.commons.lang3.builder.CompareToBuilder
当实现Comparable.compareTo()方法时,可用该类进行辅助实现。
该类与EqualsBuilder以及HashCodeBuilder构建equals()以及hashcode()是协同一致的。
两个对象如果使用compareTo()是相等的,则使用equals()应该也是相等的。
所有相关的字段都应该包括在比较的算法当中。排除的字段将被忽略。同一个字段,在同一个顺序下,应该使用相同的compareTo()以及equals()方法。
可像如下使用该方法:
public class MyClass {
String field1;
int field2;
boolean field3;
...
public int compareTo(Object o) {
MyClass myClass = (MyClass) o;
return new CompareToBuilder()
.appendSuper(super.compareTo(o)
.append(this.field1, myClass.field1)
.append(this.field2, myClass.field2)
.append(this.field3, myClass.field3)
.toComparison();
}
}
此外,还有使用reflectionCompare()方法,反射确定字段以进行字段添加的方法。因为这些字段通常是私有的,reflectionCompare方法使用AccessibleObject.setAccessible来绕过访问权限控制的检查。在安全管理下它将会失败,除非适当的权限被正确地设置。可以肯定,它将比明确字段的比较慢。
该方法典型的调用应该像如下:
public int compareTo(Object o) {
return CompareToBuilder.reflectionCompare(this, o);
}
在处理过程中,可以设置父类边界,然后会循环读取父类的字段,直至边界为止,该部分由reflectionCompare()处理。取出字段后会修改访问权限,然后获取字段值进行比较处理,该部分由最底层的reflectionAppend()完成。
Equals部分:
相等根据Object类中的表示,两个类只有hashcode相等时,equals才相等,反之亦然。因此我们将Equals以及HashCode放在一部分来说。
org.apache.commons.lang3.builder.IDKey
封装了System.identityHashCode(),因此可直接使用该类进行equals()判断。该类还消除了偶尔出现的重复哈希码问题,比较更准确。
org.apache.commons.lang3.builder.HashCodeBuilder
当实现Comparable.hashCode()方法时,可用该类进行辅助实现。
该类能为任何类创建一个良好的hashCode方法。该类遵循Joshua Bloch所作的《Effective
Java》中记录的规则。编写一个好的hashCode方法实际上是相当困难的。该类期望可以便捷地处理好。
以下是所采用的方法。当添加一个字段时,总结果将乘以乘数以得出新的总结果,然后一个新的值被用于计算。例如,如果当前哈希码是17,乘数是37,然后增加整型45将创建哈希码674,因为17
* 37 + 45。
所有对象关联的字段都应该被包含在hashCode方法中。子字段将不被包含在内。一般情况下,equals方法所使用的任何字段都必须在hashCode中使用。
可像如下使用该方法:
public class Person {
String name;
int age;
boolean smoker;
...
public int hashCode() {
// you pick a hard-coded, randomly chosen, non-zero, odd number
// ideally different for each class
return new HashCodeBuilder(17, 37).
append(name).
append(age).
append(smoker).
toHashCode();
}
}
如果需要使用父类hashCode(),可以通过appendSuper方法进行使用。
此外,还有使用反射确定字段以进行字段添加的方法。因为这些字段通常是私有的,reflectionCompare方法使用AccessibleObject.setAccessible来绕过访问权限控制的检查。在安全管理下它将会失败,除非适当的权限被正确地设置。可以肯定,它将比明确字段的比较慢。
该方法典型的调用应该像如下:
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
在处理过程中,可以设置父类边界,然后会循环读取父类的字段,直至边界为止,该部分由reflectionHashCode()处理。取出字段后会修改访问权限,然后获取字段值进行比较处理,该部分由最底层的reflectionAppend()完成。
org.apache.commons.lang3.builder.EqualsBuilder
当实现Object.equals()方法时,可用该类进行辅助实现。
类遵循Joshua Bloch所作的《Effective Java》中记录的规则。特别地,比较doubles、floats、arrays比较复杂。确保equals()和hashCode()一致也是困难的。
两个对象进行比较时,如果相等则必须生成相同的哈希码,但两个对象有相同的哈希码不一定相等。
所有对象关联的字段都应该被包含在equals方法中。子字段将不被包含在内。特别地,equals方法所使用的任何字段都必须在hashCode中使用,反之亦然。
可像如下使用该方法:
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) {
return false;
}
MyClass rhs = (MyClass) obj;
return new EqualsBuilder()
.appendSuper(super.equals(obj))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
此外,还有使用反射确定字段以进行字段添加的方法。因为这些字段通常是私有的,reflectionEquals方法使用AccessibleObject.setAccessible来绕过访问权限控制的检查。在安全管理下它将会失败,除非适当的权限被正确地设置。可以肯定,它将比明确字段的比较慢。
该方法典型的调用应该像如下:
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
其中getRegisterPair()方法只是构造一个Pair而不是从Register里面去除Pair,该方法名容易产生误解。
ONE FOR IT是一个呆萌CTO打理的资讯读物,每天只为你准备一篇IT行业新鲜资讯。互联网的前沿,一篇就够了。(ID:OFI)