Wie kann man die Annotationen @Nullable und @Nonnull effektiver nutzen?

140

Ich kann das sehen @Nullableund @NonnullAnmerkungen könnten hilfreich sein, um NullPointerExceptions zu verhindern, aber sie verbreiten sich nicht sehr weit.

  • Die Effektivität dieser Anmerkungen nimmt nach einer Indirektionsebene vollständig ab. Wenn Sie also nur einige hinzufügen, breiten sie sich nicht sehr weit aus.
  • Da diese Anmerkungen nicht gut durchgesetzt werden, besteht die Gefahr, dass ein mit gekennzeichneter Wert @Nonnullnicht null ist und folglich keine Nullprüfungen durchgeführt werden.

Der folgende Code verursacht einen Parameter mit markiert @Nonnullwerden , nullohne irgendwelche Beschwerden zu erhöhen. Es wirft ein, NullPointerExceptionwenn es ausgeführt wird.

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

Gibt es eine Möglichkeit, diese Anmerkungen strenger durchzusetzen und / oder weiter zu verbreiten?

Mike Rylander
quelle
1
Ich mag die Idee von @Nullableoder @Nonnull, aber wenn sie es wert sind, ist es sehr "wahrscheinlich, Debatte zu erbitten"
Maarten Bodewes
Ich denke, der Weg zu einer Welt, in der dies einen Compilerfehler oder eine Warnung verursacht, besteht darin, @Nonnullbeim Aufrufen einer @NonnullMethode mit einer nullbaren Variablen eine Umwandlung in zu erfordern . Natürlich ist das Casting mit einer Annotation in Java 7 nicht möglich, aber Java 8 bietet die Möglichkeit, Annotationen auf die Verwendung einer Variablen anzuwenden, einschließlich Casts. So kann dies in Java 8 implementiert werden.
Theodore Murdock
1
@TheodoreMurdock, ja, in Java 8 ist eine Umwandlung (@NonNull Integer) ysyntaktisch möglich, aber ein Compiler darf keinen bestimmten Bytecode basierend auf der Annotation ausgeben. Für Laufzeit-Assertions sind winzige Hilfsmethoden ausreichend, wie in bugs.eclipse.org/442103 (z. B. directPathToA(assertNonNull(y))) beschrieben - aber wohlgemerkt , dies hilft nur, schnell zu scheitern. Der einzig sichere Weg besteht darin, eine tatsächliche Nullprüfung durchzuführen (und hoffentlich eine alternative Implementierung im Zweig else).
Stephan Herrmann
1
In dieser Frage wäre es hilfreich zu sagen, über welche @Nonnullund @NullableSie sprechen, da es mehrere ähnliche Anmerkungen gibt (siehe diese Frage ). Sprechen Sie über die Anmerkungen im Paket javax.annotation?
James Dunn
1
@TJamesBoone Für den Kontext dieser Frage spielt es keine Rolle, hier ging es darum, wie man eine von ihnen effektiv einsetzt.
Mike Rylander

Antworten:

66

Kurze Antwort: Ich denke, diese Anmerkungen sind nur für Ihre IDE nützlich, um Sie vor potenziell Nullzeigerfehlern zu warnen.

Wie im Buch "Code bereinigen" erwähnt, sollten Sie die Parameter Ihrer öffentlichen Methode überprüfen und auch die Überprüfung von Invarianten vermeiden.

Ein weiterer guter Tipp ist, niemals Nullwerte zurückzugeben, sondern stattdessen das Null-Objektmuster zu verwenden.

Pedro Boechat
quelle
10
Für Rückgabewerte, die möglicherweise leer sind, empfehle ich dringend, den OptionalTyp anstelle von null
Patrick
7
Optional ist nicht besser als "null". Optional # get () löst NoSuchElementException aus, während eine Verwendung von null NullPointerException auslöst. Beide sind RuntimeException ohne aussagekräftige Beschreibung. Ich bevorzuge nullfähige Variablen.
30.
4
@ 30thh warum sollten Sie Optional.get () direkt und nicht zuerst Optional.isPresent () oder Optional.map verwenden?
GauravJ
7
@GauravJ Warum sollten Sie eine nullfähige Variable direkt verwenden und nicht prüfen, ob sie zuerst null ist? ;-)
30.
5
Der Unterschied zwischen Optionalund nullable in diesem Fall besteht darin, dass Optionalbesser kommuniziert wird, dass dieser Wert absichtlich leer sein kann. Sicherlich ist es kein Zauberstab und in der Laufzeit kann es genauso fehlschlagen wie eine nullfähige Variable. Allerdings ist der API-Empfang durch Programmierer Optionalmeiner Meinung nach besser .
user1053510
31

