对于所有对象都通用的方法!
对于所有对象都通用的方法!
月伴飞鱼覆盖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 | public String toString() { |
这是默认的
toString
实现,通常情况下,需要和对象相关的更独特的信息,而不是类名和散列值。
使类和成员的可访问性最小
设计良好的组件会隐藏所有的实现细节。
- 这可以有效的解除组件之间的耦合关系,使得这些组件可以独立的开发、测试和修改。
对于顶层的类和接口,只有两种访问级别,包级私有和公有的。
- 类或者接口使用
pulic
修饰就是公有的,否则是包级私有的。- 如果一个包级私有的类只有使用它的类用到,就应该考虑将这个类设计为私有内部类。
对于成员,有四种访问级别,私有的、包级私有、受保护的、公开的。
公有类的实例域决不能被公开,如果实例域是公开的并且是非
final
的,公开之后,就等于放弃了存储在这个域值值的控制能力。对于公开的
final
的数组域,或者提供了返回域的方法也是错误的,会导致数组内容被修改。
- 如果必有,应该将数组域设置为私有,只提供一个数组拷贝。
使可变性最小化
不可变类是指其实例不能被修改的类。
每个实例包含的信息都应该在创建该实例时提供,并且在对象的整个生命周期内不可变。
不可变类更加易于设计、实现和使用,而且不易出错。
设计不可变类遵循以下原则:
不要为类提供
setter
方法。保证类不会被继承,通过
final
修饰类或者构造方法私有的方式。所有域都是
private final
修饰的,在构造时就需要赋值,并且不允许修改。如果类具有指向可变对象的域,需要确保使用该类对象的客户端无法获得指向可变对象的引用。
接口优先于抽象类
对于在设计抽象类时,应该首先考虑一下,这个抽象类能不能设计成为接口。
相比于抽象类,现有的实现了接口的类更容易被更新。
- 因为
Java
语言允许实现多个接口,但是只允许继承一个抽象类。接口虽然可以提供缺省方法,为某些方法提供实现,但是缺省方法仍然有一些缺点:
- 接口无法给
equals、hashCode
等方法提供缺省实现。- 接口中不能包含非公有的静态域或者实例域。
为了结合接口和抽象类的优势,通过对接口提供一个抽象的骨架实现,接口负责定义类型,或者提供一些缺省方法。
- 而骨架实现类则负责提供除基本方法之外的方法。
常量接口是对接口的不良使用
类在内部使用某些常量,属于实现细节,实现常量接口会导致把这样的实现细节泄露到该类导出的API中。
以
java.io.ObjectStreamConstants
为例:
1 | public interface ObjectStreamConstants { |
如果要导出常量,首先考虑这些常量是不是与某个类或者接口紧密相关。
如果是,应该把常量添加在这些接口或者类中。
其次,使用枚举类型导出这些常量。
最后,考虑使用不可实例化的工具类进行导出。
静态成员类优先于非静态成员类
如果成员类没有访问外围类实例的需求,就应该把成员类设计为
static
的。因为非
static
的成员类需要外围实例才能创建,在创建成员类实例以后,成员类实例会持有外围类实例的引用。
- 保留这份引用不但需要消耗空间,而且会导致外围类实例不能被GC,造成内存泄漏。