Как более эффективно использовать аннотации @Nullable и @Nonnull?

142

Я могу видеть , что @Nullableи @Nonnullаннотации могут быть полезными в предотвращении NullPointerExceptions , но они не распространяются очень далеко.

  • Эффективность этих аннотаций полностью падает после одного уровня косвенного обращения, поэтому, если вы добавляете только несколько, они не распространяются очень далеко.
  • Поскольку эти аннотации плохо соблюдаются, существует опасность предположить, что значение, отмеченное значком, @Nonnullне является нулевым, и, следовательно, не выполнять нулевые проверки.

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

public class Clazz {
    public static void main(String[] args){
        Clazz clazz = new Clazz();

        // this line raises a complaint with the IDE (IntelliJ 11)
        clazz.directPathToA(null);

        // this line does not
        clazz.indirectPathToA(null); 
    }

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }
}

Есть ли способ сделать эти аннотации более строгими и / или распространяться дальше?

Майк Райландер
источник
1
Мне нравится идея @Nullableили @Nonnull, но если они того стоят, очень "вероятно, что они
вызовут
Я думаю, что способ перейти в мир, в котором это вызывает ошибку или предупреждение компилятора, - это потребовать приведения к @Nonnullпри вызове @Nonnullметода с переменной, допускающей значение NULL. Конечно, преобразование с аннотацией невозможно в Java 7, но в Java 8 будет добавлена ​​возможность применять аннотации к использованию переменной, включая приведение типов. Так что это может стать возможным реализовать в Java 8.
Теодор Мердок
2
@TheodoreMurdock, да, в Java 8 приведение (@NonNull Integer) yсинтаксически возможно, но компилятору не разрешено испускать какой-либо конкретный байтовый код на основе аннотации. Для утверждений во время выполнения достаточно крошечных вспомогательных методов, как описано в bugs.eclipse.org/442103 (например, directPathToA(assertNonNull(y))), но учтите , это только помогает быстро потерпеть неудачу. Единственный безопасный способ - выполнить фактическую нулевую проверку (плюс, надеюсь, альтернативную реализацию в ветке else).
Стефан Херрманн
1
Было бы полезно в этом вопросе , чтобы сказать, @Nonnullи @Nullableвы говорите, так как есть несколько похожих annoations (см этого вопроса ). Вы про аннотации в пакете javax.annotation?
Джеймс Данн
1
@TJamesBoone Для контекста этого вопроса это не имеет значения, речь шла о том, как эффективно использовать любой из них.
Майк

Ответы:

66

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

Как сказано в книге «Чистый код», вам следует проверять параметры вашего общедоступного метода, а также избегать проверки инвариантов.

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

Педро Бочат
источник
10
Если возвращаемые значения могут быть пустыми, я настоятельно рекомендую использовать Optionalтип вместо простогоnull
Патрик
7
Необязательный не лучше, чем "null". Необязательный # get () генерирует исключение NoSuchElementException, в то время как использование значения null вызывает исключение NullPointerException. Оба являются исключениями RuntimeException без значимого описания. Я предпочитаю переменные, допускающие значение NULL.
30
4
@ 30thh, почему бы вам сначала использовать Optional.get () напрямую, а не Optional.isPresent () или Optional.map?
GauravJ
7
@GauravJ Почему вы должны использовать переменную, допускающую значение NULL, напрямую и не проверять, является ли она сначала нулевой? ;-)
30
6
Разница между Optionalи допускающим значение NULL в этом случае состоит в том, что Optionalлучше сообщает, что это значение может быть намеренно пустым. Конечно, это не волшебная палочка, и во время выполнения она может выйти из строя точно так же, как переменная, допускающая значение NULL. Однако, Optionalна мой взгляд , прием API программистом лучше .
user1053510
32

Помимо того, что ваша среда IDE дает вам подсказки при переходе nullк методам, которые ожидают, что аргумент не будет нулевым, есть и другие преимущества:

  • Инструменты статического анализа кода могут тестировать то же, что и ваша IDE (например, FindBugs)
  • Вы можете использовать АОП, чтобы проверить эти утверждения.

Это может сделать ваш код более удобным в сопровождении (поскольку вам не нужны nullпроверки) и менее подверженным ошибкам.

