Java for-each цикл throw NullPointException
Следующий сегмент 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 может уменьшить вложенность кода.
Ответы
Ответ 1
Я вижу следующие причины, хотя я понятия не имею, думал ли кто-нибудь об этом, когда он был реализован, и каковы были фактические причины.
-
Как вы показали, текущее поведение цикла for (:) - очень легко понять. Другое поведение не
-
Это было бы единственное, что было бы в Java-сообществе таким образом.
-
Это не будет эквивалентно простому циклу for, поэтому миграция между ними фактически не будет эквивалентной.
-
Использование null - это плохая привычка, так что NPE - отличный способ сообщить разработчику, что "вы F *** разобрались, очистите свой беспорядок" с предлагаемым поведением, проблема будет просто скрыта.
-
Что делать, если вы хотите сделать что-либо еще с массивом до или после цикла... теперь у вас будет двойная проверка в коде.
Ответ 2
Чтобы ответить на ваш первый вопрос: нет, эти три цикла не эквивалентны. Во-вторых, в этих циклах нет нулевой проверки; нет смысла пытаться перебирать то, что не существует.
Предположим, что мы имеем следующий класс:
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: Enhanced For vter Iterator
Здесь байт-код для расширенного цикла.
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. Enhanced For vs. 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. Вывод
Существует веская причина, почему список разыменования, который мы разыменовываем, принимается не null - мы вызываем методы, указанные интерфейсом. Если бы эти методы не были реализованы, это было бы иначе: throw a UnsupportedOperationException
. Если объект, который мы пытаемся вызвать на контракт, не существует - это просто не имеет смысла.
Ответ 3
Looping null вызовет NullPointerException
, поэтому вы всегда должны проверить, является ли список нулевым, вы можете использовать этот общий метод:
public static boolean canLoopList(List<?> list) {
if (list != null && !list.isEmpty()) {
return true;
}
return false;
}
то перед циклом любого списка проверьте список:
if (canLoopList(yourList)) {
for(Type var : yourList) {
...
}
}
Ответ 4
Причина, по которой она не вставляет нулевую проверку, связана с тем, что она не определена. Вы можете найти правила для циклов foreach в разделе 14.14.2 Спецификации языка Java.
Что касается того, почему он спроектирован таким образом, тем больше вопрос, почему бы и нет?
-
Это естественно. цикл foreach ведет себя как эквивалент цикла без магического поведения
-
Желательно. Обычно люди не хотят, чтобы код терпел неудачу, когда возникает ошибка.
Проблема эффективности, предложенная Элвином Вонгом, скорее всего, была незначительной. JVM обычно оптимизирует нулевые проверки в случаях, когда переменная всегда не равна нулю, поэтому влияние производительности незначительно.
Ответ 5
Если у меня есть нулевой массив ArrayList, то сколько объектов оно содержит? Мой ответ равен нулю. Поэтому, по моему мнению, усиленный цикл не должен бросать 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 был брошен или мог обрабатывать нулевые коллекции изящно.
Ответ 6
"Я думаю, что для (Object o: arr) {} эквивалентно
for (int я = 0; я < arr.length; я ++) {} "
Почему ты так думаешь? как может arr.length не выдавать исключение, если arr null?
null не может иметь длину.
Если for(Object o :arr)
не генерирует исключение, это должно означать, что цикл for (:) достаточно умен, чтобы проверить, является ли arr нулевым, а не пытаться вытащить элементы из него. Очевидно, что цикл for (;;) не такой умный.
Ответ 7
Часто не рекомендуется избегать исключений с нулевым указателем с помощью атрибутов if(o != null)
- это может вообще не иметь смысла для o
равным нулю, и в этом случае вы хотите бросить и зарегистрировать исключение, если это окажется в этом случае.
Ответ 8
Вы уже ответили на свой вопрос, если arr равно null. arr.lenght выбрасывает NullPointerException. Поэтому for (Object o : arr){ }
эквивалентно
for (int i = 0; i < arr.length; i++) { }