After a rather interesting StackOverflow question, I got thinking about different types of efficiency in Java and, consequently, Android.
First, I did a basic experiment with these codes:
// Code 1
int i = 0;
Integer[] array = { 1, 2, 3, 4, 5 };
for (Integer obj : array) {
i += obj;
}
System.out.println(i);
// Code 2
int i = 0;
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
for (Integer obj : list) {
i += obj;
}
System.out.println(i);
I then analyzed the bytecode of each; both decompilations can be found here for easy reference.
First decompilation:
(I)Ljava/lang/Integer;
40: aastore
41: astore_2
42: aload_2
43: dup
44: astore 6
46: arraylength
47: istore 5
49: iconst_0
50: istore 4
52: goto 71
55: aload 6
57: iload 4
59: aaload
60: astore_3
61: iload_1
62: aload_3
63: invokevirtual #22 // Method java/lang/Integer.intValue
:()I
66: iadd
67: istore_1
68: iinc 4, 1
71: iload 4
73: iload 5
75: if_icmplt 55
78: getstatic #26 // Field java/lang/System.out:Ljava/
io/PrintStream;
81: iload_1
82: invokevirtual #32 // Method java/io/PrintStream.printl
n:(I)V
85: return
Second decompilation:
erator:()Ljava/util/Iterator;
71: astore 4
73: goto 94
76: aload 4
78: invokeinterface #35, 1 // InterfaceMethod java/util/Iterato
r.next:()Ljava/lang/Object;
83: checkcast #20 // class java/lang/Integer
86: astore_3
87: iload_1
88: aload_3
89: invokevirtual #41 // Method java/lang/Integer.intValue
:()I
92: iadd
93: istore_1
94: aload 4
96: invokeinterface #45, 1 // InterfaceMethod java/util/Iterato
r.hasNext:()Z
101: ifne 76
104: getstatic #49 // Field java/lang/System.out:Ljava/
io/PrintStream;
107: iload_1
108: invokevirtual #55 // Method java/io/PrintStream.printl
n:(I)V
111: return
As you can see, the latter (the generic List
iterator) has calls to fields and methods that the first one does not. This is because the first is optimized to run just like a basic for ( ... ) { ... }
loop; it loops through every element and acts upon it. There is a lot more overhead associated with a non-optimized iterator.
From there, I was interested as to the time complexities of each method. In order from fastest to slowest:
- Java array iteration
- Java array for-loop
- Java generic List for-loop
- Java generic List iteration
So, when running the following code:
long time;
int limit;
List<Integer> list = new ArrayList<>();
Integer[] array = new Integer[1000000];
for (int j = 0; j < array.length; j++) {
list.add(j);
array[j] = j;
}
time = System.currentTimeMillis();
for (Integer obj : array) {
obj.intValue();
}
System.out.println("1) Time taken is: "+(System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for (Integer obj : list) {
obj.intValue();
}
System.out.println("2) Time taken is: "+(System.currentTimeMillis() - time));
time = System.currentTimeMillis();
limit = list.size();
for (int j = 0; j < limit; j++) {
array[j].intValue();
}
System.out.println("3) Time taken is: "+(System.currentTimeMillis() - time));
time = System.currentTimeMillis();
limit = list.size();
for (int j = 0; j < limit; j++) {
list.get(j).intValue();
}
System.out.println("4) Time taken is: "+(System.currentTimeMillis() - time));
The time outputs (in ms) are:
1) Time taken is: 6
2) Time taken is: 20
3) Time taken is: 9
4) Time taken is: 15
Interestingly enough, these can all be explained by optimization. The array iteration, which is almost identical to the traversal, uses more native speed than the traversal, though their logic is congruent. The list for-loop requires no extraneous calls, since we handle all that ourselves (nicely enough!), and the list iterator does have this overhead, placing it in last.
So next time you're running a list iteration 60 times a second, you'll know why your program is lagging an extra 300ms!