Уве Плонус
источник
9
Я сочувствую ОП здесь, потому что, хотя вы цитируете эти два преимущества, в обоих случаях вы использовали слово «может». Это означает, что нет никакой гарантии, что эти проверки действительно произойдут. Теперь эта разница в поведении может быть полезна для тестов, чувствительных к производительности, которые вы не хотели бы запускать в производственном режиме, для чего у нас есть assert. Я считаю , @Nullableи @Nonnullбыть полезными идеями, но я хотел бы больше силы за ними, а не с нами гипотезой о том, что один может делать с ними, что до сих пор оставляет возможность ничего не делать с ними.
seh
2
Вопрос в том, с чего начать. На данный момент его аннотации необязательны. Иногда мне бы хотелось этого, если бы они не были, потому что в некоторых обстоятельствах было бы полезно принудить их к соблюдению ...
Уве Плонус
Могу я спросить, что это за АОП?
Chris.Zou
@ Chris.Zou АОП означает аспектно-ориентированное программирование, например AspectJ
Уве Плонус
13

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

Новые аннотации типов в Java 8

В указанном выше блоге рекомендуется:

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

Джонатанж
источник
1
Понятно, но проверки lint по умолчанию предупреждают, что проверки на null во время выполнения не нужны, что, на первый взгляд, не одобряет эту рекомендацию.
swooby
1
@swooby Обычно я игнорирую предупреждения о ворсинах, если уверен, что мой код правильный. Эти предупреждения не являются ошибками.
jonathanzh
12

При компиляции исходного примера в Eclipse в соответствии с требованиями 1.8 и с включенным нулевым анализом на основе аннотаций мы получаем следующее предупреждение:

    directPathToA(y);
                  ^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

Это предупреждение сформулировано аналогично тем предупреждениям, которые вы получаете при смешивании обобщенного кода с устаревшим кодом с использованием необработанных типов («неконтролируемое преобразование»). У нас здесь точно такая же ситуация: у метода indirectPathToA()есть "устаревшая" подпись, так как он не определяет никакого нулевого контракта. Инструменты могут легко сообщить об этом, поэтому они будут преследовать вас по всем переулкам, где нулевые аннотации нужно распространять, но еще не сделано.

И при использовании умного @NonNullByDefault даже не нужно говорить это каждый раз.

Другими словами: «распространяются ли нулевые аннотации очень далеко», может зависеть от используемого вами инструмента и от того, насколько строго вы выполняете все предупреждения, выдаваемые этим инструментом. С нулевыми аннотациями TYPE_USE у вас, наконец, есть возможность позволить инструменту предупреждать вас обо всех возможных NPE в вашей программе, потому что пустота стала интричным свойством системы типов.

Стефан Херрманн
источник
8

Я согласен с тем, что аннотации «далеко не распространяются». Однако я вижу ошибку со стороны программиста.

Я понимаю Nonnullаннотацию как документацию. Следующий метод выражает необходимость (в качестве предварительного условия) ненулевого аргумента x.

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }

Следующий фрагмент кода содержит ошибку. Метод вызывает directPathToA()без принуждения, которое не yравно NULL (то есть, это не гарантирует предварительное условие вызываемого метода). Одна из возможностей - добавить Nonnullаннотацию к indirectPathToA()(распространяя предварительное условие). Вторая возможность - проверить нулевое значение yin indirectPathToA()и избежать вызова directPathToA()when yis null.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }
Андрес
источник
1
Распространение того, @Nonnull что нужно, indirectPathToA(@Nonnull Integer y)- это ИМХО плохая практика: вам нужно будет поддерживать распространение в полном стеке вызовов (поэтому, если вы добавите nullпроверку directPathToA(), вам нужно будет заменить @Nonnullна @Nullableв полном стеке вызовов). Для больших приложений это потребует огромных усилий.
Julien Kronegg,
Аннотация @Nonnull просто подчеркивает, что проверка аргумента на null находится на вашей стороне (вы должны гарантировать, что вы передаете ненулевое значение). Это не ответственность метода.
Александр Дробышевский
@Nonnull тоже разумная вещь, когда нулевое значение не имеет никакого смысла для этого метода
Александр Дробышевский
6

