Java for-each循环抛出NullPointErexception

时间:2013-04-28 03:56:16

标签: java loops for-loop

以下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表达式可以减少代码嵌套。

8 个答案:

答案 0 :(得分:18)

我看到以下原因,虽然我不知道是否有人在考虑这个问题,实施时是什么,以及实际原因是什么。

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

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

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

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

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

答案 1 :(得分: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));
        }
    }
}

我们要将它分解为字节码,看看这些循环之间是否存在差异。

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

下面是迭代器的字节码。

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循环使用两个变量作为当前值(i)并将游标指向​​列表的其余部分(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

它正在做一些与众不同的事情。它根本没有使用Iterator界面。相反,我们正在拨打get(),只有List指定,而不是Iterator

3。结论

有一个正当的理由说明为什么我们解除引用的列表被假定为空 - 我们正在调用接口指定的方法。如果这些方法没有实现那么会有所不同:抛出一个UnsupportedOperationException。如果我们试图调用合同的对象不存在 - 那就没有意义了。

答案 2 :(得分:5)

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

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

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

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

答案 3 :(得分:1)

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

至于为什么这样设计,更大的问题是为什么不呢?

  • 很自然。 foreach循环的行为类似于没有魔术行为的循环

  • 这是理想的。人们通常不希望代码在发生错误时以静默方式失败。

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

答案 4 :(得分:1)

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

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

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

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

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

答案 5 :(得分:0)

“我认为(对象o:arr){}等同于

for(int i = 0; i&lt; arr.length; i ++){}“

为什么你会这么想?如果arr为null,arr.length怎么能不抛出异常? null不能有长度。

如果for(Object o :arr)没有抛出异常,那么必须意味着for(:)循环足够聪明,可以检查arr是否为null而不是尝试从中拉出项目。显然for(;;)循环并不那么聪明。

答案 6 :(得分:0)

避免使用if(o != null)警卫的空指针异常通常不是一个好主意 - o为空可能毫无意义,在这种情况下你想要抛出并记录异常如果结果是这样的话。

答案 7 :(得分:0)

你已经回答了你的问题,如果arr为null,arr.lenght会抛出NullPointerException。因此for (Object o : arr){ }等同于

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