一 考虑用静态工厂方法代替构造器
1、静态工厂方法的几大优势①静它们有名称,如:BigInteger.probablePrime②不必在每次调用都创建一个新的对象。静态工厂模式能够为重复的调用返回相同对象,这样有助于总能严格控制在某个时刻哪些实例应用改存在,这种类被称作实例受控的类。当a==b时a.equals(b)。③它们返回原返回类型的任何子类型对象。基于接口的框架,在这种框架中,接口为静态工厂方法提供了自然返回类型。④在创建参数化类型实例的时候,它们使代码变得更加简洁。
2、静态工厂方法的去点:①类如果不含有共有的或者受保护的构造器,就不能被子类化,这样会鼓励程序员使用符合而不适用继承。②它们与其他静态方法实际上没有任何区别。
3、静态工厂方法和公有构造器都各有用处,静态工厂方法通常更加合适,因此切记第一反应就是提供公有的构造器,而不优先考虑静态工厂方法。
二 遇到多个构造器参数时要考虑用构建器
1、静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。
2、重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且难以阅读。
3、替代模式一,使用javabeans模式,首先调用一个无参的构造器来创建对象,然后调用setter方法来设置每个必要的参数。但是这存在严重的缺陷,因为在调用过程当中javabean可能处于不一致的状态。
4、创建者模式也有其自身的不足,为了创建对象,必须先创建它的构建器,在某些性能要求比较高的情景下,这种方式不是首选。
5、如果类的构造器或者静态工厂方法当中含有多个参数,设计这个类时,建造者模式是一个不错的选择。
package com.object;
// Builder Pattern - Pages14-15
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize,int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; returnthis; }
public Builder fat(int val)
{ fat = val; returnthis; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; returnthis; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builderbuilder) {
servingSize = builder.servingSize;
servings =builder.servings;
calories =builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola = newNutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
}
}
1、使类成为Singleton会使它的客户端测试变得十分困难。
2、为了防止多次实例化,面对复杂的序列化和反射机制,使用枚举类型不失为一种很好的办法,单元素的枚举类型是实现单例模式最佳方案。
3、第一种方案
package com.object.field;
// Singleton with publicfinal field - Page 17
public class Elvis {
public static final Elvis INSTANCE =new Elvis();
private Elvis() { }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// Thiscode would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
4、第二种方案
package com.object.method;
// Singleton with staticfactory - Page 17
public class Elvis {
private static final Elvis INSTANCE =new Elvis();
private Elvis() { }
public static Elvis getInstance() { returnINSTANCE; }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// Thiscode would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.getInstance();
elvis.leaveTheBuilding();
}
}
四 通过私有化构造器强化不可实例化的能力
1、企图通过将类做成抽象类来强制改类不可能实例化,这是行不通的。
2、示例,这里例子避免了其使用反射机制反射新的实例。
public class UtilityClass {
// Suppressdefault constructor for noninstantiability
privateUtilityClass() {
throw new AssertionError();
}
}
3、所有的构造器必须显式或隐式地调用超类的构造器。这种情况下,子类不可能访问超类的构造器。
五 避免创建不必要的对象
1、一般来说,最好能重用对象而不是每次需要的时候就创建一个相同功能的对象。
2、不建议进行延迟初始化操作。因为这样做会使方法的实现更加复杂,从而无法将性能显著提高到超过已经达到的水平。
3、要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。如下例:
public class Sum {
// Hideouslyslow program! Can you spot the object creation?
publicstatic void main(String[] args) {
Long sum = 0L;
for(long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;//每次都要构建Long sum实例
}
System.out.println(sum);
}
}
4、维护对象池来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。数据库连接池就是这样的一个实例。
5、注意,这一条目与保护性拷贝并不违背,因为如果依照这一条目而少创建对象或者不创建对象,不遵循保护性拷贝的思想,那么会造成潜在的BUG!
六 消除过去的对象引用
1、所谓的过期引用,是指永远不会再被解除的引用,这种引用直径影响着JVM对对象的回收,造成内存泄露。(无意识的对象保持)
2、JVM只回收那些没有任何引用再指向它的对象。如果一个引用一直指向一个对象,而这个引用在以后的时间里不会再使用到,那么就会造成内存泄露。
3、消除过期引用最好的方法是让包含该引用的变量结束其生命周期。如果再最紧凑的作用域范围内定义每一个变量,这种情形自然会发生,这也是《将局部变量的作用域最小化》所提倡的。
4、①一般而言,只要类是自己管理内存,程序员就应该警惕内存泄露问题。
5、②内存泄露的另一个常见来源是缓存。一旦把对象引用放到缓存中,就很容易被遗忘掉。使用WeakHashMap可以避免这一点。只有当所有的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。
6、③内存泄露的第三个常见来源是监听器和其他回调。如果实现了一个API,客户端在这个API中注册回调,却没有现实地取消,那么除非你采取动作,否则它们就会积聚。确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用,例如可以将它们保存成WeakHashMap中的键。???
7、可以利用heapprofiler对栈内信息进行检查
七 避免使用终结方法(深入理解java的finalize)
1、终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定、降低性能,以及可移植性问题。根据经验,应该避免使用终结方法。
2、java的语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。不应该依赖于终结方法来更新重要的持久状态。System.gc和System.runFinalization这两个方法趋势增加了终结方法被执行的机会,但是它们并不保证终结方法一定会被执行。
3、使用终结方法有一个非常严重的性能损失。
4、显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止。
Foo foo = new Foo();
try {
} finally {
foo.terminate();
}
显式终止方法的典型例子是InputStream、OutputStream或者Connection上的close()方法。
5、子类如果覆盖了父类的finalizer,子类应该手工调用父类的finalizer,应该放在finally里,以确保子类finalize时出异常时,父类finalizer仍被调用。
利用匿名类,终结外围实体(enclosing instance),外围实体在一个private instance中存储一个reference,指向终结函数守卫者(finalizer guardian),于是,finalizer guardian在外部实体存在时也存在,当guardian被终结时,它执行外围实体所期望的终结动作。
例如:
public class Foo{
private final object finalizerGuardian = new Object(){
protected void finalize() throws Throwable{
//终结外围实体
}
}
}
八 覆盖equals时请遵守通用约定
1、equals的约定,指示其他某个对象是否与此对象“相等”。 (逻辑上相等)
equals 方法在非空对象引用上实现相等关系:
- 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
- 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
- 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
- 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
- 非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。
Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
2、高质量equals方法的诀窍:
①使用==操作符检查“参数是否为这个对象的引用”。
②使用instanceof操作符检查“参数是否为正确的类型”。
③把参数转换为正确的类型。因为转换之前进行过instanceof测试,所以确保成功。
④对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。
⑤当编写完equals方法之后,应该问自己三个问题,它是否是对称的、传递的、一致的。
3、对于既不是float也不是double类型的基本类型域,可以使用==操作符进行比较;对于对象引用域,可以递归地调用equals方法;对于float域,可以使用Float.compare方法;对于double域,则使用Double.compare。对float和double域进行特殊处理是有必要的,因为存在着Float.NaN、-0.0f以及类似的double常量。
4、 NaN,是Not a Number的缩写。
NaN 用于处理计算中出现的错误情况,比如 0.0 除以 0.0 或者求负数的平方根。由上面的表中可以看出,对于单精度浮点数,NaN 表示为指数为 emax + 1 = 128(指数域全为 1),且尾数域不等于零的浮点数。IEEE 标准没有要求具体的尾数域,所以 NaN 实际上不是一个,而是一族。不同的实现可以自由选择尾数域的值来表达 NaN,比如 Java 中的常量 Float.NaN 的浮点数可能表达为01111111110000000000000000000000,其中尾数域的第一位为 1,其余均为 0(不计隐藏的一位),但这取决系统的硬件架构。Java 中甚至允许程序员自己构造具有特定位模式的 NaN 值(通过 Float.intBitsToFloat() 方法)。比如,程序员可以利用这种定制的 NaN 值中的特定位模式来表达某些诊断信息。
5、compareTo:比较两个Float 对象所表示的数值。在应用到基本 float 值时,有两种方法可以比较执行此方法产生的值与执行 Java 语言的数字比较运算符(<、<=、== 和 >= >)产生的那些值之间的区别:
- 此方法认为 Float.NaN 等于其自身,且大于其他所有 float 值(包括 Float.POSITIVE_INFINITY)。
- 此方法认为 0.0f 大于 -0.0f。
这可以确保受此方法影响的 Float 对象的自然顺序与 equals 一致。
6、compare:比较两个指定的 float 值。返回整数值的符号与以下调用返回整数的符号相同:newFloat(f1).compareTo(new Float(f2))
7、注意:
①覆盖equals时总要覆盖hashCode
②不要企图equals方法过于智能。
③不要将equals声明中的Object对象替换为其他的类型。
九 覆盖equals时总要覆盖hashCode
1、在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列表的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable提供的哈希表)的性能。
hashCode 的常规协定是:
- 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
- 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
2、编写一个合法但是不好用的hashCode没有任何代价,如
@override public int hashCode(){return 1;}
上面这个hashCode方法是合法的,因为它确保了相等的对象总是具有相同的散列码。但是也极为不好,因为他们使用了相同的散列码。因此每个对象都被映射到同一个散列桶中,使得列表退化为链表。它使得本应该在线性时间运行的程序变成了以平方级时间在运行。对于大规模的散列表而言,这会关系到散列表能否正常工作。
3、编写HashCode方法
1. 把某个非零常数值,比如说17,保存在一个叫result的int类型的变量中。
2. 对于对象中每一个关键域f(指equals方法中考虑的每一个域),完成以下步骤:
a) 为该域计算int类型的散列码c:
i. 如果该域是boolean类型,则计算(f?0:1)
ii. 如果该域是byte、char、short或者int类型,则计算(int)f。
iii. 如果该域是long类型,则计算(int)(f^(f>>>32))
iv. 如果该域是float类型,则计算Float.floatToBits(f)。
v. 如果该域是double类型,则计算Double.doubleToLongBits(f)得到一个long类型的值,然后按照步骤2.a.iii,对该long型值计算散列值。
vi. 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals的方式来比较这个域,则同样对这个域递归调用hashCode。如果要 求一个更为复杂的比较,则为这个域计算一个“规范表示(canonicalrepresentation)”,然后针对这个范式表示调用hashCode。如果这个域的值为null,则返回0(或者其他某个常数,但习惯上使用0)。
vii. 如果该域是一个数组,则把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来。
b) 按照下面的公式,把步骤a中计算得到的散列码c组合到result中:
3. 返回result。
4. 写完hashCode方法之后,问问自己“相等的实例是否都具有相等的散列码”。编写单元测试进行验证。
示例代码:
package com.methods;
// Shows the need for overriding hashcode when you override equals - Pages 45-46
import java.util.*;
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix,
int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
private static void rangeCheck(int arg, int max,
String name) {
if (arg < 0 || arg > max)
throw new IllegalArgumentException(name +": " + arg);
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
// Broken - no hashCode method!
// A decent hashCode method - Page 48
@Override public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
//因为计算散列码开销比较大,应该考虑把散列码缓存在队形内部,而不是每次都计算。
// Lazily initialized, cached hashCode - Page 49
// private volatile int hashCode; // (See Item 71)
//
// @Override public int hashCode() {
// int result = hashCode;
// if (result == 0) {
// result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// hashCode = result;
// }
// return result;
// }
public static void main(String[] args) {
Map<PhoneNumber, String> m
= new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
}
}
4、不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。虽然这可以更快的得到散列码,但是它可能导致散列表越来越慢。
十 始终要覆盖toString
1、当你调用某个没有复写toString方法的类的toString方法时,它返回:包含类的名称以及一个“@”符号,接着是其散列码的无符号十六进制表示法。例如:
PhoneNumber@135b98。
2、虽然toString的约定并不像遵守equals和hashCode的约定那么重要,但是,提供好的toString实现可以使类使用起来更加舒适。在实际应用中,toString方法应该返回对象中值得关注的信息。
十一 谨慎地覆盖clone
1、推荐文章Clone使用方法详解,《Effective Java》当中写的太繁琐,读这篇文章绝对醍醐灌顶!!
2、①产生:引用总是在把对象作参数"传递"的过程中自动发生,不需要人为的产生,也不能人为的控制引用的产生。这个传递包括把对象作为函数的入口参数的情况,也包括用"="进行对象赋值的时候。
②范围:只有局部的引用,没有局部的对象。引用在Java语言的体现就是变量,而变量在Java语言中是有范围的,可以是局部的,也可以是全局的。
③生存期:程序只能控制引用的生存周期。对象的生存期是由Java控制。用"new Object()"语句生成一个新的对象,是在计算机的内存中声明一块区域存储对象,只有Java的垃圾收集器才能决定在适当的时候回收对象占用的内存。 没有办法阻止对引用的改动。
3、示例
public final class PhoneNumberimplements Cloneable {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode,int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber)super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Can't happen
}
}
public static void main(String[] args) {
PhoneNumber pn = new PhoneNumber(707, 867, 5309);
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(pn, "Jenny");
System.out.println(m.get(pn.clone()));
}
}
注意:①除了基本数据类型能自动实现深度clone以外,String对象,Integer,Double等是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。
②当一个类中处理基本数据类型之外还有其他对象类型时,不能需要进一步使得对象类型实现clone之后调用其clone以完成本类的克隆操作。
4、应该知道的是在Java中所有的基本数据类型都有一个相对应的类,象Integer类对应int类型,Double类对应double类型等等,这些类也与String类相同,都是不可以改变的类。也就是说,这些的类中的所有方法都是不能改变其自身的值的。这也让我们在编clone类的时候有了一个更多的选择。同时我们也可以把自己的类编成不可更改的类。
5、一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包, java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。
另一个值得请注意的是重载了clone()方法。最 后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调 用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。
应该说第三点是最重要的,仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非 native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了 clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方 法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为 了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。
那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且 这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
十二 考虑实现Comparable接口
1、Comparable 用作默认的比较方式
Comparator 用作自定义的比较方式,当默认的比较方式不适用时或者没有提供默认的比较方式,使用Comparator就非常有用。
像Arrays和Collections中的排序方法,当不指定Comparator时使用的就是默认排序方式,也就是使用Comparable。指定Comparator时就是使用提供的比较器。
sort(Object[]) 所有的对象都必须实现Comparable接口,它用来确定对象之间的大小关系
sort(Object[], Comparator) 对象不必实现Comparable接口,由Comparator来确定对象之间的大小关系。
可以这样比喻Comparable是正室,而Comparator是妃子。
2、推荐文章ComparableComparator具体区别
十三 使类和成员的可访问性最小化
1、区别好的模块设计与不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其中内部数据和其他实现细节。良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。这个概念被成为信息隐藏(information hiding)或封装(encapsulation),是软件设计的基本原则之一。
2、信息隐藏只所以非常重要有许多原因,其中大多数理由都源于这样一个事实:
他可以有效的解除系统组成各模块之间的耦合关系,是的这些模块可以独立的开发、测试、优化、使用、理解和修改。这样可以加快系统开发的速度,因为这些模块可以并发开发。他也减轻了维护的负担,因为程序员可以更快的理解这些模块,并且在调试他们的时候不影响其他的模块。虽然信息隐藏本身无论是对内还是对外,都不会带来更好的性能,但是他可以有效的调节性能:一旦完成一个系统,并通过剖析哪些模块影响了系统性能,那些模块可以进一步优化,而不影响到其他模块的正确性。信息隐藏提高了软件的,因为模块之间并不紧密相连,除了开发这些模块所使用的坏境之外,他们在其他坏境中往往也很有用。最后,信息隐藏也降低了构建大型系统的风险,因为即使整个系统不可用,但是这些独立的模块却有可能是可用的。
3、Java程序设计提供了很多机制(facility)来协助信息隐藏。
访问控制机制(access control)决定了类、接口和成员的可访问性(accessibility)。尽可能地使每个类或者成员不被外界访问。
4、对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别:
①私有的(private)--只要在生命该成员的顶层类内部才可以访问这个成员。
②包级私有的(package-private)--声明该成员的包内部的任何类都可以访问这个成员。从技术上讲,也也称为“缺省(default)访问级别”。
③受保护的(protected)--比包私有的多了个子类可以访问。其包含了包级私有。
④共有的(public)--在任何地方都可以访问该成员。
5、实例域决不能是公有的。如果域是非final的,或者是一个指向可变对象的final引用,那么一旦是这个域成为共有的,就放弃了对存储在这个域中的值限制的能力。
包含共有可变域的类并不是线程安全的。
类具有共有的静态的final数组域,或者返回这个域的访问方法,这几乎总是错误的。
可以使共有数组变成私有的,并返回一个公有的不可变列表。
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Ting> VALUES =Collecations.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
或者:
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
returnPRIVATE_VALUES.clone;
}
Demo:
package cn.partner4java.test;
import java.util.LinkedList;
import java.util.List;
public class ListTest {
privateList<String> names = new LinkedList<String>();
publicList<String> getNames() {
returnnames;
}
publicvoid setNames(List<String> names) {
this.names= names;
}
publicList<String> getCloneNames() {
List<String>namesClone = new LinkedList<String>();
namesClone.addAll(names);
returnnamesClone;
}
publicstatic void main(String[] args) {
ListTestlistTest = new ListTest();
listTest.getNames().add("wang");
listTest.getNames().add("chang");
listTest.getNames().add("long");
List<String>namesG = listTest.getNames();
List<String>namesCG = listTest.getCloneNames();
namesG.set(2,"ming");
System.out.println(listTest.getNames());
System.out.println(namesG);
System.out.println(namesCG);
// 后台打印:
// [wang,chang, ming]
// [wang, chang, ming]
// [wang, chang, long]
}
}
十四 在公有类中使用访问方法而非公有域(javabean)
1、如果类可以在他所在的包的外部进行访问,就提供访问方法。
2、如果类是包私有的,或者私有的嵌套类,直接暴露他的数据域并没有本质的错误。
3、总之,公有类永远都不应该暴漏可变的域。虽然还是有几个问题,但是让公有类暴漏不可变的域,起危害比较小。但是,有时候需要会用到包级私有的或者私有的嵌套类来暴漏域,无论这个类是可变还是不可变。