You can compile the methods and compare their bytecode to see the differences.
The biggest difference is between func1 and func2 where func1 only calls size() once, whereas func2 re-calls size() for every iteration. If size() is not cached (most implementations do cache it) then it could be costly to re-compute it. Even in that case the jitter can probably optimize it, especially with a local variable, since it might realize that the list does not change. But if the list were an exposed member then it might need to re-compute the size each time since a separate thread could have modified the list, though it's still very likely to be optimized to assume that the size has not changed.
The only difference between func3 and fun4 is that func4 uses an extra astore and aload since it's storing the list in a local variable, which can probably be optimized away by the jitter.
Ultimately the best way to test efficiency is to measure it.
private void func1() { ArrayList<String> list = new ArrayList<>(); int x = list.size(); for (int i = 0; i < x; i++) System.out.println(list.get(i)); } private void func2() { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < list.size(); i++) System.out.println(list.get(i)); } private void func3() { for (String str : new ArrayList<String>()) System.out.println(str); } private void func4() { ArrayList<String> list = new ArrayList<>(); for (String str : list) System.out.println(str); }
private void func1(); ... storing the list ... 8 aload_1 [list] 9 invokevirtual java.util.ArrayList.size() : int [18] // compute list.size() 12 istore_2 [x] // store list.size() 13 iconst_0 14 istore_3 [i] 15 goto 35 ... print ... 32 iinc 3 1 [i] // i++ 35 iload_3 [i] // load i 36 iload_2 [x] // load the already computed list.size() 37 if_icmplt 18 // if i < list.size() goto 18 40 return
private void func2(); ... storing the list ... 8 iconst_0 9 istore_2 [i] 10 goto 30 ... print ... 27 iinc 2 1 [i] // i++ 30 iload_2 [i] // load i 31 aload_1 [list] // load the list 32 invokevirtual java.util.ArrayList.size() : int [18] // compute list.size() 35 if_icmplt 13 // if i < list.size() goto 13 38 return
private void func3(); ... storing the list ... 7 invokevirtual java.util.ArrayList.iterator() : java.util.Iterator [50] 10 astore_2 11 goto 31 14 aload_2 15 invokeinterface java.util.Iterator.next() : java.lang.Object [54] [nargs: 1] ... print ... 31 aload_2 32 invokeinterface java.util.Iterator.hasNext() : boolean [60] [nargs: 1] 37 ifne 14 40 return
private void func4(); ... storing the list ... 7 astore_1 [list] // these are the only extra operations 8 aload_1 [list] // to store the list in a local variable 9 invokevirtual java.util.ArrayList.iterator() : java.util.Iterator [50] 12 astore_3 13 goto 33 16 aload_3 17 invokeinterface java.util.Iterator.next() : java.lang.Object [54] [nargs: 1] ... print ... 33 aload_3 34 invokeinterface java.util.Iterator.hasNext() : boolean [60] [nargs: 1] 39 ifne 16 42 return
forthere is a difference (measurable?) since thesizemethod is called each iteration;for-eachloop is almost the same,getStringListis called only once (14.14.2. The enhanced for statement)