String[] strs = new String[] { "1", "2", ... , "6" };
for (String s : strs) {
System.out.println(s);
}
This is a question about java internals.
In the above code sample, how does the foreach loop figure out how long the array is? Are arrays actually objects internally or is it using stuff like sizeof that is inaccessible to front end programmers?
I have a feeling I'm just missing something stupid, but I figure it could also be cool. :-)
I compiled the following code:
public class ArrayIterator
{
public static void main(String[] argv)
{
String[] strs = new String[] { "1", "2", "3", "4", "5" };
enhancedPrint(strs);
normalPrint(strs);
}
public static void enhancedPrint( String[] strs )
{
for (String s : strs)
{
System.out.println(s);
}
}
public static void normalPrint( String[] strs )
{
String[] localArray = strs;
int len = localArray.length;
for (int i = 0; i < len; i++)
{
String s = localArray[i];
System.out.println(s);
}
}
}
This is the disassembled (javap -c ArrayIterator) bytecode for the iterating functions:
The enhanced print:
public static void enhancedPrint(java.lang.String[]);
Code:
0: aload_0
1: astore_1
2: aload_1
3: arraylength
4: istore_2
5: iconst_0
6: istore_3
7: iload_3
8: iload_2
9: if_icmpge 31
12: aload_1
13: iload_3
14: aaload
15: astore 4
17: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload 4
22: invokevirtual #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: iinc 3, 1
28: goto 7
31: return
A normal for loop:
public static void normalPrint(java.lang.String[]);
Code:
0: aload_0
1: astore_1
2: aload_1
3: arraylength
4: istore_2
5: iconst_0
6: istore_3
7: iload_3
8: iload_2
9: if_icmpge 31
12: aload_1
13: iload_3
14: aaload
15: astore 4
17: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload 4
22: invokevirtual #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: iinc 3, 1
28: goto 7
31: return
As you can see, in both cases, the compiler is loading the arrays length (strs.length) and looping against it. We see that the enhanced for-each loop, in the case of an Array is syntactic sugar for looping against the array's length (rather than in an Object's case where it uses an Iterator).
I have edited the 'normal' for loop such that it is much less idiomatic, but that it has the same bytecode as the enhanced for loop. For all intents and purposes, the normal for loop version is what the compiler generates when it compiles the enhanced for each loop.
Yes, there is something akin to the C++ sizeof operator -- it's the length instance variable (which gives the number of elements in the array, not the size in bytes.) The length field is always a public member of the array, so it is accessible to Java programmers.
And yes, arrays are objects, and not just internally.
(More broadly, the for loop syntax works as described in the question that org.life.java linked to; but that isn't quite what you were asking.)
Yes, arrays are objects with a "length" field. Java Language Specification: http://java.sun.com/docs/books/jls/third_edition/html/arrays.html#64347
Related
I just encountered this decompiled class file of my class:
MyClass
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
The while loop has been changed to a for loop in the class file:
Decompiled MyClass
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
Why has this loop been changed to a for?
I think it might be another way of code optimization by the compiler, I could be wrong.
I just wanted to know if it is, what advantages does a for loop provide over a while loop or other loop?
What is the category of such code optimizations?
In this situation changing while() to for() is not an optimization. There is simply no way to know from bytecode which one was used in a source code.
There are many situations when:
while(x)
is the same as:
for(;x;)
Suppose we have a three similar java applications - one with while() statement, and two with corresponting for(). First for() with stopping criterion only like in the standard while(), and second for() also with iterator declaration and incrementation.
APPLICATION #1 - SOURCE
public class While{
public static void main(String args[]) {
int i = 0;
while(i<5){
System.out.println(i);
i++;
}
}
}
APPLICATION #2 - SOURCE
public class For{
public static void main(String args[]) {
int i = 0;
for(; i<5 ;){
System.out.println(i);
i++;
}
}
}
APPLICATION #3 - SOURCE
public class For2{
public static void main(String args[]) {
for(int i=0;i<5;i++){
System.out.println(i);
}
}
}
If we compile all of them we have got:
APPLICATION #1 - BYTECODE
public class While {
public While();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
APPLICATION #2 - BYTECODE
public class For {
public For();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
APPLICATION #3 - BYTECODE
public class For2 extends java.lang.Object{
public For2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
So you can see, there is no difference associated with for and while usage.
As others have already pointed out: The decompiler (usually) cannot distinguish between different source codes that result in the same byte code.
Unfortunately, you did not provide the full code of the method. So the following contains some guesses about where and how this loop appears inside a method (and these guesses might, to some extent, distort the result).
But let's have a look at some roundtrips here. Consider the following class, containing methods with both versions of the code that you posted:
import java.io.BufferedReader;
import java.io.IOException;
import java.util.regex.Pattern;
public class DecompileExample {
public static void methodA(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
String[] colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
}
}
Compiling it with
javac DecompileExample.java -g:none
will create the corresponding class file. (Note: The -g:none parameter will cause the compiler to omit all debug information. The debug information might otherwise be used by the decompiler to reconstruct a more verbatim version of the original code, particularly, including the original variable names)
Now looking at the byte code of both methods, with
javap -c DecompileExample.class
will yield the following:
public static void methodA(java.io.BufferedReader) throws java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aload_0
5: invokevirtual #2 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
8: dup
9: astore_1
10: ifnull 61
13: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
16: new #4 // class java/lang/StringBuilder
19: dup
20: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
23: ldc #6 // String line:
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: aload_1
29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
35: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
38: iload_2
39: ifne 55
42: aload_1
43: ldc #10 // String |
45: invokestatic #11 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
48: invokevirtual #12 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
51: astore_3
52: goto 4
55: iinc 2, 1
58: goto 4
61: return
and
public static void methodB(java.io.BufferedReader) throws java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aconst_null
5: astore_3
6: aload_0
7: invokevirtual #2 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
10: dup
11: astore_1
12: ifnull 60
15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
18: new #4 // class java/lang/StringBuilder
21: dup
22: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
25: ldc #6 // String line:
27: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: aload_1
31: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: iload_2
41: ifne 54
44: aload_1
45: ldc #10 // String |
47: invokestatic #11 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
50: invokevirtual #12 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
53: astore_3
54: iinc 2, 1
57: goto 6
60: return
}
(There is a small difference: The String[] colArr = null is translated into an
aconst null
astore_3
at the beginning of the second version. But this is one of the aspects that is related to parts of the code that you have omitted in the question).
You did not mention which one you are using, but the JD-GUI decompiler from http://jd.benow.ca/ decompiles this into the following:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Pattern;
public class DecompileExample
{
public static void methodA(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
String[] arrayOfString = str.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
String[] arrayOfString = null;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
arrayOfString = str.split(Pattern.quote("|"));
}
i++;
}
}
}
You can see that the code is the same for both cases (at least regarding the loop - there one more is a difference regarding the "dummy variables" that I had to introduce in order to compile it, but this is unrelated to the question, so to speak).
The tl;dr message is clear:
Different source codes can be compiled into the same byte code. Consequently, the same byte code can be decompiled into different source codes. But every decompiler has to settle for one version of the source code.
(A side note: I was a bit surprised to see that when compiling without -g:none (that is, when the debug information is retained), JD-GUI even somehow manages to reconstruct that the first one used a while-loop and the second one used a for-loop. But in general, and when the debug information is omitted, this is simply no longer possible).
That's basically because of the nature of bytecode. Java bytecode is something like assembly language, so there are no such things as for and while loop, there is simply jump instruction: goto. So there may be no difference between while and for loop, Both can be compiled to similar code and decompiler is just making guess.
Both the for loop and the while loop code segments can be translated into similar machine code. After that when de-compiling the de-compiler has to pick one of the two possible scenarios.
I guess that is what's happening here.
simply:
compile(A) -> C
compile(B) -> C
So when you are given C, then there should be a guess to pick A or B
I have a question that i have been pondering for a while. Take for instance this particular piece of class
class A{
private static ArrayList<String> listOne;
public Static ArrayList<String> getList()
{
return this.listOne
}
}
Let say i have class B that possess a method to read the details with listOne. To Look through the arraylist, i would need to first get the size of the list in order for my code to know when the arraylist ends. There are 2 ways in which i can do so, one being
int listSize = A.getList().size()
for(int count =0; count < listSize; count++)
{
// code to read through arraylist
}
or i can achieve the same thing with
for(int count=0; count < A.getList().size(); count++)
{
// code to read through arraylist
}
In terms of memory and efficiency, which method is better? Furthermore let says i am reading through a very large array recursively. For simplicity purposes, lets assume that recursively reading through this array would a stack overflow exception. In this situation, would the first method theoretically cause a stack overflow to happen earlier then the second method seeing that each recursive call's stack frame has to keep the state of the variable "listSize"?
Have a look at the result of javap -verbose:
0: invokestatic #2 // Method A.getList:()Ljava/util/ArrayList;
3: invokevirtual #3 // Method java/util/ArrayList.size:()I
6: istore_1
7: iconst_0
8: istore_2
9: iload_2
10: iload_1
11: if_icmpge 27
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: iload_2
18: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
21: iinc 2, 1
24: goto 9
27: iconst_0
28: istore_2
29: iload_2
30: invokestatic #2 // Method A.getList:()Ljava/util/ArrayList;
33: invokevirtual #3 // Method java/util/ArrayList.size:()I
36: if_icmpge 52
39: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
42: iload_2
43: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
46: iinc 2, 1
49: goto 29
52: return
First case is:
9: iload_2
10: iload_1
11: if_icmpge 27
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: iload_2
18: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
21: iinc 2, 1
24: goto 9
And the second one:
29: iload_2
30: invokestatic #2 // Method A.getList:()Ljava/util/ArrayList;
33: invokevirtual #3 // Method java/util/ArrayList.size:()I
36: if_icmpge 52
39: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
42: iload_2
43: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
46: iinc 2, 1
49: goto 29
As you can see, it will get the list and its size during each loop iteration.
But, this might be optimized by JIT, so the result is not obvious from just the compiled bytecode.
Created from:
import java.io.*;
import java.util.*;
public class Z {
public static void main(String[] args) throws Exception {
int listSize = A.getList().size();
for(int count =0; count < listSize; count++) {
System.out.println(count);
}
for(int count =0; count < A.getList().size(); count++) {
System.out.println(count);
}
}
}
class A{
private static ArrayList<String> listOne = new ArrayList<>(Arrays.asList("1", "2", "3"));
public static ArrayList<String> getList()
{
return listOne;
}
}
Both the loop are same. Second one is better coding as it reduces line of code.
Since you mentioned that your need is to traverse the list it is much better to use the enhanced (for-each) for loop.
What are the Advantages of Enhanced for loop and Iterator in Java?
why is enhanced for loop efficient than normal for loop
Regarding which of the method is more efficient, I think they won't have any noticeable differences. And depending on the JVM, like Germann has said, the compiler will even optimize this. So just don't worry about this negligible difference.
I personally will use the second method since it has fewer lines of code and I'm lazy...
However, why not use neither?
There's a super cool alternative, and its name is... JON CENA The enhanced for loop.
Let's compare it with a normal for loop:
Normal:
for (int i = 0 ; i < A.getList().size() ; i++ {
}
Enhanced:
for (String item : A.getList()) {
// Instead of using A.getList().get(i) to access the items, just use "item"!
}
Look how nice it is!
The major difference between these two for loops is that
The normal for loop is just a kind of a while loop with initialization and incrementation.
The enhanced for loop calls .iterator().hasNext() and .iterator().next() to loop.
You need to know the size of the list to use a normal for loop
Your list just needs to implement Iterable and probably Iterator to use an enhanced for loop. No size is needed.
An enhanced for loop has the following limitations:
You can't loop through two arrays at the same time
It's better to use a normal for loop if you want to know the index of the list, since calling indexOf a lot of times is not very efficient.
I understand an array object in Java is created when I call its constructor with the new keyword:
int[] myIntArray = new int[3];
But if I instead write
int[] myIntArray = {1,2,3};
an array object gets created, but I haven't called its constructor with new. How does this work under the hood - how can an object be created in Java without calling the constructor?
As far as creating the array object is concerned, it's syntactic sugar. When compiled, it works exactly like the standard syntax.
The difference here though is that with the first version, you aren't populating the array - all elements are the default value for int, which is zero.
With the second version, you're creating, and populating the array.
This part:
{1,2,3}
is an array initializer that can be used as part of a declaration. To quote the JLS, section 10.6:
An array initializer may be specified in a declaration (§8.3, §9.3,
§14.4), or as part of an array creation expression (§15.10), to create
an array and provide some initial values.
ArrayInitializer:
{ VariableInitializersopt ,opt }
The first version populates an integer array with the default 0 value. The second assigns values explicitly.
The first version is equivalent to
int[] myIntArray = {0, 0, 0};
while the second is the same as
int[] myIntArray = new int[] {1,2,3};
The new keyword is only mandatory for non declarative statements, for example .
int[] myIntArray;
myIntArray = new int[] {1,2,3};
Datatypes
Arrays
Both statements are same. The second statement int[] myIntArray = {1,2,3}; is short cut to syntax using new method.
int[] myIntArray ={1,2,3} , this case length of the array is determined by the number of values provided between braces and separated by commas.
Take this code and compile it:
public class ArrayTest {
public static void main1() {
int[] array = new int[3]; array[0] = 10; array[1] = 20; array[3] = 30;
}
public static void main2() {
int[] array = new int[] {10, 20, 30};
}
public static void main3() {
int[] array = {10, 20, 30};
}
}
Then use javap -c to disassemble it to view its bytecode to get the following results. But what you will is that the later two snippets or methods compile to the same bytecode. So int[] array = new int[] {1, 2, 3} and int[] array = {1, 2, 3} are the same. But seperately creating an array and assigning values to each of its element is treated differently and so the later two snippets are not syntactic sugar for the first snippet.
$ javap -c ArrayTest
Compiled from "ArrayTest.java"
public class ArrayTest extends java.lang.Object{
public ArrayTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main1();
Code:
0: iconst_3
1: newarray int
3: astore_0
4: aload_0
5: iconst_0
6: bipush 10
8: iastore
9: aload_0
10: iconst_1
11: bipush 20
13: iastore
14: aload_0
15: iconst_3
16: bipush 30
18: iastore
19: return
public static void main2();
Code:
0: iconst_3
1: newarray int
3: dup
4: iconst_0
5: bipush 10
7: iastore
8: dup
9: iconst_1
10: bipush 20
12: iastore
13: dup
14: iconst_2
15: bipush 30
17: iastore
18: astore_0
19: return
public static void main3();
Code:
0: iconst_3
1: newarray int
3: dup
4: iconst_0
5: bipush 10
7: iastore
8: dup
9: iconst_1
10: bipush 20
12: iastore
13: dup
14: iconst_2
15: bipush 30
17: iastore
18: astore_0
19: return
Under the hood, both ways of initializing an array is the same.
For another example, look at this:
String str = "Hello"; // no 'new' statement here
String str = new String("Hello");
Both statements do the same thing, but one is far more convenient than the other. But under the hood, they pretty much do the same thing after compilation.
I'm having a problem initializing array of HashSet
int N = 100;
HashSet<Integer> []array = new HashSet[N];
for (HashSet<Integer> set:array){
set = new HashSet<Integer>();
}
But the array contains only null. (Also error when HashSet []array = .... )
But when running with:
for(int i = 0; i < N; i++){
array[i] = new HashSet<Integer>();
}
All is well.
Why does the first code isn't working? Is it my mistake?
Thank you
You never actually assign initialized instances to elements of the array. Instead you iterate over the elements of the array with a variable that gets assigned to a new object in your loop, then is never used. In a case like this the enhanced for...each syntax is not appropriate, use the traditional for loop instead.
two 'for' is different in Java, look the java code & bytecode ..
Example:
public class T{
public static void main(String[] args){
String[] data = new String[10];
System.out.print("");
for(String str:data){
str="1";
}
System.out.print("");
for(int i=0;i<data.length;i++){
data[i]="1";
}
}
}
$>javac -classpath . T.java
$>javap -c T
Compiled from "T.java"
public class T extends java.lang.Object{
public T();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: anewarray #2; //class java/lang/String
5: astore_1
6: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #4; //String
11: invokevirtual #5; //Method java/io/PrintStream.print:(Ljava/lang/String;)V
14: aload_1
15: astore_2
16: aload_2
17: arraylength
18: istore_3
19: iconst_0
20: istore 4
22: iload 4
24: iload_3
25: if_icmpge 44
28: aload_2
29: iload 4
31: aaload
32: astore 5
34: ldc #6; //String 1
36: astore 5
38: iinc 4, 1
41: goto 22
44: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
47: ldc #4; //String
49: invokevirtual #5; //Method java/io/PrintStream.print:(Ljava/lang/String;)V
52: iconst_0
53: istore_2
54: iload_2
55: aload_1
56: arraylength
57: if_icmpge 71
60: aload_1
61: iload_2
62: ldc #6; //String 1
64: aastore
65: iinc 2, 1
68: goto 54
71: return
}
from line 25--44 and line 57--71:
aload : Retrieves an object reference from a local variable and pushes it onto the operand stack.
aaload : Retrieves an object reference from an array of objects and places it on the stack.
astore : Take object or reference store to local variable.
aastore : Take reference type value store to array.
so,first can't store array , didn't using initializing array.
An enhanced for loop, does not use the actual instances in the array( and as well, collection), but rather copies them in the loop control variable.
This should not be a problem with non-null values, since they point to the same object. Problems arise, if the values are null, and re-assigning values to the control variable, would not change the actual value. So in this case, always use regular for loops.
The same would be true, to arrays of primitive types, since coping them, and changing the copied variable, will not affect the original variable.
Simply use new Set[n] instead of new HashSet[n] , to get it working
Set<Integer>[] list = new Set[n];
for(int i=0;i<n;i++)
list[i] = new HashSet<>();
You need to create an array that contains HashSet
so, you need to initialize the array and then initialize the HashSet
which I have provided in the below code
int N = 100;
Set<Integer>[] array = new Set[n];
for(int i = 0; i < n; i++) {
array[i] = new HashSet<Integer>();
}
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 11 years ago.
Goolge comes up with a lot of comparisions but not on what I am looking for:
What is better if I want to iterate through a splitted String
String[] flagArr = flags.split(";");
for (String f: flagArr) {
// some stuff
}
OR
for (String f: flags.split(";")) {
// some stuff
}
With the second pice of code I wonder if the compiler is smart enough to do the split only once
You can check the generated bytecode, but I'm pretty sure they'll both do the same thing. Why would the second one be any different?
EDIT: As you can see, both ways only call split() once.
The bytecode for the first one:
public class javatesting.JavaTesting extends java.lang.Object{
public javatesting.JavaTesting();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2; //String 1;2;3
2: astore_1
3: aload_1
4: ldc #3; //String ;
6: invokevirtual #4; //Method java/lang/String.split:(Ljava/lang/String;)
[Ljava/lang/String;
9: astore_2
10: aload_2
11: astore_3
12: aload_3
13: arraylength
14: istore 4
16: iconst_0
17: istore 5
19: iload 5
21: iload 4
23: if_icmpge 41
26: aload_3
27: iload 5
29: aaload
30: astore 6
32: aconst_null
33: astore 6
35: iinc 5, 1
38: goto 19
41: return
}
And the bytecode for the second one:
public class javatesting.JavaTesting extends java.lang.Object{
public javatesting.JavaTesting();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2; //String 1;2;3
2: astore_1
3: aload_1
4: ldc #3; //String ;
6: invokevirtual #4; //Method java/lang/String.split:(Ljava/lang/String;)
[Ljava/lang/String;
9: astore_2
10: aload_2
11: arraylength
12: istore_3
13: iconst_0
14: istore 4
16: iload 4
18: iload_3
19: if_icmpge 37
22: aload_2
23: iload 4
25: aaload
26: astore 5
28: aconst_null
29: astore 5
31: iinc 4, 1
34: goto 16
37: return
}
It doesn't matter at all. Don't waste time with such type of optimization.
EDIT: If you ever want to care about such thing than you might use the second option since the array with the splits will exist only inside the for loop scope.
If you really want to see the difference in bytecode, use javap -c. Compiled with the Eclipse compiler, the first version (with a local variable in the source code) gives this:
public static void test(java.lang.String);
Code:
0: aload_0
1: ldc #16; //String ;
3: invokevirtual #18; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
6: astore_1
7: aload_1
8: dup
9: astore 5
11: arraylength
12: istore 4
14: iconst_0
15: istore_3
16: goto 27
19: aload 5
21: iload_3
22: aaload
23: astore_2
24: iinc 3, 1
27: iload_3
28: iload 4
30: if_icmplt 19
33: return
}
And the second:
public static void test(java.lang.String);
Code:
0: aload_0
1: ldc #16; //String ;
3: invokevirtual #18; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
6: dup
7: astore 4
9: arraylength
10: istore_3
11: iconst_0
12: istore_2
13: goto 24
16: aload 4
18: iload_2
19: aaload
20: astore_1
21: iinc 2, 1
24: iload_2
25: iload_3
26: if_icmplt 16
29: return
}
As you can see (if you can read this), the first variant does use an extra local variable slot (astore_1/aload_1 at the beginning). However (a) even if the bytecode was interpreted, the overhead of this would be negligible (it's just copying the reference) and (b) any JIT compiler will be able to optimize this, whether you have done some static bytecode optimization beforehand or not: local variable (1) is never subsequently used.
Local variables in the source code are mainly used for clarity, and for being able to re-use intermediate results. Fundamentally, the second variant (without an explicit local variable) will not let you re-use the result of split further down in your method (or even directly within the loop), whereas you could when naming your local variable in the code.
With the second pice of code I wonder if the compiler is smart enough
to do the split only once
The only difference between your two methods is the explicit declaration of a local variable in the source code.
In both cases, for (String f: flags.split(";")) { } is syntactic sugar (except that neither local variables are accessible) equivalent to :
int _hidden_i = 0;
for (String[] _hidden_arr=flags.split(";"); _hidden_i<_hidden_arr.length; _hidden_i++){
String f = _hidden_arr[_hidden_i];
// some stuff
}
The points to note are that:
With this notation, _hidden_i is never exposed, so you can never allocate something into _hidden_arr[_hidden_i].
Even a naive source-to-bytecode compiler knows that both _hidden_arr and _hidden_i will never be used again, so it doesn't copy them for further use.
What would have been fundamentally different would have been:
for (var i = 0; i < flags.split(";").length; i++) {
String f = flags.split(";")[i];
}
In the for(;;) notation, the first part is always executed once. The second expression is always executed before entering the loop block (to test whether or not to run it). The last is always executing after running the loop block (unless break is used).
You would generally want to avoid method calls that are potentially heavy in the second part, especially when it's always going to be the same result.
I think the compiler will optimize that. It might depend on the Java provider you hae chosen (sun, ibm, etc...) but this is so simple I would be surprised if every compiler did not.
Perhaps create a really large flags string and do some simple performance testing?
But, since this is easy, why not just do your first option....
I'm quite certain the the split will be done only one. Internally this code will be translated into a loop with an iterator, so the compiler will do the split once, create the resulting collection, then create the iterator over that collection.
The iterator would not be pointing to the right place if the split were to be repeated.
Try this:
public double calculateRunTime()
{
long startTime = System.currentTimeMillis();
split();
long endTime = System.currentTimeMillis();
return endTime - startTime;
}