为什么在 Java 中使用实例变量时声明(最终)局部变量?

Why is a (final) local variable declared when using an instance variable in Java?

考虑我从 JDK 中的 LinkedList class 中找到的这段代码。

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

第一个问题:为什么在这段代码中声明了这个看似多余的局部变量l?据我所知,我们可以简单地使用 last 来代替。


在 HashMap class 的下一个代码中,完成了同样的事情。局部变量 tab 被声明为等于实例变量 table.

第二个问题: 为什么这里 final 不像之前的代码那样与 tab 一起使用?

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

编写这样的代码的主要原因是线程安全。举第一个例子:

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

如果我们简单地删除局部变量 l 并直接访问 last 然后另一个线程将 last 更改为 null 在我们检查 null 和尝试之间访问 l.item,结果是 NullPointerException.

这里的写法(假设 l.item 在某些情况下不会被覆盖)这个方法总是 either 抛出一个 NoSuchElementException 或 return 是(或至少在某些时候)列表的一部分的值。

将局部变量声明为 final 的选择几乎完全取决于样式选择,因为它只影响编译器将接受的代码,而不影响实际的代码生成。 final 局部变量和有效最终变量(即被赋值一次且此后永不更改的变量)在任何方面的行为都没有不同。

First question: Why is this seemingly redundant local variable l declared in this code? From what I see, we could've simply used last instead.

我能想到两个可能的原因:

  • 尽管 LinkedList 并非旨在实现线程安全,但在不与任何更重要的目标(例如性能)发生冲突时,它仍然可以保持一致的行为。如果 'getLast()' 两次引用 'last','last' 第二次可能为空,触发 NullPointerException 而不是所需的 NoSuchElementException。

    • 这符合 LinkedList 对 ConcurrentModificationException 的支持——尽最大努力检测错误,但没有实际的线程安全保证。
  • 性能:

    • 局部变量访问可能稍快:Java local vs instance variable access speed
    • 使用局部变量可能会让优化器更清楚,它可以将此引用存储在寄存器中并重新使用它,而无需重新检查 'last' 的值。 (从技术上讲,Java 内存模型已经允许它执行此操作,但可能并非所有版本的 JVM 都足够智能,甚至某些版本 有意不要,不管出于什么原因。)

    JDK 类 非常重视性能,因为它们的使用非常广泛,而且它们是 JVM 的“亲密朋友”。

无论哪种情况,请注意 JDK 代码并没有真正反映 Java 主流代码的最佳实践;考虑这些决定很有趣,但请不要模仿它们!

Second question: Why is final not used here with tab like it was used in the previous code?

我怀疑这里有什么特别的原因;使用 final.

没有任何缺点