Java: come verificare in modo efficiente i puntatori nulli

Esistono alcuni schemi per verificare se a un metodo è stato assegnato un valore null .

Innanzitutto, quello classico. È comune nel codice auto-fatto e ovvio da capire.

 public void method1(String arg) { if (arg == null) { throw new NullPointerException("arg"); } } 

In secondo luogo, è ansible utilizzare un framework esistente. Quel codice sembra un po ‘più bello perché occupa solo una singola riga. Lo svantaggio è che potenzialmente chiama un altro metodo, che potrebbe rendere il codice eseguito un po ‘più lento, a seconda del compilatore.

 public void method2(String arg) { Assert.notNull(arg, "arg"); } 

Terzo, puoi provare a chiamare un metodo senza effetti collaterali sull’object. Questo può sembrare strano all’inizio, ma ha meno token rispetto alle versioni precedenti.

 public void method3(String arg) { arg.getClass(); } 

Non ho visto il terzo modello ampiamente utilizzato, e mi sembra quasi di averlo inventato io stesso. Mi piace per la sua brevità e perché il compilatore ha buone possibilità di ottimizzarlo completamente o di convertirlo in una singola istruzione macchina. Inoltre compilo il mio codice con le informazioni sul numero di riga, quindi se viene lanciata una NullPointerException , posso risalire alla variabile esatta, dal momento che ho un solo controllo per riga.

Quale controllo preferisci e perché?

Approach # 3: arg.getClass(); è intelligente, ma a meno che questo idioma non veda un’adozione diffusa, preferirei i metodi più chiari e più verbosi rispetto al salvataggio di pochi caratteri. Sono un tipo di programmatore “scrivi una volta, leggi molti”.

Gli altri approcci sono auto-documentanti: c’è un messaggio di log che puoi usare per chiarire cosa è successo – questo messaggio di log è utilizzato durante la lettura del codice e anche in fase di esecuzione. arg.getClass() , così com’è, non è auto-documentante. Potresti usare un commento almeno o chiarire ai revisori del codice:

 arg.getClass(); // null check 

Ma non hai ancora la possibilità di inserire un messaggio specifico nel runtime come puoi con gli altri metodi.


Approccio n. 1 vs n. 2 (null-check + NPE / IAE vs assert): cerco di seguire le linee guida come questa:

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

  • Usa assert per controllare i parametri su metodi privati
    assert param > 0;

  • Utilizzare null check + IllegalArgumentException per verificare i parametri sui metodi pubblici
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • Utilizzare il controllo nullo + NullPointerException dove necessario
    if (getChild() == null) throw new NullPointerException("node must have children");


TUTTAVIA , dal momento che questa domanda potrebbe riguardare la cattura di potenziali problemi null più efficiente, quindi devo menzionare il mio metodo preferito per trattare null usando l’analisi statica, ad esempio annotazioni di tipo (es. @NonNull ) a la JSR-305 . Il mio strumento preferito per controllarli è:

The Checker Framework:
Tipi di plug-in personalizzati per Java
https://checkerframework.org/manual/#checker-guarantees

