为什么在 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
.
没有任何缺点
考虑我从 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 usedlast
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 withtab
like it was used in the previous code?
我怀疑这里有什么特别的原因;使用 final
.