对于所有对象都通用的方法!

覆盖equals方法时请遵守通用约定

如果类具有自己逻辑相等的概念,应该覆盖equals方法。

覆盖时,遵守以下约定:

  • 自反性,对于非空性x,x.equals(x)必须返回true。
  • 对称性,对于非空引用x, y, x.equals(y)为true时,y.equals(x)也必须为true。
  • 传递性,对于非空引用x, y, z, x.equals(y)为true,y.equals(z)为true,x.equals(z)也必须为true。
  • 一致性,对于非空引用x, y, x.equals(y)的多次调用结果应该相同。
  • 非空性,非空引用x,x.equals(null)必须为false。

实现高质量equals方法的诀窍:

使用==操作符检查参数是否为当前对象的引用,如果是,则返回true。

使用instanceof操作符检查参数类型是否为当前类型,不是则返回false。

把参数转换为当前类型。

根据逻辑相等的定义,对关键域进行相等判断,如果全部判断相等,则返回true,否则返回false。

对于既不是float也不是double的基本类型,使用==操作符判断。

对于float类型或者double类型,使用java.lang.Float#compare(float f1, float f2)

  • 或者java.lang.Double#compare(double d1, double d2)进行判断。

对于引用类型,可以递归调用引用类型的equals方法。

equals方法不需要对入参进行null检查,因为类型检查会返回false

覆盖equals方法时总要覆盖hashCode方法

这是因为如果两个对象调用equals方法比较是相等的,则hashCode方法返回值也必须一致。

  • 否则该类无法结合所有基于散列的集合一起工作。

比如两个通过equal方法判定为相等的对象,在添加到HashSet中时。

  • 由于没有覆盖hashCode方法,会重复添加,但是根据Set的定义,Set中的元素应该是唯一的,不能有两个 相等 的对象。

始终覆盖toString

1
2
3
public String toString() {  
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

这是默认的toString实现,通常情况下,需要和对象相关的更独特的信息,而不是类名和散列值。

使类和成员的可访问性最小

设计良好的组件会隐藏所有的实现细节。

  • 这可以有效的解除组件之间的耦合关系,使得这些组件可以独立的开发、测试和修改。

对于顶层的类和接口,只有两种访问级别,包级私有和公有的。

  • 类或者接口使用pulic修饰就是公有的,否则是包级私有的。
  • 如果一个包级私有的类只有使用它的类用到,就应该考虑将这个类设计为私有内部类。

对于成员,有四种访问级别,私有的、包级私有、受保护的、公开的。

公有类的实例域决不能被公开,如果实例域是公开的并且是非final的,公开之后,就等于放弃了存储在这个域值值的控制能力。

对于公开的final的数组域,或者提供了返回域的方法也是错误的,会导致数组内容被修改。

  • 如果必有,应该将数组域设置为私有,只提供一个数组拷贝。

使可变性最小化

不可变类是指其实例不能被修改的类。

每个实例包含的信息都应该在创建该实例时提供,并且在对象的整个生命周期内不可变。

不可变类更加易于设计、实现和使用,而且不易出错。

设计不可变类遵循以下原则:

  • 不要为类提供setter方法。

  • 保证类不会被继承,通过final修饰类或者构造方法私有的方式。

  • 所有域都是private final修饰的,在构造时就需要赋值,并且不允许修改。

  • 如果类具有指向可变对象的域,需要确保使用该类对象的客户端无法获得指向可变对象的引用。

接口优先于抽象类

对于在设计抽象类时,应该首先考虑一下,这个抽象类能不能设计成为接口。

相比于抽象类,现有的实现了接口的类更容易被更新。

  • 因为Java语言允许实现多个接口,但是只允许继承一个抽象类。

接口虽然可以提供缺省方法,为某些方法提供实现,但是缺省方法仍然有一些缺点:

  • 接口无法给equals、hashCode等方法提供缺省实现。
  • 接口中不能包含非公有的静态域或者实例域。

为了结合接口和抽象类的优势,通过对接口提供一个抽象的骨架实现,接口负责定义类型,或者提供一些缺省方法。

  • 而骨架实现类则负责提供除基本方法之外的方法。

常量接口是对接口的不良使用

类在内部使用某些常量,属于实现细节,实现常量接口会导致把这样的实现细节泄露到该类导出的API中。

java.io.ObjectStreamConstants为例:

1
2
3
4
5
6
7
8
public interface ObjectStreamConstants {  
static final short STREAM_MAGIC = (short)0xaced;

static final short STREAM_VERSION = 5;

static final byte TC_BASE = 0x70;
// 审略部分代码
}

如果要导出常量,首先考虑这些常量是不是与某个类或者接口紧密相关。

如果是,应该把常量添加在这些接口或者类中。

其次,使用枚举类型导出这些常量。

最后,考虑使用不可实例化的工具类进行导出。

静态成员类优先于非静态成员类

如果成员类没有访问外围类实例的需求,就应该把成员类设计为static的。

因为非static的成员类需要外围实例才能创建,在创建成员类实例以后,成员类实例会持有外围类实例的引用。

  • 保留这份引用不但需要消耗空间,而且会导致外围类实例不能被GC,造成内存泄漏。