3

Defining a "for-each loop", it is better use this convention:

Case 1

for (String s : xxx.getList())

or this:

Case 2

List<String> list = xxx.getList();
for (String s : list)

In Case 1, how many times the method getList() is invoked? One time every loop or only one time at the beginnning?

Thanks

valerio
  • 53
  • 1
  • 9

6 Answers6

4

Never spend time thinking about this type of optimisation.

1) If you can think of a one line change which seems more efficient but functionally equivalent, its certain that the people who write the JIT compiler also thought of it. And they will compile to the same bytecode.

2) Readability is far more important than performance in all but the most critical loops. You should use case two only if you intend to use the list for something else which is outside the scope of the for loop, and your for loop alters the list. In that case it is semantically clearer to carry the altered list than to repeatedly call xxx.getList(), as a programmer looking at a part of the function may not realise that you have edited the list in xxx further up in the same function.

3) There are few hard and fast rules in this type of programming, and opinions on what represents "readability" vary.

Remember that a compiled language is not the same as an interpreted language, and the code you write is not a literal script book for the processor. Almost every small optimisation that you can imagine, and a lot that you cannot, will be made by the Compiler. Moreover, lots of standard programming patters of high complexity can be optimised by the compiler, when attempts to "optimise it" on your own will just lead to the compiler `getting confused', not recognising that this is a standard pattern, and failing to optimise when it should.

========================

In the comments slim has pointed out that there is another case for using case 2 - if you have several for loops and getList is an expensive operation which constructs the list on request, rather than just returning an existing object.

phil_20686
  • 4,000
  • 21
  • 38
  • I mostly agree, but "never" is a bit strong. I recently had *very* slow code that did `while(x < file.length()) ...` instead of `size = file.length(); while(x < file.length())`. This `getList()` method could potentially be a very expensive operation. – slim Jun 13 '14 at 10:24
  • Your right of course, I assumed getList simply returns a list variable held in the xxx class, but if it is constructed on request it could potentially be very expensive. – phil_20686 Jun 13 '14 at 10:29
  • Of course, while loops in your comment check the condition on every loop, whereas the for loop idiom for(String s : xxx.getList()) only ever calls the list once. I suppose in this case its not "Functionally equivalent" since the compiler cannot generally guarantee that the code in the while loop never alters the value of the condition, so it must be optimised `by hand'. But good example. – phil_20686 Jun 13 '14 at 10:31
1

You can test it like this:

public static void main(tring[] args) {
   for (String string : getList()) {
       System.out.println(string);
   }
}

private static List<String> getList() {
   System.out.println("getList");
   List<String> l = new ArrayList<String>();
   l.add("a");
   l.add("b");
   return l;
}

... and you will find that getList() is only called once.

The syntax of the enhanced for() loop is:

 for(Type item : iterable) ...

In your example, getList() returns an Iterable at runtime -- in this case a List. Once it's done that, the loop gets to work with the one Iterable it needs.

 for (String string : getList()) ...

and

 List list = getList();
 for (String string : list) ...

... are equivalent. The first form has the advantage of being short and clear. The second form has the advantage that you can use the list again afterwards, if you need to.

In Eclipse, you can switch between the two forms with an automatic refactoring (other IDEs have similar):

Starting with the first form, select getList(), right click, Refactor -> extract local variable. Eclipse will prompt you for a variable name. Enter list, and it will create the second form for you.

Starting with the second form, select list in the for() statement, right click, Refactor -> Inline. It will prompt, then change it back to the old format.

Refactorings are supposed to result in functionally identical code, so you can use this as evidence that the two forms are equivalent.

Take care however; other loop forms are not as clever.

  while( size < file.length()) {
       ...
  }

... executes file.length() every time the loop iterates, whereas

 long fileLength = file.length();
 while( size < fileLength ) {
     ...
 }

... executes file.length() only once. The same applies to traditional for(;;) loops.

The Eclipse refactoring transform described above will also switch between these two forms, but the behaviour is not the same.

slim
  • 40,215
  • 13
  • 94
  • 127
0

The getList method is invoked the same number of times in each case - only once.

As for which style to adopt, it depends. If you need to reference the list again after the for loop, go for Case 2 to avoid calling getList more than you need to.

I also prefer Case 2 from a debugging perspective. You can set a breakpoint on the for loop and inspect the contents of list before the iteration begins.

JamesB
  • 7,774
  • 2
  • 22
  • 21
0

The enhanced for loop uses the provided Iterable (or array) to obtain an Iterator to loop over the items. The Iterator is obtained only once.

So

for (String s : list) {
    // loop code
}

is the same as:

Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String s = iter.next();

    // loop code
}

Tip: you can write and use your own Iterable implementations in enhanced for loops.

Peter Walser
  • 15,208
  • 4
  • 51
  • 78
0
for (String s : list)

After Compilation

for (Iterator<String> i = list.iterator(); i.hasNext(); ){...}

for (String s : xxx.getList()){...}

After compilation it would be -

for (Iterator<String> i = xxx.getList().iterator(); i.hasNext(); ){...}

It means single time getList would be called for the second case also.

Subhrajyoti Majumder
  • 40,646
  • 13
  • 77
  • 103
0

In case 1, getList() method is invoked only once. You can verify this by printing something when the method is invoked.

In addition to answers that people have already provided, we can look at the byte code produced by the two for-loop styles.

For this code:

List<String> l = getList();
for (String s : l) {
  processItem(s);
}

the byte code is:

0:   invokestatic    #4; //Method getList:()Ljava/util/List;
3:   astore_1
4:   aload_1
5:   invokeinterface #5,  1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
10:  astore_2
11:  aload_2
12:  invokeinterface #6,  1; //InterfaceMethod java/util/Iterator.hasNext:()Z
17:  ifeq    37
20:  aload_2
21:  invokeinterface #7,  1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
26:  checkcast       #8; //class java/lang/String
29:  astore_3
30:  aload_3
31:  invokestatic    #9; //Method processItem:(Ljava/lang/String;)V
34:  goto    11
37:  return

And for this code:

for (String s : getList()) {
  processItem(s);
}

the byte code is:

0:   invokestatic    #4; //Method getList:()Ljava/util/List;
3:   invokeinterface #5,  1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
8:   astore_1
9:   aload_1
10:  invokeinterface #6,  1; //InterfaceMethod java/util/Iterator.hasNext:()Z
15:  ifeq    35
18:  aload_1
19:  invokeinterface #7,  1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
24:  checkcast       #8; //class java/lang/String
27:  astore_2
28:  aload_2
29:  invokestatic    #9; //Method processItem:(Ljava/lang/String;)V
32:  goto    9
35:  return

The only difference between the two byte codes is that in the former byte code, we have:

3:   astore_1
4:   aload_1

which corresponds to storing the result returned by getList() method in a variable. In other words, the two for-loop styles have pretty much the same performance (with the exception of variable used to store the result returned by getList() method).

fajarkoe
  • 1,543
  • 10
  • 12