Come utilizzare le annotazioni @Nullable e @Nonnull in modo più efficace?


140

Vedo che @Nullablee le @Nonnullannotazioni potrebbero essere utili per prevenire NullPointerExceptions ma non si propagano molto lontano.

  • L'efficacia di queste annotazioni diminuisce completamente dopo un livello di riferimento indiretto, quindi se ne aggiungi solo alcune non si propagano molto lontano.
  • Poiché queste annotazioni non vengono applicate correttamente, esiste il pericolo di presumere che un valore contrassegnato @Nonnullnon sia null e di conseguenza non eseguire controlli null.

Il codice seguente fa sì che un parametro contrassegnato con @Nonnullsia nullsenza sollevare lamentele. Emette un NullPointerExceptionquando viene eseguito.

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        
    }
}

C'è un modo per rendere queste annotazioni più rigorosamente applicate e / o propagare ulteriormente?


1
Mi piace l'idea di @Nullableo @Nonnull, ma se ne valgono la pena è molto "probabile che solleciti il ​​dibattito"
Maarten Bodewes,

Penso che il modo per spostarsi in un mondo in cui ciò causa un errore o un avvertimento del compilatore sia richiedere un cast @Nonnullquando si chiama un @Nonnullmetodo con una variabile nullable. Ovviamente, il casting con un'annotazione non è possibile in Java 7, ma Java 8 aggiungerà la possibilità di applicare annotazioni all'uso di una variabile, inclusi i cast. Quindi questo potrebbe diventare possibile implementare in Java 8.
Theodore Murdock

1
@TheodoreMurdock, sì, in Java 8 un cast (@NonNull Integer) yè sintatticamente possibile, ma a un compilatore non è consentito emettere alcun codice byte specifico in base all'annotazione. Per le asserzioni di runtime sono sufficienti piccoli metodi di supporto, come discusso in bugs.eclipse.org/442103 (ad es. directPathToA(assertNonNull(y))) - ma attenzione, questo aiuta solo a fallire velocemente. L'unico modo sicuro è quello di eseguire un controllo null effettivo (oltre, si spera, un'implementazione alternativa nel ramo else).
Stephan Herrmann,

1
Sarebbe utile in questa domanda dire di quale @Nonnulle di @Nullablecui stai parlando, in quanto vi sono più fastidi simili (Vedi questa domanda ). Stai parlando delle annotazioni nel pacchetto javax.annotation?
James Dunn,

1
@TJamesBoone Per il contesto di questa domanda non importa, si trattava di come usarne una in modo efficace.
Mike Rylander,

Risposte:


66

Risposta breve: immagino che queste annotazioni siano utili solo per il tuo IDE per avvisarti di errori del puntatore potenzialmente nulli.

Come detto nel libro "Clean Code", dovresti controllare i parametri del tuo metodo pubblico ed evitare anche di controllare gli invarianti.

Un altro buon consiglio non è mai restituire valori null, ma utilizzare invece Null Object Pattern .


10
Per valori di ritorno eventualmente vuoti, consiglio vivamente di usare il Optionaltipo invece di plainnull
Patrick

7
Opzionale non è migliore di "null". # Get () facoltativo genera NoSuchElementException mentre un utilizzo del null genera NullPointerException. Entrambi sono RuntimeException senza una descrizione significativa. Preferisco variabili nullable.
30

4
@ 30thh perché dovresti usare Optional.get () direttamente e non Optional.isPresent () o Optional.map prima?
GauravJ,

7
@GauravJ Perché dovresti usare direttamente una variabile nullable e non controllare, se prima fosse null? ;-)
30

5
La differenza tra Optionale nullable in questo caso è che Optionalcomunica meglio che questo valore può essere intenzionalmente vuoto. Certamente, non è una bacchetta magica e durante il runtime potrebbe fallire esattamente allo stesso modo della variabile nullable. Tuttavia, Optionalsecondo me la ricezione delle API da parte del programmatore è migliore .
user1053510

31

Oltre al tuo IDE che ti dà suggerimenti quando passi nulla metodi che prevedono che l'argomento non sia nullo, ci sono ulteriori vantaggi:

  • Gli strumenti di analisi del codice statico possono testare lo stesso del tuo IDE (ad es. FindBugs)
  • È possibile utilizzare AOP per verificare queste affermazioni

Questo può aiutare il tuo codice a essere più gestibile (poiché non hai bisogno di nullcontrolli) e meno soggetto a errori.


