Java for-each循环抛出NullPointException

Lin*_*lin 20 java loops for-loop

以下java段将导致NullPointException,因为变量列表为null,它传递给for-each循环.

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

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

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

和/或

for (Iterator<type> iter = arr.iterator(); iter.hasNext(); ){ 
   type var = iter.next(); 
}
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下,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(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Jen*_*der 18

我看到以下原因,虽然我不知道是否有人想到这个,什么时候实施,以及实际原因是什么.

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

  2. 这将是java宇宙中以这种方式表现的唯一事物.

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

  4. 无论如何,使用null是一个坏习惯,所以NPE是一个很好的方式告诉开发人员"你搞砸了,清理你的烂摊子",提出的行为只会隐藏问题.

  5. 如果你想在循环之前或之后对数组做任何其他事情,那么现在你将在代码中进行两次null检查.


Mak*_*oto 11

回答你的第一个问题:不,这三个循环不相同.其次,在这些循环中没有找到空检查; 尝试迭代不存在的东西没有任何意义.


假设我们有以下类:

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

1:增强For For Iterator

这是增强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
Run Code Online (Sandbox Code Playgroud)

下面是迭代器的字节码.

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
Run Code Online (Sandbox Code Playgroud)

最终,它确实看起来像是在做类似的事情. 他们使用相同的界面.有一个变化,即增强的for循环使用两个变量用于当前值(i)和游标到list(i$)的其余部分,而迭代器只需要光标来调用.next().

类似,但不完全相同.

2.增强For 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
Run Code Online (Sandbox Code Playgroud)

它正在做一些不同的事情.根本就没有使用Iterator界面. 相反,我们正在拨打电话get(),这只是由电话号码指定List,而不是Iterator.

3.结论

有一个合理的原因可以解释为什么我们解除引用的列表不是null - 我们正在调用接口指定的方法. 如果那些方法没有实现那就不同了:抛出一个UnsupportedOperationException.如果我们试图调用合同的对象不存在 - 那就没有意义了.


小智 5

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

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

然后在循环任何列表之前检查列表:

if (canLoopList(yourList)) {
    for(Type var : yourList) {
    ...
}
}
Run Code Online (Sandbox Code Playgroud)