不可变对象和延迟初始化。

时间:2013-09-22 21:35:33

标签: java

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

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

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

  • 任何人都可以解释一下作者在上面所说的内容 线。
  • 我的班级immutable是否标有最终版本及其实例​​变量 仍然不是最终版,vice-versa my instance variables being finalclass being normal

4 个答案:

答案 0 :(得分:7)

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

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

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

最简单的方法是声明一个private volatile int hash;并运行计算,如果它是0.除了哈希码真正为0的对象(如果你的哈希方法分布均匀的话,四十分之一),你会得到懒惰。

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

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

答案 1 :(得分:4)

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

延迟初始化的好处是哈希码计算会被延迟,直到需要它为止。许多对象根本不需要它,因此您可以节省一些计算。特别昂贵的哈希计算,例如长String,可以从中受益。

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
}

答案 2 :(得分:2)

该行的含义是,由于对象是不可变的,因此hashCode必须只计算一次。此外,在构造对象时不必计算它 - 只需在首次调用函数时计算它。如果永远不使用对象的hashCode,则永远不会计算它。所以hashCode函数看起来像这样:

@Override public int hashCode(){
    synchronized (this) {
        if (!this.computedHashCode) {
            this.hashCode = expensiveComputation();
            this.computedHashCode = true;
        }
    }
    return this.hashCode;
}

答案 3 :(得分:0)

并添加其他答案。

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

以下代码会导致异常,因为您正在尝试更改对象的引用/指针。

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

但是这段代码可行。

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