Se è il mio progetto (ad es. Non una biblioteca con un’API pubblica) e se posso usare il Checker Framework in tutto:

  • Posso documentare più chiaramente la mia intenzione nell’API (ad es. Questo parametro non può essere nullo (il default), ma questo può essere nullo ( @Nullable , il metodo può restituire null, ecc.) Questa annotazione è corretta alla dichiarazione, piuttosto che più lontano nel Javadoc, quindi è molto più probabile che venga mantenuto.

  • l’analisi statica è più efficiente di qualsiasi controllo di runtime

  • l’analisi statica segnalerà in anticipo potenziali errori logici (ad esempio, ho provato a passare una variabile che può essere nullo a un metodo che accetta solo un parametro non nullo) piuttosto che a seconda del problema che si verifica in fase di esecuzione.

Un altro vantaggio è che lo strumento mi permette di inserire le annotazioni in un commento (ad es. `/ @Nullable /), quindi il mio codice libreria può essere compatibile con progetti con annotazioni di tipo e progetti non annotati (non che io abbia qualcuno di questi ).


Nel caso in cui il collegamento ritorni di nuovo , ecco la sezione dalla Guida per gli sviluppatori di GeoTools:

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

5.1.7 Uso di asserzioni, IllegalArgumentException e NPE

Il linguaggio Java ha reso disponibile per un paio di anni una parola chiave assertiva; questa parola chiave può essere utilizzata per eseguire solo controlli di debug. Mentre ci sono molti usi di questa funzione, uno comune è controllare i parametri del metodo su metodi privati ​​(non pubblici). Altri usi sono post-condizioni e invarianti.

Riferimento: programmazione con asserzioni

Le pre-condizioni (come i controlli degli argomenti nei metodi privati) sono in genere obiettivi facili per le asserzioni. Post-condizioni e invarianti sono a volte meno veloci ma più preziosi, poiché le condizioni non banali hanno più rischi da violare.

  • Esempio 1: dopo una proiezione della mappa nel modulo di riferimento, un’asserzione esegue la proiezione della mappa inversa e controlla il risultato con il punto originale (post-condizione).
  • Esempio 2: Nelle implementazioni DirectPosition.equals (Object), se il risultato è true, l’asserzione assicura che hashCode () sia identico come richiesto dal contratto Object.

Utilizzare Assert per controllare i parametri sui metodi privati

 private double scale( int scaleDenominator ){ assert scaleDenominator > 0; return 1 / (double) scaleDenominator; } 

È ansible abilitare le asserzioni con il seguente parametro della riga di comando:

 java -ea MyApp 

È ansible triggersre solo le asserzioni di GeoTools con il seguente parametro della riga di comando:

 java -ea:org.geotools MyApp 

Puoi disabilitare le asserzioni per un pacchetto specifico come mostrato qui:

 java -ea:org.geotools -da:org.geotools.referencing MyApp 

Utilizzare IllegalArgumentExceptions per verificare i parametri sui metodi pubblici

L’uso di asserzioni su metodi pubblici è severamente scoraggiato; perché l’errore riportato è stato fatto nel codice client – sii onesto e informalo in anticipo con un IllegalArgumentException quando si sono rovinati.

 public double toScale( int scaleDenominator ){ if( scaleDenominator > 0 ){ throw new IllegalArgumentException( "scaleDenominator must be greater than 0"); } return 1 / (double) scaleDenominator; } 

Utilizzare NullPointerException dove necessario

Se ansible eseguire i propri assegni nulli; lanciare un IllegalArgumentException o NullPointerException con informazioni dettagliate su cosa è andato storto.

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

Non stai ottimizzando un biiiiiiiiiiiiiiit troppo prematuramente !?

Vorrei solo usare il primo. È chiaro e conciso.

Raramente lavoro con Java, ma presumo che ci sia un modo per fare in modo che Assert funzioni solo sui build di debug, quindi sarebbe un no-no.

Il terzo mi dà i brividi, e penso che ricorrere immediatamente alla violenza se mai l’avessi visto in codice. Non è completamente chiaro cosa stia facendo.

Non dovresti lanciare NullPointerException. Se si desidera una NullPointerException, basta non controllare il valore e verrà automaticamente generato quando il parametro è nullo e si tenta di dereferenziarlo.

Controlla le classi di Apache Commons lang Validate e StringUtils.
Validate.notNull(variable) genererà un IllegalArgumentException se “variable” è null.
Validate.notEmpty(variable) genererà un IllegalArgumentException se “variable” è vuota (null o zero length “.
Forse ancora meglio:
String trimmedValue = StringUtils.trimToEmpty(variable) garantisce che “trimmedValue” non è mai nullo. Se “variable” è null, “trimmedValue” sarà la stringa vuota (“”).

È ansible utilizzare la class dell’utilità degli oggetti.

 public void method1(String arg) { Objects.requireNonNull(arg); } 

vedi http://docs.oracle.com/javase/7/docs/api/java/util/Objects.html#requireNonNull%28T%29

Il primo metodo è la mia preferenza perché trasmette i più intenti. Ci sono spesso scorciatoie che possono essere prese in programmazione, ma il mio punto di vista è che il codice più corto non è sempre un codice migliore.

A mio parere, ci sono tre problemi con il terzo metodo:

  1. L’intento non è chiaro per il lettore casuale.
  2. Anche se si dispone di informazioni sul numero di riga, i numeri di riga cambiano. In un vero sistema di produzione, sapere che c’era un problema in SomeClass alla linea 100 non ti dà tutte le informazioni che ti servono. È inoltre necessario conoscere la revisione del file in questione ed essere in grado di ottenere quella revisione. Tutto sumto, un sacco di problemi per quello che sembra essere molto poco vantaggioso.
  3. Non è del tutto chiaro perché pensi che la chiamata a arg.getClass possa essere ottimizzata. È un metodo nativo. A meno che HotSpot non sia codificato per avere una conoscenza specifica del metodo per questa esatta eventualità, probabilmente lascerà la chiamata da sola poiché non può sapere di eventuali potenziali effetti collaterali del codice C che viene chiamato.

La mia preferenza è quella di usare il n. 1 ogni volta che sento la necessità di un controllo nullo. Avere il nome della variabile nel messaggio di errore è ottimo per capire rapidamente che cosa è andato storto.

PS Non penso che l’ottimizzazione del numero di token nel file sorgente sia un criterio molto utile.

x == null è super veloce e può essere un paio di clock della CPU (inclusa la previsione del ramo che avrà successo). AssertNotNull sarà inline, quindi nessuna differenza lì.

x.getClass () non dovrebbe essere più veloce di x == null anche se utilizza trap. (motivo: la x sarà in qualche registro e controllando un registro vs un valore immediato è veloce, anche il ramo sarà previsto correttamente)

In conclusione: se non fai qualcosa di veramente strano, sarà ottimizzato dalla JVM.

La prima opzione è la più semplice ed è anche la più chiara.

Non è comune in Java, ma in C e C ++, dove l’operatore = può essere incluso in un’espressione nell’istruzione if e quindi portare a errori, è spesso consigliabile cambiare posizione tra la variabile e la costante in questo modo:

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

invece di:

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

prevenire errori del tipo:

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

Se apporti la modifica, il compilatore troverà questo tipo di errori per te.

Userò il meccanismo di asserzione Java integrato.

 assert arg != null; 

Il vantaggio di questo rispetto a tutti gli altri metodi è che può essere distriggersto.

Preferisco il metodo 4, 5 o 6, con il # 4 applicato ai metodi API pubblici e 5/6 per i metodi interni, sebbene il # 6 sia applicato più frequentemente ai metodi pubblici.

 /** * 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(); } 

La soluzione migliore per gestire null è non utilizzare null. Racchiude metodi di libreria o di terze parti che possono restituire valori null con guardie nulle, sostituendo il valore con qualcosa che abbia senso (come una stringa vuota) ma non fa nulla quando viene utilizzato. Lanciare NPE se non si deve passare un null, specialmente nei metodi setter in cui l’object passato non viene chiamato immediatamente.

Non c’è voto per questo, ma io uso una leggera variazione del # 2, come

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

Razionale: (1) Posso eseguire il loop su un gruppo di argomenti, (2) Il metodo nullCheck è nascosto in una superclass e (3) alla fine del ciclo,

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

Nel metodo della superclass, il tuo # 3 sembra bello, ma non vorrei lanciare un’eccezione (qual è il punto, qualcuno deve gestirlo e, come contenitore del servlet, tomcat lo ignorerà, quindi potrebbe anche essere questo ( )) Cordiali saluti, – MS

Primo metodo. Non farei mai il secondo o il terzo metodo, a meno che non siano implementati in modo efficiente dalla JVM sottostante. Altrimenti, questi due sono solo esempi di ottimizzazione prematura (con il terzo che ha una ansible penalizzazione delle prestazioni): non si vuole trattare e accedere ai metadati di class in punti di accesso generali.)

Il problema con gli NPE è che sono cose che intersecano molti aspetti della programmazione (e i miei aspetti, intendo qualcosa di più profondo e profondo di AOP). È un problema di progettazione del linguaggio (non dicendo che la lingua è ctriggers, ma che è una fondamentale imminente venuta … di qualsiasi linguaggio che consenta puntatori o riferimenti null).

Come tale, è meglio semplicemente affrontarlo esplicitamente come nel primo metodo. Tutti gli altri metodi sono (falliti) tentativi di semplificare un modello di operazioni, una complessità inevitabile che esiste sul modello di programmazione sottostante.

È un proiettile che non possiamo evitare di mordere. Trattalo esplicitamente così com’è, nel caso generale, meno doloroso in fondo alla strada.

Mentre sono d’accordo con il consenso generale di preferire per evitare l’hack getClass (), vale la pena notare che, a partire dalla versione 1.8.0_121 di OpenJDK, javac userà l’hack getClass () per inserire controlli nulli prima di creare espressioni lambda. Ad esempio, considera:

 public class NullCheck { public static void main(String[] args) { Object o = null; Runnable r = o::hashCode; } } 

Dopo averlo compilato con javac, puoi usare javap per vedere il bytecode eseguendo javap -c NullCheck . L’output è (in parte):

 Compiled from "NullCheck.java" public class NullCheck { public NullCheck(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()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 } 

Le istruzioni impostate su “lines” 3, 4 e 7 stanno fondamentalmente invocando o.getClass () e scartando il risultato. Se esegui NullCheck, otterrai una NullPointerException generata dalla riga 4.

Se questo è qualcosa che la gente di Java ha concluso è stata una ottimizzazione necessaria, o è solo un trucco modesto, non lo so. Tuttavia, in base al commento di John Rose su https://bugs.openjdk.java.net/browse/JDK-8042127?focusedCommentId=13612451&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13612451 , Sospetto che possa essere il caso che l’hack getClass (), che produce un controllo nullo implicito, possa essere sempre leggermente più performante della sua controparte esplicita. Detto questo, eviterei di usarlo a meno che un’attenta analisi comparativa mostrasse che ha fatto una differenza apprezzabile.

(È interessante notare che Eclipse Compiler For Java (ECJ) non include questo controllo Null e l’esecuzione di NullCheck come compilato da ECJ non genererà un NPE.)

Credo che il quarto e il modello più utile sia non fare nulla. Il tuo codice genererà NullPointerException o altre eccezioni un paio di righe dopo (se null è un valore non valido) e funzionerà correttamente se null è OK in questo contesto.

Credo che dovresti eseguire il controllo nullo solo se hai qualcosa a che fare con esso. Il controllo dell’eccezione è irrilevante nella maggior parte dei casi. Non dimenticare di menzionare in javadoc se il parametro può essere nullo.