当类为值类(以类中保存的值来区别两个实例)时(枚举例外),需要重写 equals()
和 hashCode()
方法。
重写 equals()
需要遵守的约定:
- 非空。
x != null
- 自反。
x.equals(x) == true
- 对称。
if(x.equals(y)) y.equals(x)
- 传递。
if(x.equals(y) && y.equals(z)) x.equals(z)
- 一致。 只要对象不变,每次调用必须能返回相同的结果。
Tips:
- 增加值组件时使用符合而不是继承。
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || !(o instanceof Point)) {
return false;
}
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(Point point, Color color) {
this.point = point;
this.color = color;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || !(o instanceof ColorPoint)) {
return false;
}
ColorPoint that = (ColorPoint) o;
if (point != null ? !point.equals(that.point) : that.point != null) {
return false;
}
return color != null ? color.equals(that.color) : that.color == null;
}
@Override
public int hashCode() {
int result = point != null ? point.hashCode() : 0;
result = 31 * result + (color != null ? color.hashCode() : 0);
return result;
}
}
- 对于
float
和double
类型的值进行特殊处理Float.compare(f1, f2)
- 域的比较顺序有可能会影响性能,所以应先比较最有可能不同的域及开销最低的域。
- 总是要覆盖
hashCode()
方法。
约定:
- 在应用程序执行过程中,只要
equals()
方法未更改,同一个对象调用hashCode()
返回结果应该一致。 if(x.equals(y)) x.hashCode() == y.hashCode();
实现约定:
- 将一个非 0 常量赋给
result
。 - 计算关键域的 hashCode
boolean
-->field ? 1 : 0;
Byte | char | short | int
-->(int)field
long
-->(int)(field ^ (field >>> 32))
float
-->Float.floatToIntBits(field)
double
-->Double.doubleToLongBits(field)
--> (long --> int)- 对象引用 --> 直接调用
hashCode()
- 数组 -->
Arrays.hashCode()
;
result = 31 * result + hashCode;
- 检验相等实例是否有相同 hashCode。
public class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(short areaCode, short prefix, short lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "lineNumber");
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
private void rangeCheck(int arg, int max, String name) {
if(arg < 0 || arg > max){
new IllegalArgumentException(name + ": " + arg);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || !(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber that = (PhoneNumber) o;
return lineNumber == that.lineNumber
&& areaCode == that.areaCode
&& prefix == that.prefix;
}
@Override
public int hashCode() {
int result = (int) areaCode;
result = 31 * result + (int) prefix;
result = 31 * result + (int) lineNumber;
return result;
}
@Override
public String toString() {
return "PhoneNumber{" +
"areaCode=" + areaCode +
", prefix=" + prefix +
", lineNumber=" + lineNumber +
'}';
}
}
Tips:
- 删除冗余域。
- 计算 hashCode 开销较大时,可以将其缓存到类内部。创建时计算或首次调用
hashCode()
时计算。
- 使用
enum
代替 int 常量 - 用实例域代替序数。
- 将特定枚举常量关联到特定的 int 值。
- 使用
EnumSet
代替位域- 位域:使用或运算将多个常量合并到一个集合中。
text.applyStyles(STYLE_BOLD | STYLE_ITALIC)
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC))
- 位域:使用或运算将多个常量合并到一个集合中。
- 使用
EnumMap
代替序数索引Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);
- 多维:
EnumMap<..., EnumMap<...>>
- 检查参数有效性
public
方法使用异常private
方法使用断言
- 必要时进行保护性拷贝
- getXXX()
- setXXX()
- constructor
- 谨慎设计方法签名
- 命名方式要统一
- 参数不宜超过 3 个。可以通过创建对象来传递多个参数
- 参数类型优先使用接口
- 慎用重载
- 重载方法是在编译期确定的(静态绑定)
- 慎用可变参数
- 返回 0 长度的数组或集合,而不是
null
- 为所有导出的 API 元素编写文档注释
- 将局部变量的作用域最小化
- 第一次使用时声明。
for-each > for > while
- 对含有一组元素的数据结构,实现
Iterable
接口 - 使用最合适的类型来存储数据
- 使用
StringBuilder
来连接字符串 - 通过接口引用对象(面向接口编程)