9
Sono d'accordo con l'OP qui, perché anche se citi questi due vantaggi, in entrambi i casi hai usato la parola "can". Ciò significa che non esiste alcuna garanzia che tali controlli vengano effettivamente effettuati. Ora, quella differenza comportamentale potrebbe essere utile per i test sensibili alle prestazioni che vorresti evitare di eseguire in modalità di produzione, per i quali abbiamo assert. Trovo @Nullableed @Nonnullessere idee utili, ma mi piacerebbe avere più forza dietro di loro, piuttosto che noi che ipotizziamo cosa si potrebbe fare con loro, il che lascia comunque aperta la possibilità di non farci niente.
seh

2
La domanda è da dove cominciare. Al momento le sue annotazioni sono facoltative. A volte mi piacerebbe se non lo fossero perché in alcune circostanze sarebbe utile farli rispettare ...
Uwe Plonus,

Posso chiederti a cosa si riferisce AOP qui?
Chris.Zou,

@ Chris.Zou AOP significa programmazione orientata all'aspetto, ad esempio AspectJ
Uwe Plonus

13

Penso che questa domanda originale indichi indirettamente una raccomandazione generale secondo cui è ancora necessario il controllo del puntatore null di runtime, anche se viene utilizzato @NonNull. Fare riferimento al seguente link:

Nuove annotazioni di tipo Java 8

Nel blog sopra, si consiglia di:

Le annotazioni di tipo opzionali non sostituiscono la convalida di runtime Prima delle annotazioni di tipo, la posizione principale per la descrizione di elementi come nullability o intervalli era nel javadoc. Con le annotazioni del tipo, questa comunicazione viene inserita nel bytecode in modo da consentire la verifica in fase di compilazione. Il codice deve comunque eseguire la convalida del runtime.


1
Capito, ma i controlli di lanugine predefiniti avvertono che i controlli null di runtime non sono necessari, il che a prima impressione sembra scoraggiare questa raccomandazione.
svenimento

1
@swooby Di solito ignoro gli avvisi di lanugine se sono sicuro che il mio codice sia corretto. Tali avvertimenti non sono errori.
Jonathanzh,

12

Compilando l'esempio originale in Eclipse alla conformità 1.8 e con l'analisi null basata su annotazioni abilitata, viene visualizzato questo avviso:

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

Questo avviso è formulato in analogia a quegli avvisi che ricevi quando mescoli il codice generato con il codice legacy usando tipi non elaborati ("conversione non selezionata"). Qui abbiamo la stessa identica situazione: il metodo indirectPathToA()ha una firma "legacy" in quanto non specifica alcun contratto nullo. Gli strumenti possono facilmente segnalarlo, quindi ti inseguiranno in tutti i vicoli in cui è necessario propagare le annotazioni null ma non lo sono ancora.

E quando si usa un intelligente @NonNullByDefaultnon dobbiamo nemmeno dirlo ogni volta.

In altre parole: se le annotazioni null "propagarsi molto lontano" possono dipendere dallo strumento che si utilizza e da quanto rigorosamente si presta attenzione a tutti gli avvisi emessi dallo strumento. Con TYPE_USE annotazioni nulle hai finalmente la possibilità di lasciare che lo strumento ti avverta di ogni possibile NPE nel tuo programma, perché la nullità è diventata una proprietà intrisica del sistema di tipi.


8

Concordo sul fatto che le annotazioni "non si propagano molto lontano". Tuttavia, vedo l'errore dalla parte del programmatore.

Capisco l' Nonnullannotazione come documentazione. Il seguente metodo esprime ciò che richiede (come condizione preliminare) un argomento non nullo x.

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

Il frammento di codice seguente contiene quindi un bug. Il metodo chiama directPathToA()senza imporre che ysia nullo (ovvero, non garantisce il presupposto del metodo chiamato). Una possibilità è quella di aggiungere anche Nonnullun'annotazione a indirectPathToA()(propagando la precondizione). La seconda possibilità è verificare la nullità di yin indirectPathToA()ed evitare la chiamata a directPathToA()quando yè null.

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

1
Propagare il @Nonnull necessario indirectPathToA(@Nonnull Integer y)è IMHO una cattiva pratica: sarà necessario mantenere la propagazione sullo stack di chiamate completo (quindi se si aggiunge un nullcheck-in directPathToA(), sarà necessario sostituirlo @Nonnullcon @Nullablelo stack di chiamate completo). Questo sarebbe un enorme sforzo di manutenzione per applicazioni di grandi dimensioni.
Julien Kronegg,

L'annotazione @Nonnull sottolinea solo che la verifica null dell'argomento è dalla tua parte (devi garantire che passi un valore non nullo). Non è responsabilità del metodo.
Alexander Drobyshevsky,

