对象相关
1.用静态工厂方法代替构造器
优势:
- 有名称。当一个类需要多个带有相同签名的构造器时,使用静态工厂代替,并且仔细选择名称。
- 不必在每次调用的时候都创建一个新对象。有助于类总能严格控制在某个时刻那些实例应该存在。
- 可以返回原返回类型的任何子类型的对象。灵活应用:api可以返回对象,同时又不会使对象的类变成公有的。
- 所返回的对象的类随着每次调用而产生变化,取决于静态工厂方法的参数值。
- 方法返回的对象所属的类,在编写包含该静态工厂方法的类是可以不存在。例:服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多实现中解耦出来。
缺点:
- 类如果不含公有的或者受保护的构造器,就不能被子类化。
- 很难被程序员发现。因为在api文档中,它们没有像构造器那样在api文档中明确标识出来。
2.遇到多个构造器参数时要考虑使用构造器
重叠构造器模式可行,但当有许多参数的时候,客户端代码会很难编写,并且仍然难以阅读。
在构造过程中JavaBean可能处于不一致的状态。JavaBean模式使得把类做成不可变的可能性不存在。
替代方式:建造者模式的一种形式,不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器,得到一个builder对象。然后客户端在builder对象上调用类似setter的方法,来设置每个相关的可选参数
public class A{
private final int a;
private final int b;
public static class Builder{
private final int a;
private int b = 0;
public Builder(int a){
this.a = a;
}
public Builder b(int val){
b=val;
return this;
}
public A build(){
return new A(this);
}
}
private A(Builder builder){
a = builder.a;
b = builder.b;
}
}
A a = new A.Builder(12).b(2).build();
builder 模式也适用于类层次结构。
3.用私有构造器/枚举类强化Singleton属性
1.公有静态成员是一个final域
public class Elvis{
public static final Elvis INSTANCE = new Elvis();
public Elvis(){...}
public void leaveTheBuilding(){...}
}
私有构造器近被调用一次来实例化公有的静态final域
但有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射调用私有构造器。如果需要防御,则修改构造器,在创建第二个实例的时候抛出异常。
优势在于api清楚表明这个类是一个单例模式,更简单。
2.公有的成员是个静态工厂方法
public class Elvis{
public static final Elvis INSTANCE = new Elvis();
public Elvis(){...}
public static Elvis getInstance(){return INSTANCE;}
public void leaveTheBuilding(){...}
}
对于静态方法getInstance的调用都会返回同一个对象引用,所以不会创建其他实例(但仍会被反射调用)
优势
- 提供灵活性。
- 可以编写一个泛型Singleton工厂。
- 可以通过方法引用作为提供者。
3.声明一个包含单个元素的枚举类型
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){...}
}
更加简洁无偿提供序列化机制,绝对防止多次实例化,防止反射攻击。
4.避免创建不必要的类
有些对象创建的成本比其他对象要高得多。
5.消除过期的对象引用
程序中发生内存泄漏的情况:
- 栈内部维护着对象的过期引用(永远也不会再被接触的引用。)
- 一个对象引用被无意识的保留写来,则jvm不仅不会处理这个对象,也不会处理被这个对象所引用的所有其他对象。 修复方式:在一个对象引用过期后,清空对应引用。 清空对象引用应该是一种例外,而不是一种规范行为。
- 缓存,一旦把对象放入缓存中,它就很容易被遗忘。解决方式:由一个后台线程清除缓存中没用的项。
- 监听器以及其他回调。
只要类是自己管理内存,就应该警惕内存泄漏问题。
6.避免使用终结方法和清除方法
终结方法通常是不可预测的,也是很危险的,一般情况下是不必要的。
清除方法没有终结方法那么危险,但仍然是不可预测、运行缓慢,一般情况下也是不必要的。
两者的缺点:
- 不能保证会被及时执行。从一个对象变得不可到达开始,到他的终结方法被执行,所花费的时间是任意长的,意味着注重实践的任务不应该由终结方法或清除方法来完成。
- 不应该依赖终结方法或者清除方法来更新重要的持久状态。
- 有严重的性能损失
- 有严重的安全问题:为终结方法攻击打开了类的大门。从构造器抛出的异常,应该足以防止对象继续存在,有了终结方法的存在,这一点就无法做到了。为了防止非final类受到终结方法攻击,要编写一个空的final的finalize方法。
两者的优点:
- 充当安全网,当资源所有者忘记调用close方法时。
7.try-with-resources优先于try-finally
try-finally根据经验是确保资源会被适时关闭的最佳方法,就算发生异常或者返回时也一样,但如果在添加第二个资源就会一团糟。
而使用try-with-resource时就会解决一切问题。要使用这个必须实现AutoCloseable接口,其中包含了单个返回void的close方法。
8.覆盖equals时遵守通用约定
- 类的每个实例本质上都是唯一的。
- 类没必要提供逻辑对等的测试功能。
- 超类已经覆盖了equals,超类的行为对于这个类也是合适的。
- 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用
equals方法实现了等价关系
- 自反性:对象必须等于其自身。
- 对称性:任何两个对象对于它们是否相等的问题必须保持一致。
- 传递性:如果一个对象等于第二个对象,第二个对象又等于第三个对象,则第一个对象等于第三个对象。
- 一致性:如果两个对象相等,他们就必须保持相等,除了它们中有一个对象(或两个)都被修改了。无论类是否可变,都不要用equals方法依赖于不可靠的资源
- 非空性:所有的对象都不能等于null
提高equals方法
- 使用==操作符检查 参数是否为这个对象的引用
- 使用instanceof检查参数是否为正确的类型
- 把参数转换成正确的类型
- 对于类中每个关键域,检查参数中的域是否与该对象中对应的域向匹配。
- 在编写完equals方法后,问自己3个问题:它是否对称,传递,一致。
要点:
- 覆盖equals时要覆盖hashcode
- 不要让equals过于智能
- 不要将equals声明中的Object对象换成其他类型。
9.覆盖equals是总要覆盖hashcode
在每个覆盖了equals方法的类中,都必须覆盖hashcode方法。如果不这样做就会违反hashcode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作。
要确保相等的对象必须具有相等的散列码(hash code)。不能从散列码计算中排除掉任何一个对象的关键域来提高性能(散列码应有所有关键域组成)
10.始终要覆盖toString
提供好的toString实现可以让类用起来更加舒适,使用这个类的系统也便于调试。应用中toString方法应该返回对象中包含的所有值得关注的信息。