Abgesehen von Ihrer IDE, die Ihnen Hinweise gibt, wenn Sie nullan Methoden übergeben, bei denen erwartet wird, dass das Argument nicht null ist, gibt es weitere Vorteile:

  • Tools zur Analyse statischer Codes können dieselben Tests wie Ihre IDE durchführen (z. B. FindBugs).
  • Sie können AOP verwenden, um diese Aussagen zu überprüfen

Dies kann dazu beitragen, dass Ihr Code besser gewartet werden kann (da Sie ihn nicht benötigen null Überprüfungen ) und weniger fehleranfällig ist.

Uwe Plonus
quelle
9
Ich sympathisiere hier mit dem OP, denn obwohl Sie diese beiden Vorteile anführen, haben Sie in beiden Fällen das Wort "können" verwendet. Das heißt, es gibt keine Garantie dafür, dass diese Überprüfungen tatsächlich stattfinden. Dieser Verhaltensunterschied kann nun für leistungsabhängige Tests nützlich sein, die Sie nicht im Produktionsmodus ausführen möchten, für den wir uns entschieden haben assert. Ich finde @Nullableund @Nonnullnützliche Ideen zu sein, aber ich würde mehr Kraft hinter sie mögen, anstatt uns über hypothesizing , was man konnte mit ihnen zu tun, die noch Blätter die Möglichkeit , nichts zu tun mit ihnen öffnen.
seh
2
Die Frage ist, wo ich anfangen soll. Im Moment sind seine Antennen optional. Manchmal würde es mir gefallen, wenn sie es nicht wären, weil es unter bestimmten Umständen hilfreich wäre, sie durchzusetzen ...
Uwe Plonus
Darf ich fragen, was AOP ist, auf das Sie sich hier beziehen?
Chris.Zou
@ Chris.Zou AOP bedeutet aspektorientierte Programmierung, zB AspectJ
Uwe Plonus
13

Ich denke, diese ursprüngliche Frage weist indirekt auf eine allgemeine Empfehlung hin, dass eine Laufzeit-Nullzeigerprüfung weiterhin erforderlich ist, obwohl @NonNull verwendet wird. Siehe folgenden Link:

Die neuen Typanmerkungen von Java 8

Im obigen Blog wird empfohlen, dass:

Optionale Typanmerkungen sind kein Ersatz für die Laufzeitvalidierung. Vor Typanmerkungen befand sich der primäre Speicherort für die Beschreibung von Dingen wie Nullbarkeit oder Bereichen im Javadoc. Bei Typanmerkungen wird diese Kommunikation auf eine Weise zur Überprüfung der Kompilierungszeit in den Bytecode eingegeben. Ihr Code sollte weiterhin eine Laufzeitüberprüfung durchführen.

Jonathanzh
quelle
1
Verstanden, aber die Standard-Flusenprüfungen warnen davor, dass Laufzeit-Nullprüfungen nicht erforderlich sind, was diese Empfehlung auf den ersten Blick zu entmutigen scheint.
Swooby
1
@swooby Normalerweise ignoriere ich Flusenwarnungen, wenn ich sicher bin, dass mein Code korrekt ist. Diese Warnungen sind keine Fehler.
Jonathanzh
12

Beim Kompilieren des Originalbeispiels in Eclipse mit Konformität 1.8 und aktivierter annotationsbasierter Nullanalyse wird folgende Warnung angezeigt:

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

Diese Warnung ist analog zu den Warnungen formuliert, die Sie erhalten, wenn Sie generierten Code mit Legacy-Code unter Verwendung von Rohtypen mischen ("ungeprüfte Konvertierung"). Wir haben hier genau die gleiche Situation: MethodeindirectPathToA() hat eine "Legacy" -Signatur, da sie keinen Nullvertrag angibt. Tools können dies leicht melden, sodass sie Sie durch alle Gassen jagen, in denen Nullanmerkungen weitergegeben werden müssen, aber noch nicht.

