25

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

4

8 回答 8

21

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.

于 2013-04-28T04:07:37.320 回答
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 循环的字节码。

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 循环添加字节码。

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.

三、结论

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

于 2013-04-28T05:38:45.487 回答
6

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

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

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

if (canLoopList(yourList)) {
    for(Type var : yourList) {
    ...
}
}
于 2016-03-14T12:02:58.937 回答
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.

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.

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.

于 2013-04-28T04:06:55.670 回答
1

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

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 或能够优雅地处理空集合。

于 2013-11-12T04:54:17.037 回答
0

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

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

你为什么那么想?如果 arr 为空,arr.length 如何不抛出异常?null 不能有长度。

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

于 2013-04-28T03:58:44.963 回答
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.

于 2013-04-28T04:05:29.623 回答
0

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

for (int i = 0; i < arr.length; i++) { } 
于 2013-04-28T04:13:32.007 回答