web-dev-qa-db-ja.com

Java for-each loop throw NullPointErexception

次のJavaセグメントは、変数リストがnullであり、for-eachループに渡されるため、NullPointExceptionが発生します。

_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式を含めると、コードのネストが減少する可能性があります。

18
Linlin

次の理由がわかりますが、誰がこれについて考えたのか、いつ実装されたのか、実際の理由は何だったのかわかりません。

  1. あなたが示したように、for(:)ループの現在の動作は非常に理解しやすいです。他の動作はそうではありません

  2. Javaユニバースがこのように動作する唯一のことです。

  3. 単純なforループと同等ではないため、2つの間を移行することは実際には同等ではありません。

  4. とにかくnullを使用するのは悪い習慣なので、NPEは、開発者に、問題が隠されただけの動作を「F *** ed up、clean up your mess」と伝えるいい方法です。

  5. ループの前後に配列を使用して何か他の処理を実行したい場合は、コードでnullチェックを2回実行します。

18
Jens Schauder

最初の質問に答えるために、いいえ、これら3つのループは同等ではありません。第二に、これらのループにはnullチェックがありません。存在しないものを反復しようとする意味はありません。


次のクラスがあると仮定します。

_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$_)に2つの変数を使用しているのに対し、イテレーターは.next()

類似、しかしまったく同じではありません。

2.強化された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インターフェイスをまったく使用していません。代わりに、Listではなく、Iteratorでのみ指定されているget()を呼び出しています。

3.結論

参照解除しているリストがnullでないと仮定される正当な理由があります-インターフェイスで指定されたメソッドを呼び出しています。それらのメソッドが「実装されていない」とは異なります:UnsupportedOperationExceptionをスローします。コントラクトを呼び出そうとしているオブジェクトが存在しなかった場合は、意味がありません。

11
Makoto

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) {
    ...
}
}

Nullチェックを挿入しないのは、定義されていないためです。 foreachループのルールは、Java言語仕様のセクション14.14.2にあります。

なぜこのように設計されているのかということに関して、大きな疑問はなぜそうではないのかということです。

  • 当然です。 foreachループは、魔法の動作のない同等のforループのように動作します

  • 望ましいです。通常、エラーが発生したときにコードが静かに失敗することは望ましくありません。

Alvin Wongによって提案されたパフォーマンスの問題は、せいぜいマイナーな考慮事項でした。 JVMは通常、変数が常に非nullである場合にnullチェックを最適化するため、パフォーマンスへの影響は無視できます。

1
Antimony

NullのArrayListがある場合、いくつのオブジェクトが含まれていますか?私の答えはゼロです。したがって、私の考えでは、拡張forループはNPEをスローすべきではありません

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

しかし、そうです。明らかにこれはJava仕様では変更されないので、多分彼らはエルビスと安全なナビゲーション演算子を導入する必要があります。

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

次に、開発者は、NPEをスローするか、nullコレクションを適切に処理できるかを選択できます。

1
Shane Rowatt

「(Object o:arr){}は

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

どうしてそんなことを考えるのか? arrがnullの場合、どのようにarr.lengthが例外をスローしないのですか? nullには長さを指定できません。

for(Object o :arr)が例外をスローしない場合、for(:)ループは、arrがnullであるかどうかを確認し、そこから項目を取り出そうとしないことを確認できるほどスマートであることを意味する必要があります。明らかにfor(;;)ループはそれほどスマートではありません。

0
Genia S.

if(o != null)ガードを使用してNULLポインター例外を回避することはよくありません。oがNULLである場合はまったく意味がない場合があります。それが事実であることが判明した場合。

Arrがnullの場合、arr.lenghtはNullPointerExceptionをスローします。したがって、for (Object o : arr){ }は次と同等です。

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