Як ефективніше використовувати примітки @Nullable та @Nonnull?


140

Я бачу, що @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, але якщо вони варті, дуже "ймовірно вимагати дебатів"
Maarten Bodewes

Я думаю, що спосіб перейти до світу, де це спричиняє помилку компілятора або попередження, - це вимагати @Nonnullвиклику для виклику @Nonnullметоду із змінною змінною. Звичайно, в Java 7 неможлива трансляція з анотацією, але Java 8 додасть можливість застосовувати примітки до використання змінної, включаючи касти. Тож це можливо стане можливим втілити в Java 8.
Теодор Мердок

1
@TheodoreMurdock, так, у Java 8 акторський склад (@NonNull Integer) yсинтаксично можливий, але компілятору заборонено видавати будь-який конкретний байт-код на основі анотації. Для тверджень під час виконання достатньо невеликих допоміжних методів, як обговорювалося в bugs.eclipse.org/442103 (наприклад, directPathToA(assertNonNull(y))) - але зауважте, це лише допомагає швидко провалитися. Єдиний безпечний спосіб - це виконати фактичну нульову перевірку (плюс, сподіваємось, альтернативну реалізацію в іншій галузі).
Стефан Геррманн

1
У цьому питанні було б корисно сказати, про що @Nonnullі @Nullableви говорите, оскільки існує кілька подібних приміток (див. Це запитання ). Ви говорите про примітки в пакеті javax.annotation?
Джеймс Данн

1
@TJamesBoone Для контексту цього питання це не має значення, мова йшла про те, як ефективно використовувати будь-який з них.
Майк Риландер

Відповіді:


66

Коротка відповідь: Я думаю, що ці примітки корисні лише для вашого IDE, щоб попередити вас про потенційно помилкові помилки вказівника.

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

Ще одна корисна порада - це ніколи не повертати нульові значення, а використовувати замість цього Null Object Pattern .


10
Якщо значення, що повертаються, можуть бути порожніми, я настійно пропоную використовувати Optionalтип замість простийnull
Патрік

7
Необов’язково ist не краще, ніж "null". Необов'язково # get () кидає NoSuchElementException, тоді як використання null кидає NullPointerException. Обидва - це RuntimeException без змістовного опису. Я віддаю перевагу змінним змінним.
30

4
@ 30thh, чому б ви використовували Optional.get () безпосередньо, а не спочатку Optional.isPresent () або Optional.map?
GauravJ

7
@GauravJ Навіщо використовувати пряму змінну безпосередньо, а не перевіряти її, якщо вона спочатку є нульовою? ;-)
30

5
Різниця між Optionalі нульовим в цьому випадку полягає в тому, що Optionalкраще повідомляється, що це значення навмисно може бути порожнім. Звичайно, це не чарівна паличка, і під час виконання вона може вийти з ладу точно так само, як і змінну змінну. Однак прийом API програмістом краще Optionalна мій погляд.
користувач1053510

31

Крім вашого IDE, який дає вам підказки при переході nullдо методів, які очікують, що аргумент не буде нульовим, є й інші переваги:

  • Засоби аналізу статичного коду можуть перевірити те саме, що і ваш IDE (наприклад, FindBugs)
  • Ви можете використовувати AOP для перевірки цих тверджень

Це може допомогти вашому коду бути більш ретельним (оскільки вам не потрібні nullперевірки) та менш схильним до помилок.


9
Я співчуваю ОП тут, тому що, хоча ви цитуєте ці дві переваги, в обох випадках ви вживали слово "може". Це означає, що немає гарантії, що ці перевірки дійсно відбудуться. Тепер ця поведінкова різниця може бути корисною для тестів, що залежать від продуктивності, які ви хочете уникнути запуску у виробничому режимі, для якого у нас є assert. Я вважаю @Nullableі @Nonnullкорисними ідеї, але я хотів би, щоб за ними було більше сил, а не ми гіпотезували про те, що можна зробити з ними, що все ще залишає відкритим можливість нічого не робити з ними.
seh

2
Питання - з чого почати. На даний момент його антагони не є обов'язковими. Інколи мені хотілося б, якби їх не було, тому що за деяких обставин було б корисно примусити їх примусити ...
Uwe Plonus

Чи можу я запитати, що таке AOP, на який ви посилаєтесь тут?
Chris.Zou

@ Chris.Zou AOP означає аспект-орієнтоване програмування, наприклад, AspectJ
Uwe Plonus

13

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

Анотації нового типу Java 8

У наведеному вище блозі рекомендується:

Додаткові анотації типу не є заміною перевірки часу виконання Перед анотаціями типів, основне місце для опису таких речей, як нульовість або діапазони, було в javadoc. З анотаціями Type це повідомлення надходить у байт-код таким чином, щоб перевірити час компіляції. Ваш код все одно повинен перевіряти час виконання.


1
Зрозуміли, але чеки за замовчуванням перевіряють попередження, що нульові перевірки часу виконання не потрібні, що, на перший погляд, здається, що це рекомендація відштовхує.
swooby

