4

http://www.javapractices.com/topic/TopicAction.do?Id=29

以上是我正在看的文章。不可变对象极大地简化了您的程序,因为它们:

允许 hashCode 使用延迟初始化,并缓存其返回值

  • 谁能解释一下作者在上面所说的内容。
  • 如果我的类immutable标记为 final 并且它的实例变量仍然不是 final 和vice-versa my instance variables being finaland class being normal
4

4 回答 4

7

正如其他人所解释的,因为对象的状态不会改变哈希码只能计算一次。

简单的解决方案是在构造函数中预先计算它并将结果放在最终变量中(这保证了线程安全)。

如果您想进行惰性计算(仅在需要时计算哈希码),如果您想保持不可变对象的线程安全特性,那就有点棘手了。

最简单的方法是声明 aprivate volatile int hash;并在它为 0 时运行计算。除了哈希码确实为 0 的对象(如果您的哈希方法分布良好,则为 40 亿分之一)之外,您将变得懒惰。

或者,您可以将其与 volatile 布尔值结合,但需要注意更新两个变量的顺序。

最后,为了获得额外的性能,您可以使用 String 类使用的方法,该方法使用额外的局部变量进行计算,从而在保证正确性的同时摆脱 volatile 关键字。如果您不完全理解为什么要以这种方式完成,那么最后一种方法很容易出错......

于 2013-09-22T22:03:33.917 回答
4

如果你的对象是不可变的,它就不能改变它的状态,因此它的哈希码也不能改变。这使您可以在需要时计算该值并缓存该值,因为它始终保持不变。实际上,hasCode基于可变状态实现您自己的函数是一个非常糟糕的主意,因为例如HashMap假设哈希不能改变,如果它改变它就会中断。

延迟初始化的好处是哈希码计算被延迟到需要时。许多对象根本不需要它,因此您可以节省一些计算。特别是昂贵的散列计算,如 on long Strings 会从中受益。

class FinalObject {
    private final int a, b;
    public FinalObject(int value1, int value2) {
        a = value1;
        b = value2;
    }

    // not calculated at the beginning - lazy once required
    private int hashCode;
    @Override
    public int hashCode() {
        int h = hashCode; // read
        if (h == 0) {
            h = a + b;    // calculation
            hashCode = h; // write
        }
        return h;         // return local variable instead of second read
    }
}

编辑:正如@a​​ssylias 所指出的,使用非同步/非易失性代码只有在只有 1 次读取时才能保证工作,hashCode因为即使第一次读取已经看到不同的值,该字段的每次连续读取都可能返回 0。以上版本解决了这个问题。

Edit2:替换为更明显的版本,代码略少但字节码大致相当

public int hashCode() {
    int h = hashCode; // only read
    return h != 0 ? h : (hashCode = a + b);
    //                   ^- just a (racy) write to hashCode, no read
}
于 2013-09-22T21:43:05.010 回答
3

那条线的意思是,由于对象是不可变的,因此hashCode只需计算一次。此外,它不必在构造对象时计算 - 只需在第一次调用函数时计算。如果该对象hashCode从未使用过,则它永远不会被计算。所以 hashCode 函数看起来像这样:

@Override public int hashCode(){
    synchronized (this) {
        if (!this.computedHashCode) {
            this.hashCode = expensiveComputation();
            this.computedHashCode = true;
        }
    }
    return this.hashCode;
}
于 2013-09-22T21:39:47.257 回答
1

并添加到其他答案。

不可变对象无法更改。final 关键字适用于 int 等基本数据类型。但对于自定义对象,这并不意味着 - 它必须在您的实现内部完成:

以下代码将导致编译错误,因为您试图更改指向对象的最终引用/指针。

final MyClass m = new MyClass();
m = new MyClass();

但是,此代码将起作用。

final MyClass m = new MyClass();
m.changeX();
于 2013-09-22T21:42:46.947 回答