Java: Как эффективно проверить наличие нулевых указателей - PullRequest
23 голосов
/ 25 января 2011

Существует несколько шаблонов для проверки, было ли параметру для метода присвоено значение null.

Во-первых, классический. Это часто встречается в самодельном коде и понятно для понимания.

public void method1(String arg) {
  if (arg == null) {
    throw new NullPointerException("arg");
  }
}

Во-вторых, вы можете использовать существующий фреймворк. Этот код выглядит немного лучше, потому что он занимает только одну строку. Недостатком является то, что он потенциально вызывает другой метод, который может заставить код работать немного медленнее, в зависимости от компилятора.

public void method2(String arg) {
  Assert.notNull(arg, "arg");
}

В-третьих, вы можете попытаться вызвать метод без побочных эффектов для объекта. Поначалу это может показаться странным, но у него меньше токенов, чем в предыдущих версиях.

public void method3(String arg) {
  arg.getClass();
}

Я не видел третий широко используемый шаблон, и мне кажется, что я сам его изобрел. Мне нравится это за его краткость и потому, что у компилятора есть хорошие шансы полностью его оптимизировать или преобразовать в одну машинную инструкцию. Я также компилирую свой код с информацией о номере строки, поэтому, если выдается NullPointerException, я могу отследить его до точной переменной, поскольку у меня только одна такая проверка на строку.

Какой чек вы предпочитаете и почему?

Ответы [ 14 ]

22 голосов
/ 25 января 2011

Подход № 3: arg.getClass(); умный, но если этот идиома не получит широкого распространения, я бы предпочел более четкие, более подробные методы вместо сохранения нескольких символов,Я программист типа «пиши один раз, читай много».

Другие подходы - самодокументирование: есть лог-сообщение, которое можно использовать, чтобы уточнить, что произошло - это лог-сообщение используется при чтении кода.а также во время выполнения.arg.getClass() само по себе не является самодокументированным.Вы можете использовать комментарий, по крайней мере, чтобы разъяснить рецензентам код:

arg.getClass(); // null check

Но у вас все еще нет возможности поместить конкретное сообщение во время выполнения, как вы можете использовать другие методы.


Подход № 1 против № 2 (нулевая проверка + NPE / IAE против подтверждения): Я стараюсь следовать следующим правилам:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

  • Используйте assert для проверки параметров частных методов
    assert param > 0;

  • Использовать нулевую проверку + IllegalArgumentException для проверки параметров в открытых методах
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • Использовать нулевую проверку + NullPointerException, где это необходимо
    if (getChild() == null) throw new NullPointerException("node must have children");


HOWEVER , так как это может быть вопрос о выявлении потенциальных проблем null наиболеетогда я должен упомянуть, что мой предпочтительный метод для работы с null - это использование статического анализа, например, аннотации типов (например, @NonNull) а-ля JSR-305 .Мой любимый инструмент для их проверки:

The Checker Framework:
Пользовательские подключаемые типы для Java
https://checkerframework.org/manual/#checker-guarantees

