Java foreach效率

22

我有一个类似这样的代码:

Map<String, String> myMap = ...;

for(String key : myMap.keySet()) {
   System.out.println(key);
   System.out.println(myMap.get(key)); 
}

foreach循环中,myMap.keySet()只被调用一次吗?我认为是,但想听听你的意见。

我想知道以这种方式(myMap.keySet())使用foreach是否会产生性能影响,还是与以下方式等效:

Set<String> keySet = myMap.keySet();
for (String key : keySet) {
   ...
}

增强型for循环的语法有点前后颠倒。 - Tom Hawtin - tackline
2
我不知道是否同意称之为过早优化。想要了解编译器对代码的处理是合理的。我们也不知道他在项目中的哪个阶段(如果他正在进行项目而不是学术上的提问)。这可能是在最后阶段。 - James McMahon
6个回答

65

如果你想要绝对的确定性,那么请进行两种方式的编译,然后反编译并进行比较。我使用以下源代码进行了这个操作:

public void test() {
  Map<String, String> myMap = new HashMap<String, String>();

  for (String key : myMap.keySet()) {
    System.out.println(key);
    System.out.println(myMap.get(key));
  }

  Set<String> keySet = myMap.keySet();
  for (String key : keySet) {
    System.out.println(key);
    System.out.println(myMap.get(key));
  }
}

我使用Jad反编译class文件后,得到以下结果:

public void test()
{
    Map myMap = new HashMap();
    String key;
    for(Iterator iterator = myMap.keySet().iterator(); iterator.hasNext(); System.out.println((String)myMap.get(key)))
    {
        key = (String)iterator.next();
        System.out.println(key);
    }

    Set keySet = myMap.keySet();
    String key;
    for(Iterator iterator1 = keySet.iterator(); iterator1.hasNext(); System.out.println((String)myMap.get(key)))
    {
        key = (String)iterator1.next();
        System.out.println(key);
    }
}

这就是答案。它使用for循环的形式之一调用一次。


哇!那很有趣...不错。+1。 - Hari Krishna Ganji
抱歉打扰,但这里似乎有些奇怪。为什么println语句在for循环的end-section中,而next的调用在body中呢?看起来是一个非常奇怪的设置。 - Carcigenicate
@Carcigenicate 我同意,这是一个有趣的设置。但从逻辑上讲,它是可行的:在 for 循环的结束部分被执行时,next 方法已经在迭代器上被调用了,所以一切都很棒。对我来说,使用原始迭代器总是感觉有点奇怪,因为在大多数情况下,实际的“迭代”发生在循环开始时显式地进行,而我习惯于它(至少在概念上)发生在上一个循环的结尾。 - Daniel Brady

35

它只被调用一次。实际上,它使用迭代器来完成技巧。

此外,在您的情况下,我认为您应该使用

for (Map.Entry<String, String> entry : myMap.entrySet())
{
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

为了避免每次在map中搜索。


2
谢谢大家分享您的智慧!但愿我的同行也能如此! - Flueras Bogdan
如果myMap.entrySet()不返回一个常量值(比如说在循环中更新了myMap,比如添加了一个键值对),那会怎么样呢?它只被调用一次吗?这不会产生奇怪的结果吗? - JavaTechnical
@JavaTechnical:问题在于,在更新时,您不应更改映射的内容,否则会出现ConcurrentModificationException。如果您需要在迭代时更改映射,则唯一安全的方法是通过Iterator进行操作。 - Valentin Rocher

9

keySet() 只会被调用一次。 "增强型for循环" 基于 Iterable 接口,该接口用于获取 Iterator,然后用于循环。你甚至无法以其他方式迭代 Set,因为没有索引或其他任何可用于获取单个元素的东西。

然而,你真正应该做的是完全放弃这种微观优化的担忧——如果你遇到真正的性能问题,那么有99%的机会是你自己从未考虑过的事情。


2
你真正应该做的是完全放弃这种微观优化的担忧。他所关注的问题在一般情况下并不是微观优化... - hhafez
你可以构造一个恶意的特例,导致几乎任何东西都出现巨大的性能问题,但这并不改变事实,它几乎肯定不会成为问题 - 首先,关键字集在我所见过的任何Map实现中都被缓存。 - Michael Borgwardt
微观优化 - 我很在意!它们对于复杂的程序肯定会产生影响! - JavaTechnical
1
@JavaTechnical:当然,它们会产生影响:使得复杂程序更难维护,甚至可能变得更慢。你一定要关注它们,避免使用它们。 - Michael Borgwardt

7
答案在Java语言规范中,不需要反编译 :) 这就是我们可以阅读到的增强型for循环语句的内容。

The enhanced for statement has the form:

EnhancedForStatement:
        for ( VariableModifiersopt Type Identifier: Expression) Statement

The Expression must either have type Iterable or else it must be of an array type (§10.1), or a compile-time error occurs.

The scope of a local variable declared in the FormalParameter part of an enhanced for statement (§14.14) is the contained Statement

The meaning of the enhanced for statement is given by translation into a basic for statement.

If the type of Expression is a subtype of Iterable, then let I be the type of the expression Expression.iterator(). The enhanced for statement is equivalent to a basic for statement of the form:

for (I #i = Expression.iterator(); #i.hasNext(); ) {

        VariableModifiersopt Type Identifier = #i.next();
   Statement
}

Where #i is a compiler-generated identifier that is distinct from any other identifiers (compiler-generated or otherwise) that are in scope (§6.3) at the point where the enhanced for statement occurs.

Otherwise, the Expression necessarily has an array type, T[]. Let L1 ... Lm be the (possibly empty) sequence of labels immediately preceding the enhanced for statement. Then the meaning of the enhanced for statement is given by the following basic for statement:

T[] a = Expression;
L1: L2: ... Lm:
for (int i = 0; i < a.length; i++) {
        VariableModifiersopt Type Identifier = a[i];
        Statement
}

Where a and i are compiler-generated identifiers that are distinct from any other identifiers (compiler-generated or otherwise) that are in scope at the point where the enhanced for statement occurs.

在您的情况下,myMap.keySet() 返回 Iterable 的子类型,因此您的增强型 for 语句等同于以下基本的 for 语句:
for (Iterator<String> iterator = myMap.keySet().iterator(); iterator.hasNext();) {
   String key = iterator.next();

   System.out.println(key);
   System.out.println(myMap.get(key)); 
}

myMap.keySet() 因此只被调用一次。


5
是的,无论哪种方式,它只被调用一次。

-3

我相信它是编译器优化,每次循环入口只运行一次。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接