Если вы используете Kotlin, он поддерживает эти аннотации, допускающие значение NULL, в своем компиляторе и не позволит вам передать значение NULL в метод java, который требует аргумента, отличного от NULL. Событие, хотя этот вопрос изначально был нацелен на Java, я упоминаю эту функцию Kotlin, потому что она специально нацелена на эти аннотации Java, и вопрос звучал так: «Есть ли способ сделать эти аннотации более строгими и / или распространяться дальше?» и эта функция делает эти аннотации более строгими .

Класс Java с использованием @NotNullаннотации

public class MyJavaClazz {
    public void foo(@NotNull String myString) {
        // will result in an NPE if myString is null
        myString.hashCode();
    }
}

Класс Kotlin вызывает класс Java и передает значение null для аргумента, аннотированного с помощью @NotNull

class MyKotlinClazz {
    fun foo() {
        MyJavaClazz().foo(null)
    }
}  

Ошибка компилятора Kotlin при применении @NotNullаннотации.

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

см. http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

Майк Райландер
источник
3
Вопрос касается Java по его первому тегу, а не Kotlin.
seh
1
@seh см. обновление, чтобы узнать, почему этот ответ имеет отношение к этому вопросу.
Майк
2
Справедливо. Это приятная особенность Котлина. Я просто не думаю, что это удовлетворит тех, кто приезжает сюда, чтобы узнать о Java.
seh
но доступ по- myString.hashCode()прежнему будет вызывать NPE, даже если @NotNullон не добавлен в параметр. Так что более конкретно о его добавлении?
kAmol
@kAmol Разница в том, что при использовании Kotlin вы получите ошибку времени компиляции вместо ошибки времени выполнения . Аннотация предназначена для уведомления о том, что разработчик должен убедиться, что значение null не передано. Это не предотвратит передачу значения null во время выполнения, но не позволит вам написать код, который вызывает этот метод с помощью null (или с функцией, которая может возвращать null).
Майк Райландер
5

Что я делаю в своих проектах, так это активирую следующий параметр в проверке кода «Постоянные условия и исключения»:
предлагаю аннотацию @Nullable для методов, которые могут возвращать значение NULL, и сообщать обнуляемые значения, переданные параметрам без аннотации Инспекции

При активации все неаннотированные параметры будут обрабатываться как ненулевые, и поэтому вы также увидите предупреждение при косвенном вызове:

clazz.indirectPathToA(null); 

Для еще более сильных проверок может оказаться хорошим выбором Checker Framework (см. Этот хороший учебник .
Примечание : я еще не использовал его, и могут возникнуть проблемы с компилятором Jack: см. Этот отчет об ошибке)

TmTron
источник
4

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

Ионуй Г. Стан
источник
16
С этим нужно быть осторожным. Необязательный должен использоваться только в том случае, если значение действительно необязательно, а отсутствие которого используется в качестве шлюза для принятия решения для дальнейшей логики. Я видел, как этим злоупотребляли с полной заменой объектов на необязательные параметры и заменой нулевых проверок проверками на присутствие, которые упускают из виду.
Кристофер Перри
если вы ориентируетесь на JDK 8 или новее, предпочитайте использовать java.util.Optionalвместо класса Guava. См. Примечания / сравнение Guava для получения подробной информации о различиях.
AndrewF
1
«проверяет нуль заменены проверки на предмет наличия которых пропускает точку» вы можете разработать, то, на что точка находится ? На мой взгляд, это не единственная причина для выбора Optionals, но, безусловно, самая большая и лучшая.
скаббо
-3

Поскольку новая функция Java 8 является необязательной, вам больше не следует использовать @Nullable или @Notnull в своем собственном коде . Возьмите пример ниже:

public void printValue(@Nullable myValue) {
    if (myValue != null) {
        System.out.print(myValue);
    } else {
        System.out.print("I dont have a value");
}

Его можно переписать с помощью:

public void printValue(Optional<String> myValue) {
    if (myValue.ifPresent) {
        System.out.print(myValue.get());
    } else {
        System.out.print("I dont have a value");
}

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

Еще одно преимущество - код становится более читабельным . С добавлением Java 9 ifPresentOrElse функцию можно было бы даже записать как:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}
Матье Жемар
источник
Даже при Optionalэтом по-прежнему существует множество библиотек и фреймворков, которые используют эти аннотации, поэтому невозможно обновить / заменить все ваши зависимости версиями, обновленными для использования Optionals. OptionalОднако может помочь в ситуациях, когда вы используете null в своем собственном коде.
Майк