27

The following java segment will result a NullPointException, since the variable list is null, which is pass to the for-each loop.

List<> arr = null;
for (Object o : arr) {
    System.out.println("ln "+o);
}

I think for (Object o : arr){ } is a equivalent to

for (int i = 0; i < arr.length; i++) { }

and/or

for (Iterator<type> iter = arr.iterator(); iter.hasNext(); ){ 
   type var = iter.next(); 
}

In either cases arr is null will cause arr.length or arr.iterator() throws a NullPointException

I'm just curious the reason why for (Object o : arr){ } is NOT translate to

if (arr!=null){
  for (int i = 0; i < arr.length; i++) { 
  }
}
and
if (arr!=null){
    for (Iterator<type> iter = arr.iterator(); iter.hasNext(); ){ 
       type var = iter.next(); 
    }
}

Include arr!=null expression could reduce code nesting.

3
  • If you know that arr is not null already, would you like it to go through an extra useless null check? I suppose it is no. Commented Apr 28, 2013 at 3:59
  • 1
    I can't see a reason to want to iterate over a known null list, though. Why should a null check be in the enhanced for loop as opposed to the list just never being null before iteration? Commented Apr 28, 2013 at 4:00
  • I'd assumed the "known null list" was just for the purpose of explaining the behaviour for the question Commented May 2, 2017 at 13:59

8 Answers 8

24

I see the following reasons, although I have no idea if anybody thought about this, when it was implemented, and what the actual reasons were.

  1. As you demonstrated the current behavior of the for(:)-loop is very easy to understand. The other behavior isn't

  2. It would be the only thing in the java universe behaving in this way.

  3. It wouldn't be equivalent to the simple for-loop so migrating between the two would actually not be equivalent

  4. Using null is a bad habit anyway, so NPEs are a nice way of telling the developer "you F***ed up, clean up your mess" with the proposed behavior the problem would just be hidden.

  5. What if you want to do anything else with the array before or after the loop ... now you would have the null check twice in your code.

Sign up to request clarification or add additional context in comments.

Comments

12

To answer your first question: no, these three loops are not equivalent. Second, there is no null check to be found in these loops; there isn't any sense in trying to iterate over that which does not exist.


Assume that we have the following class:

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class EnhancedFor {


    private List<Integer> dummyList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    private List<Integer> nullList = null;

    public void enhancedForDummyList() {
        for(Integer i : dummyList) {
            System.out.println(i);
        }
    }

    public void iteratorDummyList() {
        for(Iterator<Integer> iterator = dummyList.iterator(); iterator.hasNext();) {
            System.out.println(iterator.next());
        }
    }

    public void normalLoopDummyList() {
        for(int i = 0; i < dummyList.size(); i++) {
            System.out.println(dummyList.get(i));
        }
    }
}

We're going to decompose it to its bytecode and see if there's any difference between these loops.

1: Enhanced For vs. Iterator

Here's the bytecode for the enhanced for loop.

public enhancedForDummyList()V
   L0
    LINENUMBER 12 L0
    ALOAD 0
    GETFIELD EnhancedFor.dummyList : Ljava/util/List;
    INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator;
    ASTORE 1
   L1
   FRAME APPEND [java/util/Iterator]
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.hasNext ()Z
    IFEQ L2
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object;
    CHECKCAST java/lang/Integer
    ASTORE 2
   L3
    LINENUMBER 13 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L4
    LINENUMBER 14 L4
    GOTO L1
   L2
    LINENUMBER 15 L2
   FRAME CHOP 1
    RETURN
   L5
    LOCALVARIABLE i Ljava/lang/Integer; L3 L4 2
    LOCALVARIABLE i$ Ljava/util/Iterator; L1 L2 1
    LOCALVARIABLE this LEnhancedFor; L0 L5 0
    MAXSTACK = 2
    MAXLOCALS = 3

Below this is the bytecode for the iterator.

public iteratorDummyList()V
   L0
    LINENUMBER 24 L0
    ALOAD 0
    GETFIELD EnhancedFor.dummyList : Ljava/util/List;
    INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator;
    ASTORE 1
   L1
   FRAME APPEND [java/util/Iterator]
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.hasNext ()Z
    IFEQ L2
   L3
    LINENUMBER 25 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
    GOTO L1
   L2
    LINENUMBER 27 L2
   FRAME CHOP 1
    RETURN
   L4
    LOCALVARIABLE iterator Ljava/util/Iterator; L1 L2 1
    // signature Ljava/util/Iterator<Ljava/lang/Integer;>;
    // declaration: java.util.Iterator<java.lang.Integer>
    LOCALVARIABLE this LEnhancedFor; L0 L4 0
    MAXSTACK = 2
    MAXLOCALS = 2