Если это мой проект (например, не библиотека с общедоступнойAPI), и если я могу использовать Checker Framework везде:

  • , я могу более четко задокументировать свое намерение в API (например, этот параметр может быть не нулевым (по умолчанию), но этотможет быть нулевым (@Nullable; метод может возвращать нулевое значение и т. д.). Эта аннотация находится непосредственно в объявлении, а не дальше в Javadoc, поэтому вероятность ее сохранения гораздо выше.

  • статический анализ более эффективен, чем любая проверка во время выполнения

  • статический анализ заранее помечает потенциальные логические недостатки (например,попытался передать переменную, которая может быть нулевой, в метод, который принимает только ненулевой параметр), а не в зависимости от проблемы, возникающей во время выполнения.

Еще один бонус заключается в том, что инструментпозвольте мне разместить аннотациив комментарии (например, `/ @ Nullable /), так что код моей библиотеки может быть совместим с проектами с аннотациями типа и проектами без аннотации типов (не то чтобы у меня их нет).


В случае, если ссылка снова не работает , вот раздел из Руководства разработчика GeoTools:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

5.1.7 ИспользованиеУтверждения, IllegalArgumentException и NPE

Язык Java уже пару лет делает доступным ключевое слово assert;это ключевое слово может использоваться для проверки отладки.Хотя существует несколько способов использования этого средства, распространенным является проверка параметров метода в частных (не общедоступных) методах.Другие применения - постусловия и инварианты.

Ссылка: Программирование с утверждениями

Предварительные условия (например, проверка аргументов в частных методах)как правило, легкие цели для утверждений.Постусловия и инварианты иногда менее просты, но более ценны, поскольку нетривиальные условия имеют больше рисков для нарушения.

  • Пример 1: После проекции карты в ссылкахмодуль, утверждение выполняет обратную проекцию карты и проверяет результат с исходной точкой (постусловие).
  • Пример 2: В реализациях DirectPosition.equals (Object), если результатЗначение true, тогда утверждение гарантирует, что hashCode () идентичны, как того требует контракт объекта.

Используйте Assert для проверки параметров в приватных методах

private double scale( int scaleDenominator ){
 assert scaleDenominator > 0;
 return 1 / (double) scaleDenominator;
}

Вы можете включить утверждения с помощью следующего параметра командной строки:

java -ea MyApp

Вы можете включить только утверждения GeoTools со следующим параметром командной строки:

java -ea:org.geotools MyApp

Вы можете отключить утверждения для определенного пакета, как показано здесь:

java -ea:org.geotools -da:org.geotools.referencing MyApp

Используйте IllegalArgumentException для проверки параметров в открытых методах

Использование утверждений в открытых методах строго не рекомендуется; потому что сообщаемая ошибка была сделана в коде клиента - будьте честны и скажите им заранее с IllegalArgumentException, когда они облажались.

public double toScale( int scaleDenominator ){
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}

При необходимости используйте исключение NullPointerException

Если возможно, выполнить собственные проверки на ноль; выбрасывание IllegalArgumentException или NullPointerException с подробной информацией о том, что пошло не так.

public double toScale( Integer scaleDenominator ){
 if( scaleDenominator == null ){
 throw new NullPointerException( "scaleDenominator must be provided");
 }
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}
12 голосов
/ 25 января 2011

Не слишком ли преждевременно вы оптимизируете biiiiiiiiiiiiiit!?

Я бы просто использовал первое. Это ясно и кратко.

Я редко работаю с Java, но я предполагаю, что есть способ заставить Assert работать только на отладочных сборках, так что это будет нет-нет.

Третий дает мне мурашки по коже, и я думаю, что я немедленно прибегну к насилию, если я когда-нибудь видел это в коде. Совершенно неясно, что он делает.

5 голосов
/ 30 января 2015

Вы можете использовать Класс Утилиты Объектов.

public void method1(String arg) {
  Objects.requireNonNull(arg);
}

см. http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#requireNonNull%28T%29

5 голосов
/ 25 января 2011

Вы не должны выбрасывать исключение NullPointerException. Если вы хотите исключение NullPointerException, просто не проверяйте значение, и оно будет выброшено автоматически, когда параметр имеет значение null, и вы попытаетесь разыменовать его.

Ознакомьтесь с классами apache commons lang Validate и StringUtils.
Validate.notNull(variable) будет выдано исключение IllegalArgumentException, если переменная равна нулю.
Validate.notEmpty(variable) сгенерирует исключение IllegalArgumentException, если «переменная» пуста (нулевая или нулевая длина ».
Возможно, даже лучше:
String trimmedValue = StringUtils.trimToEmpty(variable) гарантирует, что «trimmedValue» никогда не будет нулевым. Если переменная равна нулю, то trimmedValue будет пустой строкой ("").

3 голосов
/ 25 января 2011

По моему мнению, есть три проблемы с третьим методом:

  1. Цель неясна для случайного читателя.
  2. Даже если у вас есть информация о номере строки, номера строкименять.В реальной производственной системе знание о том, что в SomeClass в строке 100 возникла проблема, не дает вам всей необходимой информации.Вы также должны знать ревизию рассматриваемого файла и иметь возможность добраться до этой ревизии.В общем, много хлопот за то, что, кажется, очень мало пользы.
  3. Не совсем понятно, почему вы думаете, что вызов arg.getClass может быть оптимизирован.Это родной метод.Если HotSpot не закодирован, чтобы иметь определенные знания о методе для этой точной возможности, он, вероятно, оставит вызов в покое, так как он не может знать о любых потенциальных побочных эффектах вызываемого кода C.

Я предпочитаю использовать # 1 всякий раз, когда я чувствую, что есть необходимость в нулевой проверке.Наличие имени переменной в сообщении об ошибке отлично подходит для быстрого выяснения, что именно пошло не так.

PS Я не думаю, что оптимизация количества токенов в исходном файле является очень полезным критерием.

3 голосов
/ 25 января 2011

Первый метод - мое предпочтение, потому что он передает самые намерения. Часто в программировании можно использовать ярлыки, но я считаю, что более короткий код не всегда лучше кода.

2 голосов
/ 25 января 2011

x == ноль суперскоростной, и это может быть пара тактовых частот процессора (включая прогноз ветвления, который будет успешным).AssertNotNull будет встроенным, поэтому никакой разницы нет.

x.getClass () не должен быть быстрее, чем x == null, даже если он использует trap.(причина: x будет в каком-то регистре, и проверка регистра против непосредственного значения выполняется быстро, ветвление также будет правильно предсказано)

Итог: если вы не сделаете что-то действительно странное, это 'будет оптимизирована JVM.

1 голос
/ 27 февраля 2017

Хотя я согласен с общим мнением о предпочтении избегать взлома getClass (), стоит отметить, что начиная с версии OpenJDK 1.8.0_121, javac будет использовать взлом getClass () для вставки нулевых проверок перед созданием лямбдывыражения.Например, рассмотрим:

public class NullCheck {
  public static void main(String[] args) {
    Object o = null;
    Runnable r = o::hashCode;
  }
}

После компиляции с помощью javac вы можете использовать javap, чтобы увидеть байт-код, запустив javap -c NullCheck.Вывод (частично):

Compiled from "NullCheck.java"
public class NullCheck {
  public NullCheck();
    Code:
       0: aload_0
       1: invokespecial #1    // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aconst_null
       1: astore_1
       2: aload_1
       3: dup
       4: invokevirtual #2    // Method java/lang/Object.getClass:()Ljava/lang/Class;
       7: pop
       8: invokedynamic #3, 0 // InvokeDynamic #0:run:(Ljava/lang/Object;)Ljava/lang/Runnable;
      13: astore_2
      14: return
}

Команда, установленная в «строках» 3, 4 и 7, в основном вызывает o.getClass () и отбрасывает результат.Если вы запустите NullCheck, вы получите исключение NullPointerException из строки 4.

Было ли это то, что, как пришли к выводу Java-пользователи, было необходимой оптимизацией, или это просто дешевый хак, я не знаю.Однако, основываясь на комментарии Джона Роуза в https://bugs.openjdk.java.net/browse/JDK-8042127?focusedCommentId=13612451&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13612451,, я подозреваю, что действительно может быть случай, когда хак getClass (), который производит неявную нулевую проверку, может быть даже немного более производительным, чем его явный аналог.Тем не менее, я бы избегал его использования, если бы тщательный бенчмаркинг не показал, что он имеет какое-либо заметное значение.

(Интересно, что Eclipse Compiler For Java (ECJ) делает , а не включите эту проверку на ноль, и запуск NullCheck, скомпилированный ECJ, не вызовет NPE.)

1 голос
/ 25 января 2011

Первый вариант самый простой, а также самый понятный.

Это не распространено в Java, но в C и C ++, где оператор = может быть включен в выражение в операторе if и, следовательно, приводит к ошибкам, часто рекомендуется переключать места между переменной и константой, какэто:

if (NULL == variable) {
   ...
}

вместо:

if (variable == NULL) {
   ...
}

предотвращение ошибок типа:

if (variable = NULL) { // Assignment!
   ...
}

Если вы сделаете изменение, компилятор найдет этот видошибок для вас.

0 голосов
/ 11 марта 2011

Первый способ.Я бы никогда не сделал второй или третий метод, если только они не будут эффективно реализованы базовой JVM.В противном случае эти два примера являются просто примерами преждевременной оптимизации (третий может привести к снижению производительности - вам не нужно иметь дело с метаданными класса и обращаться к ним в общих точках доступа.)

Проблема сNPE заключается в том, что они - вещи, которые пересекают многие аспекты программирования (и мои аспекты, я имею в виду нечто более глубокое и более глубокое, чем АОП).Это проблема языкового дизайна (не говоря о том, что язык плохой, но это один из основных недостатков ... любого языка, который допускает нулевые указатели или ссылки.)

Как таковой, он лучшепросто разобраться с этим явно, как в первом методе.Все остальные методы - (неудачные) попытки упростить модель операций, неизбежная сложность, которая существует в базовой модели программирования.

Это пуля, которую мы не можем избежать, чтобы укусить.Иметь дело с этим в явном виде - в общем случае - менее болезненным в будущем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...