微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决为什么在 Java 中使用实例变量时声明最终局部变量?

考虑我从 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;
}

解决方法

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

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 在某些情况下不会被覆盖)此方法将始终要么抛出一个 NoSuchElementException 或返回一个值那是(或至少在某个时候)列表的一部分。

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

,

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

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

  • 虽然 LinkedList 不是线程安全的,但当它不与任何更重要的目标(例如性能)冲突时,它仍然最好保持一致。如果“getLast()”两次引用“last”,则“last”第二次可能为空,从而触发 NullPointerException 而不是所需的 NoSuchElementException。

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

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

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

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

第二个问题:为什么 final 没有像前面代码中那样与 tab 一起使用?

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。