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

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

  • Эффективность этих аннотаций полностью исчезает после одного уровня косвенности, поэтому, если вы добавляете только несколько, они не распространяются очень далеко.
  • Поскольку эти аннотации не соблюдаются, существует опасность того, что значение, отмеченное знаком @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

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

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

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

Ответ 2

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

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

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

Ответ 3

Составив исходный пример в 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 в вашей программе, потому что nullness становится неотъемлемым свойством системы типов.

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

Что я делаю в своих проектах, так это активировать следующую опцию в проверке кода "Constant conditions and exceptions":
Предложить @Nullable аннотацию для методов, которые могут возвращать значения null и сообщать значения NULL, переданные в не аннотированные параметры Проверки

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

clazz.indirectPathToA(null); 

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

Ответ 7

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

Ответ 8

Если вы используете Kotlin, он поддерживает эти аннотации с нулевым значением в своем компиляторе и не позволит вам передать null в java-метод, для которого требуется непустой аргумент. Событие, хотя этот вопрос был первоначально ориентирован на 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