【问题标题】:Why is a (final) local variable declared when using an instance variable in Java? [duplicate]为什么在 Java 中使用实例变量时要声明(最终)局部变量? [复制]
【发布时间】:2021-04-14 20:25:12
【问题描述】:

考虑一下我从 JDK 的 LinkedList 类中找到的这段代码。

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

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


在 HashMap 类的下一个代码中,完成了同样的事情。局部变量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;
}

【问题讨论】:

  • 我认为这是为了线程安全,因此在 null 检查和返回之间不会发生写入。地图代码的of子句中也有赋值。
  • 这很有意义。但是为什么 tab 没有声明 final 呢?
  • 请不要在一个...问题中提出多个问题。

标签: java oop final instance-variables local-variables


【解决方案1】:

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

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

如果我们简单地删除局部变量l 并直接访问last,那么在我们检查null 和尝试访问l.item 之间,另一个线程将last 更改为空,从而导致@ 987654327@.

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

      • 局部变量访问可能稍快:Java local vs instance variable access speed
      • 使用局部变量可以使优化器更清楚地知道它可以将此引用存储在寄存器中并重新使用它,而无需重新检查“last”的值。 (从技术上讲,Java 内存模型已经允许它这样做,但可能并非所有版本的 JVM 都足够聪明地这样做,或者甚至某些版本有意不这样做,无论出于何种原因.)

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

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

    第二个问题:为什么这里没有finaltab 一起使用,就像在前面的代码中使用的那样?

    我怀疑这里有什么特别的原因;使用final 没有任何缺点。

    【讨论】:

      猜你喜欢
      • 2011-04-24
      • 2014-06-20
      • 1970-01-01
      • 1970-01-01
      • 2012-12-17
      • 1970-01-01
      • 1970-01-01
      • 2019-06-12
      • 1970-01-01
      相关资源
      最近更新 更多