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;我< arr.length;我+ +){ }
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
我只是好奇为什么(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.
包括加勒比海盗!=null表达式可以减少代码嵌套。
8 个解决方案
#1
19
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.
我看到了以下原因,尽管我不知道是否有人想到过这个,当它被执行的时候,以及实际的原因是什么。
-
As you demonstrated the current behavior of the for(:)-loop is very easy to understand. The other behavior isn't
正如您所演示的,for(:)循环的当前行为非常容易理解。其他行为不是
-
It would be the only thing in the java universe behaving in this way.
这将是java世界中唯一这样做的事情。
-
It wouldn't be equivalent to the simple for-loop so migrating between the two would actually not be equivalent
它不等于简单的for循环,所以在两者之间迁移实际上是不相等的。
-
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.
不管怎样,使用null是一个坏习惯,所以NPEs是一种很好的方法,可以告诉开发人员“you F** ed up, clean up your mess”,而这个问题只会被隐藏起来。
-
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.
如果您想在循环之前或之后使用数组执行其他操作,那该怎么办呢?现在,在代码中会有两次空检查。
#2
11
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
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()
.
最终,看起来他们做了非常相似的事情。他们使用的是同一个界面。对于循环的增强,有一个变化是使用两个变量为当前值(i)和游标到列表的其余部分(i$),而迭代器只需要游标来调用。
Similar, but not quite the same.
相似,但不完全相同。
2. Enhanced 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 Iterator
interface at all. Instead, we're making calls to get()
, which is only specified by the List
, not the Iterator
.
做不同的东西。它根本不使用迭代器接口。相反,我们要调用get(),它只由列表指定,而不是迭代器。
3. Conclusion
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.
有一个有效的理由说明为什么我们要取消引用的列表不是null——我们调用的是接口指定的方法。如果这些方法没有实现,那将是不同的:抛出一个UnsupportedOperationException。如果我们试图调用的对象不存在,那就是没有意义。
#3
4
Looping null will cause a NullPointerException
, so you must always check if the list is null, you can use this generic method:
Looping null将导致NullPointerException,因此您必须始终检查列表是否为空,您可以使用这个泛型方法:
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) {
...
}
}
#4
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.
它不插入空检查的原因是它没有被定义。您可以在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
这是自然的。foreach循环的行为类似于没有任何魔法行为的循环。
-
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.
王家卫提出的性能问题充其量也只是个小问题。在变量总是非空的情况下,JVM通常会优化空检查,因此性能影响可以忽略不计。
#5
1
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。
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,或者能够优雅地处理空集合。
#6
0
"I think for (Object o : arr){ } is a equivalent to
“我认为(Object o: arr){}是等同的。
for (int i = 0; i < arr.length; i++) { }"
for (int i = 0;我< arr.length;我+ +){ }”
Why would you think that? how can arr.length not throw an exception if arr is null? null can't have a length.
你为什么这么想?加勒比海盗如何。如果arr为空,长度不抛出异常?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.
如果for(Object o:arr)不抛出异常,那就意味着for(:)循环足够智能,可以检查arr是否为空,而不是试图从中提取项。显然for(;;)循环没有那么聪明。
#7
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.
避免使用if(o != null)守卫来避免空指针异常通常不是一个好主意,如果o为null,那么就没有任何意义,在这种情况下,如果事实证明是这样,那么您希望抛出并记录一个异常。
#8
0
You have already answered your question, if arr is null arr.lenght throws NullPointerException. Therefore for (Object o : arr){ }
is a equivalent to
你已经回答了你的问题,如果arr是空的。长度抛出NullPointerException。因此,对于(Object o: arr){}是等效的。
for (int i = 0; i < arr.length; i++) { }
#1
19
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.
我看到了以下原因,尽管我不知道是否有人想到过这个,当它被执行的时候,以及实际的原因是什么。
-
As you demonstrated the current behavior of the for(:)-loop is very easy to understand. The other behavior isn't
正如您所演示的,for(:)循环的当前行为非常容易理解。其他行为不是
-
It would be the only thing in the java universe behaving in this way.
这将是java世界中唯一这样做的事情。
-
It wouldn't be equivalent to the simple for-loop so migrating between the two would actually not be equivalent
它不等于简单的for循环,所以在两者之间迁移实际上是不相等的。
-
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.
不管怎样,使用null是一个坏习惯,所以NPEs是一种很好的方法,可以告诉开发人员“you F** ed up, clean up your mess”,而这个问题只会被隐藏起来。
-
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.
如果您想在循环之前或之后使用数组执行其他操作,那该怎么办呢?现在,在代码中会有两次空检查。
#2
11
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
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()
.
最终,看起来他们做了非常相似的事情。他们使用的是同一个界面。对于循环的增强,有一个变化是使用两个变量为当前值(i)和游标到列表的其余部分(i$),而迭代器只需要游标来调用。
Similar, but not quite the same.
相似,但不完全相同。
2. Enhanced 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 Iterator
interface at all. Instead, we're making calls to get()
, which is only specified by the List
, not the Iterator
.
做不同的东西。它根本不使用迭代器接口。相反,我们要调用get(),它只由列表指定,而不是迭代器。
3. Conclusion
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.
有一个有效的理由说明为什么我们要取消引用的列表不是null——我们调用的是接口指定的方法。如果这些方法没有实现,那将是不同的:抛出一个UnsupportedOperationException。如果我们试图调用的对象不存在,那就是没有意义。
#3
4
Looping null will cause a NullPointerException
, so you must always check if the list is null, you can use this generic method:
Looping null将导致NullPointerException,因此您必须始终检查列表是否为空,您可以使用这个泛型方法:
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) {
...
}
}
#4
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.
它不插入空检查的原因是它没有被定义。您可以在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
这是自然的。foreach循环的行为类似于没有任何魔法行为的循环。
-
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.
王家卫提出的性能问题充其量也只是个小问题。在变量总是非空的情况下,JVM通常会优化空检查,因此性能影响可以忽略不计。
#5
1
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。
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,或者能够优雅地处理空集合。
#6
0
"I think for (Object o : arr){ } is a equivalent to
“我认为(Object o: arr){}是等同的。
for (int i = 0; i < arr.length; i++) { }"
for (int i = 0;我< arr.length;我+ +){ }”
Why would you think that? how can arr.length not throw an exception if arr is null? null can't have a length.
你为什么这么想?加勒比海盗如何。如果arr为空,长度不抛出异常?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.
如果for(Object o:arr)不抛出异常,那就意味着for(:)循环足够智能,可以检查arr是否为空,而不是试图从中提取项。显然for(;;)循环没有那么聪明。
#7
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.
避免使用if(o != null)守卫来避免空指针异常通常不是一个好主意,如果o为null,那么就没有任何意义,在这种情况下,如果事实证明是这样,那么您希望抛出并记录一个异常。
#8
0
You have already answered your question, if arr is null arr.lenght throws NullPointerException. Therefore for (Object o : arr){ }
is a equivalent to
你已经回答了你的问题,如果arr是空的。长度抛出NullPointerException。因此,对于(Object o: arr){}是等效的。
for (int i = 0; i < arr.length; i++) { }