9、优化自定义hasCode()方法和equals()方法
在不能使用EnumMap的情况下,至少也要优化 hashCode() 和 equals() 方法。一个好的 hashCode() 方法是很有必要的,因为它能防止对高开销 equals() 方法多余的调用。
在每个类的继承结构中,需要容易接受的简单对象。让我们看一下jOOQ的 org.jooq.Table 是如何实现的?
最简单、快速的 hashCode() 实现方法如下:
// AbstractTable一个通用Table的基础实现:
@Override
public int hashCode() {
// [#1938] 与标准的QueryParts相比,这是一个更加高效的hashCode()实现
return name.hashCode();
}
name即为表名。我们甚至不需要考虑schema或者其它表属性,因为表名在数据库中通常是唯一的。并且变量 name 是一个字符串,它本身早就已经缓存了一个 hashCode() 值。
这段代码中注释十分重要,因继承自 AbstractQueryPart 的 AbstractTable 是任意抽象语法树元素的基本实现。普通抽象语法树元素并没有任何属性,所以不能对优化 hashCode() 方法实现抱有任何幻想。覆盖后的 hashCode() 方法如下:
// AbstractQueryPart一个通用抽象语法树基础实现:
@Override
public int hashCode() {
// 这是一个可工作的默认实现。
// 具体实现的子类应当覆盖此方法以提高性能。
return create().renderInlined(this).hashCode();
}
换句话说,要触发整个SQL渲染工作流程(rendering workflow)来计算一个普通抽象语法树元素的hash代码。
equals() 方法则更加有趣:
// AbstractTable通用表的基础实现:
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
// [#2144] 在调用高开销的AbstractQueryPart.equals()方法前,
// 可以及早知道对象是否不相等。
if (that instanceof AbstractTable) {
if (StringUtils.equals(name,
(((AbstractTable<?>) that).name))) {
return super.equals(that);
}
return false;
}
return false;
}
首先,不要过早使用 equals() 方法(不仅在N.O.P.E.中),如果:
• this == argument
• this“不兼容:参数
注意:如果我们过早使用 instanceof 来检验兼容类型的话,后面的条件其实包含了argument == null。我在以前的博客中已经对这一点进行了说明,请参考10个精妙的Java编码最佳实践。
在我们对以上几种情况的比较结束后,应该能得出部分结论。比如jOOQ的 Table.equals() 方法说明是,用来比较两张表是否相同。不论具体实现类型如何,它们必须要有相同的字段名。比如下面两个元素是不可能相同的:
• com.example.generated.Tables.MY_TABLE
• DSL.tableByName(“MY_OTHER_TABLE”)
如果我们能方便地判断传入参数是否等于实例本身(this),就可以在返回结果为 false 的情况下放弃操作。如果返回结果为 true,我们还可以进一步对父类(super)实现进行判断。在比较过的大多数对象都不等的情况下,我们可以尽早结束方法来节省CPU的执行时间。
一些对象的相似度比其它对象更高。
在jOOQ中,大多数的表实例是由jOOQ的代码生成器生成的,这些实例的 equals() 方法都经过了深度优化。而数十种其它的表类型(衍生表 (derived tables)、表值函数(table-valued functions)、数组表(array tables)、连接表(joined tables)、数据透视表(pivot tables)、公用表表达式(common table expressions)等,则保持 equals() 方法的基本实现。