Ultimately, it does look like they're doing very similar things. They're using the same interface. There is a variation in that the enhanced for loop is using two variables for the current value (i) and cursor to the rest of the list (i$), whereas the iterator only needs the cursor to invoke .next().

Similar, but not quite the same.

2. Enhanced For vs. for-Loop

Let's add in the bytecode for the for loop.

public normalLoopDummyList()V
   L0
    LINENUMBER 24 L0
    ICONST_0
    ISTORE 1
   L1
   FRAME APPEND [I]
    ILOAD 1
    ALOAD 0
    GETFIELD EnhancedFor.dummyList : Ljava/util/List;
    INVOKEINTERFACE java/util/List.size ()I
    IF_ICMPGE L2
   L3
    LINENUMBER 25 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 0
    GETFIELD EnhancedFor.dummyList : Ljava/util/List;
    ILOAD 1
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L4
    LINENUMBER 24 L4
    IINC 1 1
    GOTO L1
   L2
    LINENUMBER 27 L2
   FRAME CHOP 1
    RETURN
   L5
    LOCALVARIABLE i I L1 L2 1
    LOCALVARIABLE this LEnhancedFor; L0 L5 0
    MAXSTACK = 3
    MAXLOCALS = 2

It's doing something different. It's not using the Iterator interface at all. Instead, we're making calls to get(), which is only specified by the List, not the Iterator.

3. Conclusion

There's a valid reason as to why the list we're dereferencing is assumed not null - we're invoking methods specified by the interface. If those methods weren't implemented that'd be different: throw an UnsupportedOperationException. If the object we're trying to invoke the contract on didn't exist - that just doesn't make sense.

Comments

5

Looping null will cause a NullPointerException, so you must always check if the list is null, you can use this generic method:

public static boolean canLoopList(List<?> list) {
    if (list != null && !list.isEmpty()) {
        return true;
    }
    return false;
}

then before looping any list check the list:

if (canLoopList(yourList)) {
    for(Type var : yourList) {
    ...
}
}

Comments

2

The reason it does not insert a null check is because it is not defined to. You can find the rules for foreach loops in section 14.14.2 of the Java Language Specification.

As for why it is designed this way, the bigger question is why not?

  • It is natural. the foreach loop behaves like an equivalent for loop with no magic behavior

  • It is desired. People usually don't want code to fail silently when an error occurs.

The performance issue suggested by Alvin Wong was likely a minor consideration at best. The JVM will usually optimize away null checks in cases where the variable is always nonnull, so the performance impact is negligible.

Comments

1

You have already answered your question, if arr is null arr.lenght throws NullPointerException. Therefore for (Object o : arr){ } is a equivalent to

for (int i = 0; i < arr.length; i++) { } 

Comments

0

"I think for (Object o : arr){ } is a equivalent to

for (int i = 0; i < arr.length; i++) { }"

Why would you think that? how can arr.length not throw an exception if arr is null? null can't have a length.

If for(Object o :arr) doesn't throw an exception that must mean that the for(:) loop is smart enough to check to see if arr is null and not try to pull items out of it. Obviously the for(;;) loop is not as smart.

2 Comments

for(Object o :arr) is not smart to check arr is null
if it doesn't throw an exception, it must be! :)
0

It's often not a good idea to avoid null pointer exceptions with if(o != null) guards - it may make no sense at all for o to be null, in which case you want to throw and log an exception if that turns out to be the case.

Comments

0

If I have a null ArrayList, then how many objects does it contain? My answer is zero. So in my mind the enhanced for loop should not throw a NPE for

List<Object> myList = null;
for (Object obj : myList) {
    System.out.println(obj.toString());
}

but it does. Obviously this is not going to change now in the java spec so maybe they should introduce the elvis and safe navigation operators so this is supported:

List<Object> myList = null;
for (Object obj ?: myList) {
    System.out.println(obj?.toString());
}

Then developers have a choice over whether they want NPE to be thrown or be able to handle null collections gracefully.

2 Comments

"If I have a null ArrayList..." is nonsense. You can't have a null anything. null means you don't have one. It doesn't make sense to ask how many objects "it" contains if there is no "it". OTOH, if you have an empty ArrayList, that contains zero objects.
The proposed syntax makes sense; it would often be useful to not have to check the existence of a field before iterating over it; especially for third party libraries where you have no control over instantiating the list. e.g loop through the CC recipients in a javax.mail.MimeMessage

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.