@Nonnull è anche una cosa sensata quando questo valore nullo non ha alcun senso per questo metodo
Alexander Drobyshevsky

5

Quello che faccio nei miei progetti è attivare la seguente opzione nell'ispezione del codice "Condizioni e eccezioni costanti":
suggerire l'annotazione @Nullable per i metodi che possono eventualmente restituire valori null e riportare valori nullable passati a parametri non annotati ispezioni

Quando attivato, tutti i parametri non annotati saranno trattati come non null e quindi vedrai anche un avviso sulla tua chiamata indiretta:

clazz.indirectPathToA(null); 

Per controlli ancora più efficaci, il Checker Framework può essere una buona scelta (vedi questo bel tutorial .
Nota : non l'ho ancora usato e potrebbero esserci problemi con il compilatore Jack: vedi questo bugreport


4

In Java userei il tipo opzionale di Guava . Essendo un tipo reale ottieni garanzie del compilatore sul suo utilizzo. È facile aggirarlo e ottenere un NullPointerException, ma almeno la firma del metodo comunica chiaramente cosa si aspetta come argomento o cosa potrebbe restituire.


16
Devi stare attento con questo. Opzionale dovrebbe essere usato solo dove un valore è veramente facoltativo e la cui assenza è usata come gate decisionale per ulteriore logica. Ho visto questo abuso con la sostituzione totale di Oggetti con Optionals e controlli null sostituiti con controlli di presenza che non raggiungono il punto.
Christopher Perry,

se scegli come target JDK 8 o versioni successive, preferisci utilizzare al java.util.Optionalposto della classe Guava. Vedi le note / il confronto di Guava per i dettagli sulle differenze.
AndrewF,

1
"controlli nulli sostituiti con i controlli per la presenza, che non coglie il punto" si può elaborare, allora, su che cosa il punto è ? Questo non è l' unico motivo per Optionals, secondo me, ma è sicuramente il più grande e il migliore.
Scubbo,

4

Se usi Kotlin, supporta queste annotazioni di nullability nel suo compilatore e ti impedirà di passare un metodo null a un metodo java che richiede un argomento non null. Tuttavia, sebbene questa domanda fosse originariamente indirizzata a Java, menziono questa funzionalità di Kotlin perché è specificamente indirizzata a queste annotazioni Java e la domanda era "Esiste un modo per rendere queste annotazioni più rigorosamente applicate e / o diffondersi ulteriormente?" e questa funzione rende queste annotazioni più rigorosamente applicate .

Classe Java tramite @NotNullannotazione

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

Classe Kotlin che chiama la classe Java e passa null per l'argomento annotato con @NotNull

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

Errore del compilatore di Kotlin che impone l' @NotNullannotazione.

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

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


3
La domanda si rivolge a Java, per il suo primo tag, e non a Kotlin.
seh

1
@seh vedi l'aggiornamento per cui questa risposta è rilevante per questa domanda.
Mike Rylander,

2
Giusto. Questa è una bella caratteristica di Kotlin. Non penso che soddisferà quelli che vengono qui per conoscere Java.
seh

ma l'accesso myString.hashCode()continuerà a lanciare NPE anche se @NotNullnon viene aggiunto nel parametro. Cosa c'è di più specifico nell'aggiungerlo?
kAmol,

@kAmol La differenza qui è che quando si utilizza Kotlin, si otterrà un errore di compilazione anziché un errore di runtime . L'annotazione informa che lo sviluppatore deve assicurarsi che non venga passato un null. Ciò non impedirà il passaggio di un null in fase di esecuzione, ma impedirà di scrivere codice che chiama questo metodo con un null (o con una funzione che può restituire null).
Mike Rylander,

-4

Dalla nuova funzionalità di Java 8 opzionale non è più necessario utilizzare @Nullable o @Notnull nel proprio codice . Prendi l'esempio seguente:

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

Potrebbe essere riscritto con:

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

L'uso di un facoltativo ti obbliga a verificare il valore null . Nel codice sopra, puoi accedere al valore solo chiamando il getmetodo.

Un altro vantaggio è che il codice diventa più leggibile . Con l'aggiunta di ifPresentOrElse di Java 9 , la funzione potrebbe anche essere scritta come:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}

Anche con Optional, ci sono ancora molte librerie e framework che usano queste annotazioni in modo tale che non è possibile aggiornare / sostituire tutte le dipendenze con versioni aggiornate per usare Optionals. Optionalpuò tuttavia essere d'aiuto in situazioni in cui si utilizza null all'interno del proprio codice.
Mike Rylander,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.