For each vs For

I posted a reply a few days ago, and I must have been tired as I missed a break statement. However the behavior of the code puzzled me:

String desc = "Key1: Val1 ; Key2: Val2 ; Key3: Val3 ; Key4: Val4 ; Key5: Val5";
String[] parts = desc.split(";");

System.out.println("For Each -------------------------");

for ( String part : parts )
{
    System.out.println(part);

    if ( part.contains("Key3") )
    {
        parts = part.split(":");

        System.out.println("***************** Value is: '" + parts[1].trim() + "'");
    }
}

System.out.println("For Each -------------------------");

Produces

For Each -------------------------
 Key1: Val1 
 Key2: Val2 
 Key3: Val3 
***************** Value is: 'Val3'
 Key4: Val4 
 Key5: Val5
For Each -------------------------

The strange part is that I changed the contents of parts midway through the foreach loop. This should have caused an error, as the value and length of parts changes.

The loop using an index

String desc = "Key1: Val1 ; Key2: Val2 ; Key3: Val3 ; Key4: Val4 ; Key5: Val5";
String[] parts = desc.split(";");

System.out.println("Index -------------------------");

for ( int i = 0; i < parts.length; i++ )
{
    System.out.println(parts[i]);

    if ( parts[i].contains("Key3") )
    {
        parts = parts[i].split(":");

        System.out.println("***************** Value is: '" + parts[1].trim() + "'");
    }
}

System.out.println("Index -------------------------");

Produces

Index -------------------------
 Key1: Val1 
 Key2: Val2 
 Key3: Val3 
***************** Value is: 'Val3'
Index -------------------------

The loop ends because the length of parts is changed, and the for test exits the loop. We never see Key4 and Key5.

The question is, why does the foreach loop continue, and with what values?

This was run using Eclipse and a jpage file.

Answer

We can take a look at the Java Language Specification (JLS) Section 14.14.2 to get an idea of how foreach loops (also called enhanced for statements in the JLS) work under the hood:

The enhanced for statement has the form:

EnhancedForStatement:
  for ( {VariableModifier} LocalVariableType VariableDeclaratorId : Expression ) Statement

EnhancedForStatementNoShortIf:
  for ( {VariableModifier} LocalVariableType VariableDeclaratorId : Expression ) StatementNoShortIf

  • If the type of Expression is a subtype of Iterable, then the translation is as follows.

Well, this is great if we are iterating over an iterator, but in your case, you are iterating over an array, so I will skip that section and go to:

  • 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.

    The enhanced for statement is equivalent to a basic for statement of the form:

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

The answer here is in the first line of that equivalent basic for loop. The array that was passed to the foreach loop, parts, is called Expression here. In the first line, the JLS is saying the foreach loop stores a reference to that array in a variable called #a. This is a lot of theoretical stuff, so let’s bring it back to your scenario by turning your foreach loop into the equivalent standard for loop. Your foreach loop is essentially:

for (String part : parts) {
    ...
}

When we expand this out to look like the for loop from the JLS, we get this:

String[] a = parts;

for (int i = 0; i < a.length; i++) {
    String part = a[i];
    ...
}

The key here is that this for loop is not actually iterating over parts. It’s iterating over a, and a was set to whatever array parts was referencing at the time of the assignment, which was before the for loop. After that, you can assign a new array to parts, but that doesn’t change the fact that a is still assigned to the original array. A short snippet of code demonstrating this:

String str1 = "hello";
String str2 = str1;
str1 = "goodbye";
System.out.println(str2);

Will print out:

hello

Because str2 retained a reference to the "hello" object. The same thing happens in your for loop with the arrays. a retains a reference to the original array object, even when you change what parts refers to.

Leave a Reply

Your email address will not be published. Required fields are marked *