1
@swooby Зазвичай я ігнорую попередження про пісні, якщо впевнений, що код правильний. Ці застереження не є помилками.
Джонатанж

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 null анотаціям , нарешті, ви маєте можливість дозволити інструменту попередити вас про всі можливі NPE у вашій програмі, оскільки nullness перетворився на інтрисичну властивість системи типу.


8

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

NonnullАнотацію я розумію як документацію. Наступний метод виражає, що вимагає (як попередня умова) ненульовий аргумент x.

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

Наступний фрагмент коду містить помилку. Метод викликає directPathToA()без примусового виконання, що yє ненульовим (тобто не гарантує попередньої умови виклику методу). Однією з можливостей є також додавання Nonnullпримітки до indirectPathToA()(розповсюдження передумови). Можливість два, щоб перевірити на недійсність yв indirectPathToA()і уникнути виклику , directPathToA()коли yодно нуль.

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

1
Пропагуючи @Nonnull мати indirectPathToA(@Nonnull Integer y)це ІМХО погану практику: вам потрібно буде mainain поширення на повному стеку викликів (так що якщо ви додаєте nullперевірку в directPathToA(), вам потрібно буде замінити @Nonnullна @Nullableв повному стеку викликів). Це було б величезним зусиллям з обслуговування для великих застосувань.
Жульєн Кронегг

Анотація @Nonnull просто підкреслює, що перевірка аргументу на нуль знаходиться на вашій стороні (ви повинні гарантувати, що ви передаєте ненулеве значення). Це не відповідальність методу.
Олександр Дробишевський

@Nonnull також є розумною річчю, коли null-значення не мають сенсу для цього методу
Олександр Дробишевський,

5

Що я роблю в своїх проектах - це активувати наступний параметр у коді перевірки коду "Постійні умови та винятки":
Запропонувати @Nullable анотацію для методів, які, можливо, можуть повернути нульові значення та повідомити про зведені значення, передані не-анотованим параметрам Перевірки

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

clazz.indirectPathToA(null); 

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


4

У Java я б використовував необов'язковий тип Guava . Будучи фактичним типом, ви отримуєте гарантії компілятора щодо його використання. Її легко обійти і отримати NullPointerException, але принаймні підпис методу чітко повідомляє, що він очікує як аргумент або що він може повернути.


16
Ви повинні бути обережними з цим. Необов’язковий слід використовувати лише тоді, коли значення є дійсно необов’язковим, а відсутність якого використовується як ворота рішення для подальшої логіки. Я бачив, як це зловживають за допомогою бланкової заміни об'єктів на необов'язкові, а нульові перевірки замінюються чеками на наявність, які не вистачають точки.
Крістофер Перрі

якщо ви орієнтуєтесь на JDK 8 або новішу версію, вважайте за краще використовувати java.util.Optionalзамість класу Guava. Деталі про відмінності див. У примітках / порівнянні Гуави.
AndrewF

1
"Нульові перевірки замінені на чеки на наявність, які пропускають точку", чи можете ви потім детальніше розглянути, про що йдеться ? На мою думку, це не єдина причина для необов'язкових, але це, безумовно, найбільша та найкраща.
scubbo

4

Якщо ви використовуєте Kotlin, він підтримує ці анотації про нівельованість у своєму компіляторі і не дасть вам передати нуль методу java, який вимагає ненульового аргументу. Подія, хоча спочатку це питання було націлене на Java, я згадую про цю функцію Котліна, оскільки вона спеціально орієнтована на ці анотації на Java, і було питання: "Чи є спосіб зробити ці анотації більш чіткими та / або поширюватися далі?" і ця особливість робить ці коментарі більш чіткими .

Клас Java з використанням @NotNullанотацій

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

Клас Котлін, який викликає клас 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, відповідно до її першого тегу, а не Котліна.
seh

1
@seh див. оновлення, чому ця відповідь стосується цього питання.
Майк Риландер

2
Досить справедливо. Це приємна риса Котліна. Я просто не думаю, що це задовольнить тих, хто приїжджає сюди, щоб дізнатися про Яву.
seh

але доступ до myString.hashCode()NPE все ще буде кинутим, навіть якщо @NotNullвін не доданий у параметрі. Отже, що конкретніше в його додаванні?
kAmol

@kAmol Різниця тут полягає в тому, що при використанні Kotlin ви отримаєте помилку часу компіляції замість помилки виконання . Анотація полягає в тому, щоб повідомити, що розробнику потрібно переконатися, що нуль не буде передано. Це не запобіжить передачі нуля під час виконання, але заважатиме писати код, який викликає цей метод з нулем (або з функцією, яка може повернути нуль).
Майк Риландер

-4

Оскільки нова функція 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, існує ще багато бібліотек та фреймворків, які використовують ці примітки, так що неможливо оновлювати / замінювати всі ваші залежності версіями, оновленими для використання необов’язкових. Optionalоднак може допомогти у ситуаціях, коли ви використовуєте null у власному коді.
Майк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.