Java for-each loop throw NullPointException

2022-09-01 17:15:01

下面的 java 段将生成 NullPointException,因为变量列表为 null,该列表将传递给 for-each 循环。

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

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

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

和/或

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

在这两种情况下,arr 为 null 都会导致 arr.length 或 arr.iterator() 抛出 NullPointException

我只是好奇为什么不翻译的原因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(); 
    }
}

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


答案 1

我看到以下原因,尽管我不知道是否有人考虑过这一点,何时实施以及实际原因是什么。

  1. 正如你所演示的,for(:)-loop的当前行为非常容易理解。另一种行为不是

  2. 这将是Java宇宙中唯一以这种方式表现的东西。

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

  4. 无论如何,使用null是一个坏习惯,所以NPE是告诉开发人员“你F***ed,清理你的烂摊子”的好方法,建议的行为只是隐藏问题。

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


答案 2

回答你的第一个问题:不,这三个循环不是等价的。其次,在这些循环中没有空检查;试图迭代不存在的东西没有任何意义。


假设我们有以下类:

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

我们将它分解为字节码,看看这些循环之间是否有任何区别。

1:增强 For 与迭代器

下面是增强型 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

下面是迭代器的字节码。

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

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

相似,但不完全相同。

2. 增强 For 与 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

它正在做一些不同的事情。它根本没有使用迭代器接口。相反,我们正在调用 ,它仅由 指定,而不是 .get()ListIterator

3. 结论

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


推荐