Und wenn man einen cleveren benutzt @NonNullByDefault wir wir das nicht jedes Mal sagen.

Mit anderen Worten: Ob sich Nullanmerkungen "sehr weit ausbreiten" oder nicht, hängt möglicherweise von dem von Ihnen verwendeten Tool ab und davon, wie streng Sie alle vom Tool ausgegebenen Warnungen beachten. Mit TYPE_USE-Nullanmerkungen haben Sie endlich die Möglichkeit, sich vom Tool vor jeder möglichen NPE in Ihrem Programm warnen zu lassen , da die Nullheit zu einer intrisischen Eigenschaft des Typsystems geworden ist.

Stephan Herrmann
quelle
8

Ich bin damit einverstanden, dass sich die Anmerkungen "nicht sehr weit verbreiten". Ich sehe jedoch den Fehler auf der Seite des Programmierers.

Ich verstehe die NonnullAnmerkung als Dokumentation. Die folgende Methode drückt aus, dass (als Voraussetzung) ein Nicht-Null-Argument erforderlich ist x.

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

Das folgende Codefragment enthält dann einen Fehler. Die Methode ruft auf, directPathToA()ohne zu erzwingen, dass sie ynicht null ist (das heißt, sie garantiert nicht die Vorbedingung der aufgerufenen Methode). Eine Möglichkeit besteht darin, auch eine NonnullAnmerkung hinzuzufügen indirectPathToA()(Weitergabe der Vorbedingung). Möglichkeit zwei besteht darin, die Nichtigkeit von yin zu überprüfen indirectPathToA()und den Aufruf von directPathToA()when zu vermeiden, wenn ynull ist.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }
Andres
quelle
1
Die Weitergabe des @Nonnull zu haben indirectPathToA(@Nonnull Integer y)ist meiner Meinung nach eine schlechte Praxis: Sie müssen die Weitergabe auf dem vollständigen Aufrufstapel beibehalten (wenn Sie also einen nullCheck-in hinzufügen directPathToA(), müssen Sie im vollständigen Aufrufstapel @Nonnulldurch ersetzen @Nullable). Dies wäre ein großer Wartungsaufwand für große Anwendungen.
Julien Kronegg
Die Annotation @Nonnull betont lediglich, dass die Nullüberprüfung des Arguments auf Ihrer Seite liegt (Sie müssen sicherstellen, dass Sie einen Wert ungleich Null übergeben). Es liegt nicht in der Verantwortung der Methode.
Alexander Drobyshevsky
@Nonnull ist auch sinnvoll, wenn Nullwert für diese Methode keinen Sinn
ergibt
5

In meinen Projekten aktiviere ich die folgende Option in der Codeüberprüfung "Konstante Bedingungen und Ausnahmen":
Schlagen Sie eine Annullation @Nullable für Methoden vor, die möglicherweise null zurückgeben und nullfähige Werte melden, die an nicht annotierte Parameter übergeben werden Inspektionen

Wenn diese Option aktiviert ist, werden alle nicht mit Anmerkungen versehenen Parameter als nicht null behandelt. Daher wird bei Ihrem indirekten Aufruf auch eine Warnung angezeigt:

clazz.indirectPathToA(null); 

