以下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表达式可以减少代码嵌套。
答案 0 :(得分:18)
我看到以下原因,虽然我不知道是否有人在考虑这个问题,实施时是什么,以及实际原因是什么。
正如您所展示的那样for(:) - 循环的当前行为非常容易理解。其他行为不是
这将是java宇宙中唯一以这种方式表现的事情。
它不等同于简单的for-loop,因此在两者之间进行迁移实际上并不等效
无论如何,使用null是一个坏习惯,所以NPE是一种很好的方式,告诉开发人员“你搞砸了,清理你的烂摊子”,提出的行为只会隐藏问题。 / p>
如果您想在循环之前或之后对数组执行任何其他操作,那么现在您将在代码中进行两次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));
}
}
}
我们要将它分解为字节码,看看这些循环之间是否存在差异。
这是增强型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()
。
类似,但不完全相同。
让我们添加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
。如果我们试图调用合同的对象不存在 - 那就没有意义了。
答案 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循环不应该为
抛出NPEList<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++) { }