Подтвердить что ты не робот

Java: как эффективно проверить нулевые указатели

Есть несколько шаблонов для проверки того, был ли параметр для метода присвоен значение 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, я могу отследить его до точной переменной, так как у меня есть только одна такая проверка на строку.

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

4b9b3361

Ответ 1

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

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

arg.getClass(); // null check

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


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

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

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

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

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


ОДНАКО, так как это вопрос, возможно, касается наиболее эффективного устранения проблем null, то я должен упомянуть, что мой предпочтительный метод для работы с null использует статический анализ, например. (например, @NonNull) a la JSR-305. Мой любимый инструмент для их проверки:

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

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

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

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

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

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


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

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

5.1.7 Использование утверждений, исключение IllegalArgumentException и NPE

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

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

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

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

Использовать 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

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

Использование утверждений о публичных методах строго запрещено; потому что сообщаемая ошибка была сделана в клиентском коде - будьте честны и расскажите им об этом с помощью исключения 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;
}

Ответ 2

Разве вы не слишком оптимизируете biiiiiiiiiiiiiii!!

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

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

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

Ответ 3

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

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

Ответ 5

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

вместо:

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

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

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

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

Ответ 9

Я бы использовал встроенный механизм Java assert.

assert arg != null;

Преимущество этого по всем другим методам заключается в том, что его можно отключить.

Ответ 10

Я предпочитаю метод 4, 5 или 6, причем # 4 применяется к общедоступным API-методам и 5/6 для внутренних методов, хотя # 6 будет более часто применяться к общедоступным методам.

/**
 * Method 4.
 * @param arg A String that should have some method called upon it. Will be ignored if
 * null, empty or whitespace only.
 */
public void method4(String arg) {
   // commons stringutils
   if (StringUtils.isNotBlank(arg) {
       arg.trim();
   }
}

/**
 * Method 5.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // Let NPE sort 'em out.
   arg.trim();
}

/**
 * Method 6.
 * @param arg A String that should have some method called upon it. Shouldn't be null.
 */
public void method5(String arg) {
   // use asserts, expect asserts to be enabled during dev-time, so that developers
   // that refuse to read the documentations get slapped on the wrist for still passing
   // null. Assert is a no-op if the -ae param is not passed to the jvm, so 0 overhead.

   assert arg != null : "Arg cannot be null"; // insert insult here.
   arg.trim();
}

Лучшим решением для обработки нулей является не использование нулей. Оберните сторонние или библиотечные методы, которые могут возвращать нули с нулевыми защитами, заменяя значение чем-то, что имеет смысл (например, пустая строка), но ничего не делает при использовании. Бросьте NPE, если нулевое значение действительно не должно быть передано, особенно в методах setter, где переданный объект не сразу вызван.

Ответ 11

Для этого нет голоса, но я использую небольшое изменение # 2, например

erStr += nullCheck (varName, String errMsg); // returns formatted error message

Обоснование: (1) Я могу перебрать кучу аргументов, (2) Метод nullCheck спрятан в суперклассе и (3) в конце цикла,

if (erStr.length() > 0)
    // Send out complete error message to client
else
    // do stuff with variables

В методе суперкласса ваш # 3 выглядит красиво, но я бы не выбрал исключение (какой смысл, кто-то должен его обрабатывать, а в качестве контейнера сервлетов tomcat будет игнорировать его, поэтому он может также be this()) С уважением, - М.С.

Ответ 12

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

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

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

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

Ответ 13

Хотя я согласен с общим согласием, предпочитая избегать 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 для Java (ECJ) не включает эту нулевую проверку, а запуск NullCheck, скомпилированный ECJ, не будет вызывать n NPE.)

Ответ 14

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

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