Java for-each循环抛出NullPointException。

时间:2021-10-02 14:28:50

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

下面的java段将会产生一个NullPointException,因为变量列表为null,这将传递给for-each循环。

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

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

我认为for (Object o: arr){}相当于。

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

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

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

在这两种情况下,arr都是null,这将导致arr。length或arr.iterator()抛出一个NullPointException。

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

我只是好奇为什么(Object o: arr){}不被翻译成。

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.

包括加勒比海盗!=null表达式可以减少代码嵌套。

8 个解决方案

#1


19  

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

    正如您所演示的,for(:)循环的当前行为非常容易理解。其他行为不是

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

    这将是java世界中唯一这样做的事情。

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

    它不等于简单的for循环,所以在两者之间迁移实际上是不相等的。

  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.

    不管怎样,使用null是一个坏习惯,所以NPEs是一种很好的方法,可以告诉开发人员“you F** ed up, clean up your mess”,而这个问题只会被隐藏起来。

  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.

    如果您想在循环之前或之后使用数组执行其他操作,那该怎么办呢?现在,在代码中会有两次空检查。

#2


11  

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.

这是增强for循环的字节码。

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().

最终,看起来他们做了非常相似的事情。他们使用的是同一个界面。对于循环的增强,有一个变化是使用两个变量为当前值(i)和游标到列表的其余部分(i$),而迭代器只需要游标来调用。

Similar, but not quite the same.

相似,但不完全相同。

2. Enhanced For vs. for-Loop

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

让我们在for循环中添加字节码。

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.

做不同的东西。它根本不使用迭代器接口。相反,我们要调用get(),它只由列表指定,而不是迭代器。

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.

有一个有效的理由说明为什么我们要取消引用的列表不是null——我们调用的是接口指定的方法。如果这些方法没有实现,那将是不同的:抛出一个UnsupportedOperationException。如果我们试图调用的对象不存在,那就是没有意义。

#3


4  

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

Looping null将导致NullPointerException,因此您必须始终检查列表是否为空,您可以使用这个泛型方法:

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) {
    ...
}
}

#4


1  

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.

它不插入空检查的原因是它没有被定义。您可以在Java语言规范的14.14.2节中找到foreach循环的规则。

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

    这是自然的。foreach循环的行为类似于没有任何魔法行为的循环。

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

王家卫提出的性能问题充其量也只是个小问题。在变量总是非空的情况下,JVM通常会优化空检查,因此性能影响可以忽略不计。

#5


1  

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

如果我有一个空的ArrayList,那么它包含了多少个对象?我的答案是零。所以在我看来,增强的for循环不应该抛出NPE。

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:

但它确实。很明显,这在java规范中是不会改变的所以也许他们应该引入elvis和安全导航操作符所以这是支持的:

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.

然后,开发人员可以选择是否要抛出NPE,或者能够优雅地处理空集合。

#6


0  

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

“我认为(Object o: arr){}是等同的。

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

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

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

你为什么这么想?加勒比海盗如何。如果arr为空,长度不抛出异常?null不能有长度。

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.

如果for(Object o:arr)不抛出异常,那就意味着for(:)循环足够智能,可以检查arr是否为空,而不是试图从中提取项。显然for(;;)循环没有那么聪明。

#7


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.

避免使用if(o != null)守卫来避免空指针异常通常不是一个好主意,如果o为null,那么就没有任何意义,在这种情况下,如果事实证明是这样,那么您希望抛出并记录一个异常。

#8


0  

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

你已经回答了你的问题,如果arr是空的。长度抛出NullPointerException。因此,对于(Object o: arr){}是等效的。

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

#1


19  

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

    正如您所演示的,for(:)循环的当前行为非常容易理解。其他行为不是

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

    这将是java世界中唯一这样做的事情。

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

    它不等于简单的for循环,所以在两者之间迁移实际上是不相等的。

  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.

    不管怎样,使用null是一个坏习惯,所以NPEs是一种很好的方法,可以告诉开发人员“you F** ed up, clean up your mess”,而这个问题只会被隐藏起来。

  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.

    如果您想在循环之前或之后使用数组执行其他操作,那该怎么办呢?现在,在代码中会有两次空检查。

#2


11  

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.

这是增强for循环的字节码。

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().

最终,看起来他们做了非常相似的事情。他们使用的是同一个界面。对于循环的增强,有一个变化是使用两个变量为当前值(i)和游标到列表的其余部分(i$),而迭代器只需要游标来调用。

Similar, but not quite the same.

相似,但不完全相同。

2. Enhanced For vs. for-Loop

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

让我们在for循环中添加字节码。

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.

做不同的东西。它根本不使用迭代器接口。相反,我们要调用get(),它只由列表指定,而不是迭代器。

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.

有一个有效的理由说明为什么我们要取消引用的列表不是null——我们调用的是接口指定的方法。如果这些方法没有实现,那将是不同的:抛出一个UnsupportedOperationException。如果我们试图调用的对象不存在,那就是没有意义。

#3


4  

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

Looping null将导致NullPointerException,因此您必须始终检查列表是否为空,您可以使用这个泛型方法:

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) {
    ...
}
}

#4


1  

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.

它不插入空检查的原因是它没有被定义。您可以在Java语言规范的14.14.2节中找到foreach循环的规则。

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

    这是自然的。foreach循环的行为类似于没有任何魔法行为的循环。

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

王家卫提出的性能问题充其量也只是个小问题。在变量总是非空的情况下,JVM通常会优化空检查,因此性能影响可以忽略不计。

#5


1  

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

如果我有一个空的ArrayList,那么它包含了多少个对象?我的答案是零。所以在我看来,增强的for循环不应该抛出NPE。

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:

但它确实。很明显,这在java规范中是不会改变的所以也许他们应该引入elvis和安全导航操作符所以这是支持的:

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.

然后,开发人员可以选择是否要抛出NPE,或者能够优雅地处理空集合。

#6


0  

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

“我认为(Object o: arr){}是等同的。

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

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

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

你为什么这么想?加勒比海盗如何。如果arr为空,长度不抛出异常?null不能有长度。

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.

如果for(Object o:arr)不抛出异常,那就意味着for(:)循环足够智能,可以检查arr是否为空,而不是试图从中提取项。显然for(;;)循环没有那么聪明。

#7


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.

避免使用if(o != null)守卫来避免空指针异常通常不是一个好主意,如果o为null,那么就没有任何意义,在这种情况下,如果事实证明是这样,那么您希望抛出并记录一个异常。

#8


0  

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

你已经回答了你的问题,如果arr是空的。长度抛出NullPointerException。因此,对于(Object o: arr){}是等效的。

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