Für noch stärkere Überprüfungen ist das Checker Framework möglicherweise eine gute Wahl (siehe dieses nette Tutorial .
Hinweis : Ich habe das noch nicht verwendet und es kann Probleme mit dem Jack-Compiler geben: siehe diesen Fehlerbericht

TmTron
quelle
4

In Java würde ich den optionalen Typ von Guava verwenden . Als tatsächlicher Typ erhalten Sie Compiler-Garantien für seine Verwendung. Es ist einfach, es zu umgehen und ein zu erhalten NullPointerException, aber zumindest die Signatur der Methode kommuniziert klar, was sie als Argument erwartet oder was sie zurückgeben könnte.

Ionuț G. Stan
quelle
16
Damit muss man vorsichtig sein. Optional sollte nur verwendet werden, wenn ein Wert wirklich optional ist und dessen Fehlen als Entscheidungsgatter für die weitere Logik verwendet wird. Ich habe gesehen, dass dies missbraucht wurde, indem Objekte durch Optionale ersetzt und Nullprüfungen durch Anwesenheitsprüfungen ersetzt wurden, die den Punkt verfehlen.
Christopher Perry
Wenn Sie auf JDK 8 oder höher abzielen, bevorzugen Sie die Verwendung java.util.Optionalanstelle der Guava-Klasse. Einzelheiten zu den Unterschieden finden Sie in den Notizen / im Vergleich von Guava .
AndrewF
1
"Nullprüfungen ersetzt durch Anwesenheitsprüfungen, die den Punkt verfehlen" Können Sie dann näher erläutern, worum es geht ? Dies ist meiner Meinung nach nicht der einzige Grund für Optionals, aber sicherlich der mit Abstand größte und beste.
Scubbo
4

Wenn Sie Kotlin verwenden, werden diese Annotationen zur Nullfähigkeit in seinem Compiler unterstützt und Sie können keine Null an eine Java-Methode übergeben, für die ein Argument ungleich Null erforderlich ist. Obwohl diese Frage ursprünglich auf Java ausgerichtet war, erwähne ich diese Kotlin-Funktion, da sie speziell auf diese Java-Annotation ausgerichtet ist und die Frage lautete: "Gibt es eine Möglichkeit, diese Annotationen strenger durchzusetzen und / oder weiter zu verbreiten?" Durch diese Funktion werden diese Anmerkungen strenger erzwungen .

Java-Klasse mit @NotNullAnnotation

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

Kotlin-Klasse, die die Java-Klasse aufruft und null für das mit @NotNull annotierte Argument übergibt

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

Kotlin-Compilerfehler beim Erzwingen der @NotNullAnmerkung.

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

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

Mike Rylander
quelle
3
Die Frage richtet sich an Java gemäß dem ersten Tag und nicht an Kotlin.
seh
1
@seh siehe Update, warum diese Antwort für diese Frage relevant ist.
Mike Rylander
2
Meinetwegen. Das ist eine schöne Eigenschaft von Kotlin. Ich glaube einfach nicht, dass es diejenigen befriedigen wird, die hierher kommen, um etwas über Java zu lernen.
seh
Beim Zugriff myString.hashCode()wird jedoch weiterhin NPE ausgelöst, auch wenn dies @NotNullnicht im Parameter hinzugefügt wird. Was ist also spezifischer beim Hinzufügen?
kAmol
@kAmol Der Unterschied besteht darin, dass bei Verwendung von Kotlin anstelle eines Laufzeitfehlers ein Fehler bei der Kompilierung angezeigt wird . Die Annotation soll Sie darüber informieren, dass Sie als Entwickler sicherstellen müssen, dass keine Null übergeben wird. Dies verhindert nicht, dass zur Laufzeit eine Null übergeben wird, verhindert jedoch, dass Sie Code schreiben, der diese Methode mit einer Null aufruft (oder mit einer Funktion, die null zurückgeben kann).
Mike Rylander
-4

Seit der neuen Java 8-Funktion Optional sollten Sie @Nullable oder @Notnull nicht mehr in Ihrem eigenen Code verwenden . Nehmen Sie das folgende Beispiel:

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

Es könnte umgeschrieben werden mit:

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

Wenn Sie eine Option verwenden, müssen Sie nach Nullwerten suchen . Im obigen Code können Sie nur durch Aufrufen von auf den Wert zugreifenget Methode .

Ein weiterer Vorteil ist, dass der Code besser lesbar wird . Mit Java 9 ifPresentOrElse könnte die Funktion sogar wie folgt geschrieben werden:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}
Mathieu Gemard
quelle
Trotzdem Optionalgibt es immer noch viele Bibliotheken und Frameworks, die diese Anmerkungen verwenden, sodass es nicht möglich ist, alle Ihre Abhängigkeiten durch Versionen zu aktualisieren / zu ersetzen, die für die Verwendung von Optionals aktualisiert wurden. Optionalkann jedoch in Situationen helfen, in denen Sie null in Ihrem eigenen Code verwenden.
Mike Rylander