Java for-each 循环抛出 NullPointException

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16259435/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 06:02:37  来源:igfitidea点击:

Java for-each loop throw NullPointException

javaloopsfor-loop

提问by Linlin

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; 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

在任何一种情况下 arr 为 null 都会导致 arr.length 或 arr.iterator() 抛出 NullPointException

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

我只是好奇为什么for (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.

包含 arr!=null 表达式可以减少代码嵌套。

采纳答案by Jens Schauder

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.

  1. 正如您所展示的 for(:) 循环的当前行为很容易理解。另一种行为不是

  2. 这将是 Java 世界中唯一以这种方式运行的东西。

  3. 它不等同于简单的 for 循环,因此在两者之间迁移实际上并不等同

  4. 无论如何,使用 null 是一个坏习惯,因此 NPE 是一种很好的方式来告诉开发人员“你操蛋了,清理你的烂摊子”,建议的行为只是隐藏了问题。

  5. 如果您想在循环之前或之后对数组执行任何其他操作,该怎么办……现在您将在代码中进行两次空检查。

回答by Yevgeny Simkin

"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; 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.

你为什么那么想?如果 arr 为空,arr.length 如何不抛出异常?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.

Iffor(Object o :arr)没有抛出异常,这一定意味着 for(:) 循环足够聪明,可以检查 arr 是否为 null 并且不尝试从中提取项目。显然 for(;;) 循环没有那么智能。

回答by Zim-Zam O'Pootertoot

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 oto 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,在这种情况下,如果事实证明是这种情况,您想要抛出并记录异常。

回答by Antimony

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

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

  • 这是自然的。foreach 循环的行为类似于没有魔法行为的等效 for 循环

  • 这是需要的。人们通常不希望代码在发生错误时无声无息地失败。

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.

Alvin Wong 提出的性能问题充其量只是一个次要的考虑。在变量总是非空的情况下,JVM 通常会优化掉空检查,因此性能影响可以忽略不计。

回答by Ahmet Karakaya

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

您已经回答了您的问题,如果 arr 为空 arr.lenght 抛出 NullPointerException。因此 for (Object o : arr){ }相当于

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

回答by Makoto

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

1:针对迭代器的增强

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

最终,看起来他们确实在做非常相似的事情。他们使用相同的界面。有一个变体,增强的 for 循环使用两个变量作为当前值 ( i) 和指向列表其余部分的游标 ( i$),而迭代器只需要游标来调用.next()

Similar, but not quite the same.

相似,但不完全相同。

2. Enhanced For vs. for-Loop

2. 增强的 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 Iteratorinterface at all.Instead, we're making calls to get(), which is only specified by the List, not the Iterator.

它正在做一些不同的事情。它根本不使用该Iterator界面。相反,我们正在调用get(),这仅由 指定List,而不是由 指定Iterator

3. Conclusion

3. 结论

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.

关于为什么我们要取消引用的列表被假定为非空的原因有一个有效的理由——我们正在调用接口指定的方法。如果这些方法没有实现,那就不同了:抛出一个UnsupportedOperationException. 如果我们试图调用合约的对象不存在——那是没有意义的。

回答by Shane Rowatt

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 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:

但确实如此。显然这不会在 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 还是能够优雅地处理空集合。

回答by Mohamed Aymen Charrada

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

循环 null 将导致 a NullPointerException,因此您必须始终检查列表是否为 null,您可以使用此通用方法:

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