Null-eingeschränkte und nullable Typen
(bugs.openjdk.org)- Java bereitet ein Preview-Sprachfeature vor, das Typen mit Nullness-Markern versieht:
Foo!lehntnullab,Foo?erlaubt es bewusst - Ein nicht markiertes
Foobleibt in seiner Nullness unspecified und damit mit bestehendem Code kompatibel; Konvertierungen zwischen Typen mit unterschiedlicher Nullness können Warnungen oder Laufzeitprüfungen nach sich ziehen - Felder und Arrays, die
nullablehnen, können den Standardwertnullnicht verwenden: Instanzfelder müssen vor dem Aufruf vonsuper(...)definitiv zugewiesen werden, Arrays benötigen einen Initialwert für ihre Komponenten - Wird
nullaufFoo!verengt, entsteht eineNullPointerException; bei Speicherpfaden für Arrays und Felder verhindern ArrayStoreException und FieldStoreException eine Verunreinigung - Optimierungen wie Valhallas Flattening von Value Classes sind die Grundlage dafür, dass der Ausschluss von
nullverlässlich angenommen werden kann; eine Anwendung auf die Standardbibliothek oder eine automatische Neuinterpretation bestehenden Codes ist derzeit nicht Ziel
Preview-Feature zur expliziten Nullness in Java-Typen
- Java-Typen erhalten Nullness-Marker, um auszudrücken, ob die Wertemenge eines Typs
nullenthält Foo!ist ein null-restricted Typ, dernullaus der Wertemenge ausschließtFoo?ist ein nullable Typ, dernullbewusst in die Wertemenge aufnimmt- Ein nicht markiertes
Foohat unspecified Nullness;nullkann auftreten, aber es ist nicht erkennbar, ob dies beabsichtigt ist - Dieses Feature ist ein Preview-Feature, das über die Compiler- und Laufzeit-Flags
--enable-previewaktiviert wird
Ziele und Nicht-Ziele
- Ausdrücken, ob Java-Referenztypen
nullerwarten, sowie Warnungen und Prüfungen für Konvertierungen zwischen Typen mit unterschiedlicher Nullness bereitstellen - Mit bestehendem Java-Code kompatibel sein und schrittweise eingeführt werden können, ohne Source- oder Binärinkompatibilitäten zu erzeugen
- Variablen eines Typs, der
nullablehnt, müssen initialisiert sein, bevor sie erstmals gelesen werden; auch in separat kompilierten Klassen wird die Ablehnung vonnullzur Laufzeit erzwungen - Metadaten und Integritätsgarantien bereitstellen, damit Laufzeitoptimierungen wie das Flattening von Valhallas value class null-eingeschränkten Typen vertrauen können
- Derzeit nicht im Zielumfang sind:
- Bestehenden Code automatisch neu zu interpretieren
- Jedes mögliche
nullzu einem Compilerfehler zu machen - Nullable Formen für primitive Typen wie
inthinzuzufügen - In der aktuellen Phase die Spracherweiterung auf die Standardbibliothek anzuwenden
Warum das nötig ist
- Eine Java-Variable vom Typ
Stringkann eine Referenz auf einString-Objekt odernullenthalten, aber die Sprache bietet keine Möglichkeit auszudrücken, was davon beabsichtigt ist - Viele Programme gehen davon aus, dass
nullnicht vorhanden ist; dies in Javadoc-Spezifikation und Implementierungscode konsistent zu erzwingen, erfordert jedoch zusätzliche Arbeit - Wird diese Erwartung verletzt, kann ein
null-Wert durch Implementierungscode fließen und erst an einer weit vom ursprünglichen Bug entfernten Stelle eine Exception auslösen - Wenn Entwickler als Teil eines Typs ausdrücken, dass
nullabgelehnt oder erlaubt ist, helfen Compile-Time-Feedback und Laufzeitprüfungen dabei, unerwartetenull-Werte früher zu finden - In Valhalla können Variablen von Value-Class-Typen zu einer geflatteten Darstellung des Werts optimiert werden; wenn jedoch zusätzliche Bits für die Kodierung von
nullnötig sind, kann der Speicherverbrauch steigen oder eine Speicheroptimierung unmöglich werden - In Amber kann die Nullness von Pattern-Matching-Kandidaten die Exhaustiveness-Beurteilung eines
switchbeeinflussen, und die Nullness von Typ-Patterns kann beeinflussen, obnullgematcht wird
Syntax der Nullness-Marker und Typstruktur
- Nullness wird als inhärenter Teil eines Typs behandelt;
Foo?undFoohaben unterschiedliche Nullness und sind daher verschiedene Typen - Sowohl Array-Typen als auch Array-Komponententypen können Nullness-Marker haben
Foo?[]!ist ein Typ, bei dem das Array selbst null-restricted ist und die Komponenten nullableFoosind- Bei mehrdimensionalen Arrays kann nach jedem Klammerpaar ein Marker stehen; konventionell wird von links nach rechts, von außen nach innen interpretiert
- Parametrisierte Typen und Typ-Argumente können ebenfalls Nullness-Marker haben
Predicate!<Foo?>ist ein null-restrictedPredicate, dessen Typ-Argument ein nullableFooist
- Um einen null-restricted oder nullable Typ auszudrücken, muss
!oder?im Quelltext explizit erscheinen - Künftig könnte geprüft werden, alle Typen einer Klasse oder Kompilierungseinheit standardmäßig als null-restricted zu interpretieren und nur
?als Ausnahme zu verwenden; die Details sind jedoch separate Arbeit
Initialisierungsregeln für Felder und Arrays
- In bestehendem Java ist der Standardwert von Referenztyp-Feldern und Array-Komponenten
null; für null-restricted Felder oder Array-Komponenten ist das jedoch kein geeigneter Initialwert - Null-restricted Felder und Arrays müssen vom Programm immer initialisiert werden, bevor sie gelesen werden
- Hat ein null-restricted Instanzfeld keinen Initializer, muss es vor dem expliziten oder impliziten Aufruf von
super(...)in jedem Konstruktor definitiv zugewiesen sein- Flexible Constructor Bodies JEP erlaubt es, den notwendigen Initialisierungscode am Anfang eines Konstruktors zu schreiben
- In diesem early construction context sind Aktionen nicht erlaubt, die
thisreferenzieren oder riskieren, nicht initialisierte Felder zu lesen
- Hat ein null-restricted Instanzfeld einen Initializer, wird dieser am Anfang jedes Konstruktors vor dem Aufruf von
super(...)ausgeführt- Ein Konstruktor, der
this(...)aufruft, ist wie nach den bestehenden Regeln ein Sonderfall und führt Initializer nicht aus
- Ein Konstruktor, der
- Null-restricted statische Felder müssen bis zum Ende aller statischen Initializer und Initialisierungsblöcke der Klasse definitiv zugewiesen sein
- Versucht eine andere Klasse, dieses Feld während der Klasseninitialisierung zu lesen, erkennt eine Laufzeitprüfung das zu frühe Lesen und wirft eine Exception
- Arrays mit einem null-restricted Komponententyp müssen im Array-Erzeugungsausdruck für jede Komponente einen Initialwert bereitstellen
- Mit einem Array-Initializer können alle Werte aufgelistet werden
- Auch eine neue Kurzsyntax ist möglich, deren Syntax aber noch TBD ist
Nullness von Ausdrücken und Nullness-Konvertierungen
- Der Java-Compiler bestimmt während der Typprüfung die Nullness jedes Ausdrucks
- Die Nullness einer Variablenreferenz stammt aus der Variablendeklaration, die Nullness eines Methodenaufrufs aus dem Rückgabetyp der referenzierten Methode
- Das Literal
nullist nullable - Die meisten anderen Ausdrücke von Referenztypen sind null-restricted
- Dazu gehören Literale, String-Verkettung,
this, Erzeugung von Klasseninstanzen, Array-Erzeugung, Methodenreferenzen und Lambda-Ausdrücke
- Dazu gehören Literale, String-Verkettung,
- Nullness-Konvertierungen sind in Zuweisungs-, Aufruf- und Cast-Kontexten erlaubt
- Widening-Nullness-Konvertierungen umfassen:
Foo!→Foo?Foo!→ unspecifiedFooFoo?→ unspecifiedFoo- unspecified
Foo→Foo?
- Narrowing-Nullness-Konvertierungen umfassen:
Foo?→Foo!- unspecified
Foo→Foo!
- Narrowing-Nullness-Konvertierungen werden vom Compiler ähnlich wie Unboxing-Konvertierungen automatisch durchgeführt, führen zur Laufzeit aber eine dynamische Prüfung aus, die eine
NullPointerExceptionauslösen kann - Der Versuch, das Literal
nulldirekt in einen null-restricted Typ zu konvertieren, ist ein Compile-Time-Fehler
Laufzeitprüfungen und Exceptions
- Wenn zur Laufzeit ein
null-Wert per Narrowing-Nullness-Konvertierung in einen null-restricted Typ konvertiert wird, entsteht eine NullPointerException - Narrowing-Nullness-Konvertierungen, die im Quellcode nicht explizit sichtbar sind, können ebenfalls während der Laufzeitausführung auftreten
- Ein Array, das mit einem null-restricted Komponententyp angelegt wurde, lehnt
null-Werte bei der üblichen Array-Store-Prüfung ab, selbst wenn es im Quellcode als weniger spezifischer Typ behandelt wird- Dieser Konvertierungsfehler verursacht eine ArrayStoreException
- Wenn ein Feld, das zum Kompilierzeitpunkt nicht null-restricted war, später durch separate Kompilierung null-restricted wird, lehnt ein neuer Field-Store-Check das Speichern von
nullab- Dieser Konvertierungsfehler verursacht eine FieldStoreException
- Bei Methodenaufrufen in Override-Beziehungen kann auf die Konvertierung in den Aufruftyp des Parameters der Obermethode eine Konvertierung in den Parametertyp der überschreibenden Methode folgen
- Auch Rückgabewerte von Methoden können zunächst in den deklarierten Rückgabetyp der Methode und danach in den am Aufrufpunkt erwarteten Rückgabetyp konvertiert werden
Generics, Typ-Argumente und Overriding
- Auch Verwendungen von Typvariablen können Nullness-Marker tragen;
T!ist ein null-restricted Typ undT?ein nullable Typ - Null-restricted und nullable Typvariablen-Typen behaupten innerhalb generischen Codes eine bestimmte Nullness
- Auch als Typ-Argumente verwendete Typen können Nullness ausdrücken; Nullness-Marker an Typvariablen-Typen überschreiben die im Typ-Argument behauptete Nullness
- Innerhalb der erased Implementierung einer generischen API können Null-Einschränkungen nicht erzwungen werden
- Allerdings erzwingen die üblichen impliziten Casts an Grenzen generischer APIs zur Laufzeit null-restricted Typ-Argumente
- Für Interoperabilität wird Nullness innerhalb von Typ-Argumenten nicht strikt erzwungen
Predicate<String!>kann inPredicate<String>oderPredicate<String?>konvertiert werden- Eine solche unchecked nullness conversion kann Warnungen auslösen
- Auch Änderungen der Nullness von Array-Komponententypen sind als unchecked nullness conversion erlaubt; unter welchen Bedingungen Laufzeitprüfungen erfolgen, ist TBD
- Bei der Beurteilung der Gleichheit von Methodensignaturen wird Nullness ignoriert
- Eine Methode kann eine andere überschreiben, auch wenn die Nullness von Parametern und Rückgabetypen nicht übereinstimmt
- Wenn verschiedene APIs unabhängig voneinander Nullness-Marker einführen, können solche Abweichungen häufig auftreten
- Nullness beeinflusst nicht die Anwendbarkeit von Methoden und kann keine fehlgeschlagene Typ-Argument-Inferenz verursachen, kann aber die für den Rückgabetyp generischer Methoden inferierte Nullness beeinflussen
- Details des Inferenzalgorithmus sind TBD
Compilerwarnungen und Fehler
- Einen Typ null-restricted zu machen, kann neue Compile-Time-Fehler erzeugen
- Wenn Felder oder Arrays dieses Typs nicht initialisiert sind
- Wenn versucht wird, das Literal
nullin diesen Typ zu konvertieren - Auch der Vergleich des Literals
nullmit einem Ausdruck eines null-restricted Typs kann ein Compile-Time-Fehler werden
- In anderen Situationen ist die Nullness-Analyse unterstützend und erzeugt keine Compile-Time-Fehler
javacliefert Warnungen, um Laufzeitfehler zu vermeiden; IDEs und andere Analysewerkzeuge werden in dieselbe Richtung empfohlen- Mögliche Warnungsursachen sind:
- Narrowing-Nullness-Konvertierungen, einschließlich solcher aus
?-Typen - Die Verwendung von Ausdrücken eines
?-Typs für Member-Zugriffe oder andere null-feindliche Operationen - Wenn die Nullness von Typ-Argumenten nicht konsistent mit Bounds ist
- Wenn die Nullness von Methodenparametern oder Rückgaben nicht zur überschriebenen Methode passt
- Unchecked Conversions, die die Nullness eines Typs ändern
- Narrowing-Nullness-Konvertierungen, einschließlich solcher aus
Class-Dateien, Reflection und begleitende Änderungen
- Die meisten Verwendungen von Null-Markern werden in
class-Dateien gelöscht; begleitende Laufzeitkonvertierungen werden direkt im Bytecode ausgedrückt - Die Syntax des
Signature-Attributs wird aktualisiert, um!und?innerhalb von Typen zu erlauben - Nullness wird nicht in Deskriptoren von Methoden und Feldern kodiert
- Um Feldverunreinigung zu verhindern, markiert ein neues
NullRestricted-Attribut, dass ein Feld keinenull-Werte erlaubt- Dieses Feld muss auch mit
ACC_STRICTmarkiert sein und streng initialisiert werden - Der Verifier prüft, ob alle streng initialisierten Instanzfelder zugewiesen sind, wenn ein Konstruktor
super(...)aufruft - Jeder Schreibversuch auf das Feld prüft auf
null-Werte und wirft bei Fund eineFieldStoreException
- Dieses Feld muss auch mit
- Die Erzeugung null-restricted Arrays wird nicht durch die Anweisung
anewarrayunterstützt, sondern muss über einen Aufruf der Reflection-API erfolgen - Es gibt keine Literale
Foo!.classoderFoo?.classund auch keine entsprechendenjava.lang.Class-Instanzen - Eine neue RuntimeType API beschreibt die Typmengen, die zur Laufzeit bei Speicherprüfungen für Arrays und Felder erzwungen werden, einschließlich null-restricted Varianten aller Klassen- und Interface-Typen
- Die
Field-API unterstützt das Abfragen desRuntimeTypeeines Felds; dieser Wert kann vom Ergebnis vongetTypeabweichen - Die
Array-API unterstützt Varianten vonnewInstance, die den Komponententyp alsRuntimeTypeausdrücken- Diese Variante erlaubt auch die Bereitstellung von Initialwerten für Array-Komponenten
- Sie lehnt Versuche ab, ein null-restricted Array ohne Initialwerte zu erzeugen
- Traditionelle Deserialisierung ist nicht mit null-restricted Feldern und Arrays kompatibel; ein separates JEP soll einen Serialisierungsmechanismus bereitstellen, der nicht initialisierte null-restricted Felder und Arrays nicht offenlegt
- Von
javadocerzeugte Dokumentation enthält Nullness-Marker - Die APIs
java.lang.reflect.Typeundjavax.lang.modelkodieren Nullness in der Typdarstellung
Alternativen und Abhängigkeiten
- Mehrere Entwicklerwerkzeuge im Java-Ökosystem haben eigenes Compile-Time-Tracking von
nullimplementiert; da sie die Java-Sprache aber nicht ändern, ist die Syntax hauptsächlich auf Annotationen beschränkt und die beeinflussbaren Verhaltensweisen sind auf Compile-Time-Prüfungen begrenzt - Andere Sprachen verfolgen Nullness im Typsystem; viele Sprachen sind standardmäßig null-restricted und betrachten Zuweisungen an null-restricted Typen ohne explizite Nullprüfung als Fehler
- In Java muss das Feature optional sein und ohne eine einzige groß angelegte Migration schrittweise nutzbar sein
- Laufzeiterzwingung von Nullness kann auch durch explizite Prüfungen oder Aufrufe von
Objects.requireNonNullimplementiert werden- Eine konsistente Anwendung ist jedoch umständlich, erfordert zusätzliche Dokumentation und verschlechtert die Lesbarkeit des Programms
- Es gibt keine Möglichkeit, sie direkt auf Variablenspeicher wie Felder und Arrays anzuwenden
- Voraussetzung ist Flexible Constructor Bodies (Second Preview)
- Konstruktoren können dadurch vor dem Aufruf von
super(...)Anweisungen ausführen und Instanzfeldern Werte zuweisen, was die Initialisierungsanforderung für null-restricted Felder ermöglicht
- Konstruktoren können dadurch vor dem Aufruf von
- Künftige Arbeiten umfassen:
- Null-Restricted Value Class Types (Preview): Optimierung der Kodierung von null-restricted Feldern und Arrays mit Value-Class-Typ
- JEP 402: Enhanced Primitive Boxing (Preview): Nullness-Tracking, wenn die Sprache implizite Boxing-Konvertierungen breiter einsetzt
- Spezialisierung von JVM-Klassen und -Methoden (JEP 218 und Überarbeitung): Konkretisierung und Erzwingung der Nullness einiger Typ-Argumente
- Weitere mögliche künftige Verbesserungen umfassen die Anwendung von Nullness-Markern auf Teile der Standard-API, eine kompakte Darstellung von Nullprüfungen im Bytecode, stärkere Low-Level-Erzwingung null-restricted Methodenparameter sowie Sprachmechanismen, die alle Typen in bestimmten Kontexten implizit als null-restricted behaupten
1 Kommentare
Meinungen auf Hacker News
Interessant ist, wie sich dieser Ansatz von dem unterscheidet, den C# vor einigen Jahren eingeführt hat. Wenn man in C# Null-Zulässigkeit in einem Projekt aktiviert, werden alle Variablen als non-null deklariert, sofern sie nicht explizit als nullable markiert sind; in diesem Vorschlag werden bestehende Variablen dagegen effektiv zu einer von drei Varianten: nullable, explizit nullable oder explizit non-nullable.
Kotlin ist ebenfalls eine JVM-Sprache, behandelt Typen aber wie C# als non-null, wenn nichts explizit angegeben ist; allerdings hat Kotlin keine Last der Abwärtskompatibilität. Mit
lateinit varbietet es zudem einen Ausweg, bei dem ein non-nullable Typ leer bleiben kann, bis er in einer anderen Methode initialisiert wird, und bei Zugriff vor der Initialisierung eine spezielle Exception auslöst.Ich frage mich, warum man drei Wahlmöglichkeiten vorsieht. Könnte man nicht unannotierte Variablen nullable lassen und nur annotierte Variablen explizit non-null machen? Wenn etwas ohne Markierung ohnehin automatisch nullable ist, fällt mir kein Grund ein, warum man nullable überhaupt ausdrücklich deklarieren wollen sollte.
Mir gefällt der C#-Ansatz besser, aber dieser Ansatz hat den Vorteil, dass man ihn in Legacy-Codebasen nutzen kann, ohne alle Probleme rund um Null-Zulässigkeit lösen zu müssen. Umgekehrt macht C# Probleme mit Null-Zulässigkeit sofort sichtbar, während dieser Vorschlag erlaubt, sie wie bisher zu verstecken.
Auch die Stelle, dass „eine Methode eine andere Methode überschreiben kann, selbst wenn die Null-Eigenschaft von Parametern und Rückgabewerten nicht übereinstimmt“, wirkt seltsam. Beim Überschreiben/Implementieren von Callbacks scheint das eine Fußpistole zu sein: Die ursprüngliche Methode gibt non-null als Rückgabe vor, aber die überschreibende Methode liefert null zurück.
Aus Zeitgründen haben wir uns im Moment dafür entschieden, abgesehen von einigen Kernstellen nur die Annotationen einzuschalten, behalten aber JetBrains’
NotNullAttributeundCanBeNullAttributeweiterhin als Marker bei, um bewusst getroffene Entscheidungen sofort zu erkennen. Letzteres ließe sich entfernen, weil nullable eine explizite Markierung hat; Ersteres kollidiert aber namentlich mit der C#-eigenen Funktionalität.In diesem Sinne sind drei Wahlmöglichkeiten ziemlich wünschenswert. Denn bei Hunderttausenden Codezeilen lässt sich eine Migration nicht schnell und einfach durchführen.
In anderen internen Projekten streuen wir
#nullable enablein die Umgebung des Codes, an dem wir gerade arbeiten, und erweitern so schrittweise den Bereich der Null-Zulässigkeit. Außerdem gilt die Bedingung, dass neuer Code in einem nullable-Kontext stehen muss. Auch das ist in Ordnung, um bereits annotierte Teile explizit zu machen, ist aber ein Ansatz, der bei einer deutlich kleineren Codebasis und einem kleineren Team möglich ist.String!.Der Begriff „Plattformtyp“ und das Ausrufezeichen sind verwirrend, aber ansonsten funktioniert der Kotlin-Ansatz ziemlich gut. Da Kotlin von Anfang an Null-Zulässigkeit hatte, können Programmierer Plattformtypen nicht direkt angeben; sie werden aber weiterhin gebraucht, um mit zugrunde liegenden Plattformen wie JVM oder JavaScript kompatibel zu bleiben, bei denen die Null-Zulässigkeit unklar ist.
Bei diesem Ansatz ist der Standardwert weiterhin vernünftig. Standardmäßig sollte immer non-nullable gelten, und wer anders denkt, hat von Tony Hoare nichts gelernt. Gleichzeitig bleibt Abwärtskompatibilität erhalten. Kotlin hatte es vergleichsweise leicht; Java und C# müssen auch die Kompatibilität mit bestehendem Quellcode wahren.
Weder der Java- noch der C#-Ansatz ist ideal. Der C#-Ansatz verändert das Verhalten von Code stark abhängig von einem Compiler-Flag, und der Java-Ansatz macht die schlechteste Option zum Standard.
Trotzdem tendiere ich eher zu C#. Wenn man „wahrscheinlich nullbar“ zur einfachsten Option macht, werden die meisten Programmierer sie standardmäßig wählen. Besonders bei einer unternehmensfreundlichen Sprache wie Java scheint das plausibel. Linter und Compiler-Warnungen werden langfristig helfen, aber es dürfte viele Jahre dauern, bis der Großteil des Java-Codes die Null-Zulässigkeit korrekt annotiert. C#-Nutzer werden kurzfristig mehr Schmerzen haben, erreichen das Ziel einer klaren Null-Zulässigkeit aber wahrscheinlich deutlich schneller.
https://kotlinlang.org/docs/java-interop.html#null-safety-an...
https://www.infoq.com/presentations/Null-References-The-Bill...
T?nullable undT!non-nullable bedeutet und dass es für normalesTin neu kompiliertem Quellcode eine pragma-ähnliche Deklaration gibt, mit der der Standardwert pro Quelldatei, propackage-info-Datei oder global per Compiler-Switch festgelegt wird.In einer späteren LTS-Java-Version könnte man dann den globalen Standardwert ändern. Das würde die Umstellung von Projekten erleichtern, Pragmas könnten automatisch eingefügt/aktualisiert werden, und bei Bedarf ließe sich auch der alte Standardwert beibehalten.
Wenn man außerdem die bestehenden JSR-305-
@Nonnull-Annotationen genutzt hätte, um Vorkommen von non-nullable Typen in Klassendateien darzustellen, hätte man auch bidirektionale Kompatibilität mit älteren JDKs bieten können.!-Symbol schreiben müssen“.Schon ein Compiler-Flag oder ein Modul-Tag würde dafür wohl ausreichen.
Trotzdem gehe ich davon aus, dass es ein Compiler-Flag geben wird, das ohne gesonderte Angabe automatisch non-nullable annimmt.
Sieht gut aus. Endlich gibt es auf Sprachebene eine Möglichkeit, Tausende unnötiger Exceptions und Null-Prüfungen loszuwerden. Allerdings fühlt sich die automatische Konvertierung, die die Nullbarkeit eingrenzt, falsch an.
Bei den Beispielen im Vorschlag
String? id(String! arg) { return arg; },String s = null;,Object! o1 = s; // NPE,Object o2 = id(s); // NPE,Object o3 = (String!) s; // NPEsollten meiner Meinung nach zumindest die ersten beiden Fälle Compile-Fehler sein.Der letzte Fall ist explizit und daher etwas uneindeutig, aber besser wäre es, wenn der Compiler innerhalb von
if (s != null)den effektiven Typ alsString!erkennen undString! ss = s;erlauben würde. Dann gäbe es keine Fehlermöglichkeit.Eigentlich bevorzuge ich im letzten Fall statt des Casts
Objects.requireNonNull(s), weil das expliziter ist. Schön wäre allerdings auch etwas wieObjects.unsafeForceNonNull(s), das explizite Prüfungen umgeht und nur die Fälle ausnimmt, die wegen Optimierung blockiert werden. Mit einer unsafe-Methode könnte manrequireNonNulldirekt implementieren, ohne komplexe statische Analyse hinzuzufügen.Leider wird das nur zur Laufzeit überprüfbar sein.
Zum Beispiel sagt die Standardbibliothek ausdrücklich, dass sie zumindest vorerst nicht auf Null-Typen migrieren wird.
Es scheint unbedingt nötig, auf Paketebene oder zumindest Dateiebene alle Variablen standardmäßig als non-null markieren zu können. Andernfalls wird aus Sicherheitsgründen stark dafür argumentiert werden, fast überall die
T!-Syntax zu verwenden, und das erzeugt nur viel Rauschen.!-Symbol schreiben müssen.“@Nullannotiert werden.Probleme entstehen nur an den Codegrenzen bei der Interaktion mit Bibliotheken. Diese neue Syntax dürfte ähnlich sein.
Der Teil „Derzeit ist es nicht Ziel, die Sprachverbesserung auf die Standardbibliothek anzuwenden“ ist bedauerlich.
Aus Erfahrung mit PHP, das ich notgedrungen nutzen musste: Es ist lästig, Eigenschaften, die man für Daten vorab garantiert hat, bei jeder Interaktion mit einer riesigen Standardbibliothek wieder abzustreifen oder neu aufzubauen.
Java sollte solche Ausdruckskraft ebenfalls aktiver in die Standardbibliothek einbringen und sie zu einem First-Class Citizen machen.
Optionalverwendet, wird bei einem Vorschlag für explizit nullable/non-nullable zur Last. Record-Typen hätten vielleicht standardmäßig non-nullable sein können.Außerdem lässt sich dieses Feature in zwei Schritten leichter als Preview-Feature veröffentlichen, Feedback einsammeln und das Design danach endgültig festlegen. Wenn man alles auf einmal machen will, bleibt fast kein Raum mehr, das Feature in der Praxis iterativ zu verbessern.
Ich fände es gut, wenn dieses Feature in Java käme. Eine explizite Wahlmöglichkeit auf Sprachebene wie
T?hat in Kotlin und TypeScript die Lebensqualität von Entwicklern deutlich erhöht. Für Java gibt es Tools wie NullAway, aber sie sind umständlich.Unterstützung auf Sprachebene halte ich für viel besser als
Optional/Maybe, weil man sich auf die eigentliche Logik konzentrieren kann, statt den Code aufmap-/flatMap-Schienen zu setzen.https://github.com/uber/NullAway
Dann könnte man doch einfach eine andere JVM-Sprache verwenden.
Die Website ist derzeit offline, daher hier ein Archivlink: https://web.archive.org/web/20240802081039/https://bugs.open...
„Es ist nicht das Ziel, vom Programm zu verlangen, dass es alle möglichen
null-Werte explizit behandelt; unbehandeltenull-Werte können zur Compile-Zeit eine Warnung sein, aber kein Fehler“ ist eine schlechte Entscheidung.Java ist größtenteils eine statisch typisierte Sprache – warum also noch ein weiteres dynamisches Verhalten einbauen? Ich hoffe, es gibt eine einfache Möglichkeit, solche Warnungen zu Fehlern hochzustufen.
null-Antworten behandeln. Mit dieser Erweiterung kann erzwungenenull-Behandlung nur auf Operationen angewendet werden, die mit den neuen Typen zusammenhängen, und deshalb geht nichts kaputt.Eine Sprache, deren Typsystem die Behandlung von
null/nilin allen Fällen erzwingt, ist natürlich viel besser. Aber Java ist derzeit keine solche Sprache. Trotzdem wird das eine große Verbesserung sein.Schade, dass diese Lektionen so spät gelernt werden. Standardmäßig non-nullable, standardmäßig unveränderlich, standardmäßig der engste Scope.
Bei neuen Designs entscheidet man sich viel zu oft für unmittelbare Bequemlichkeit, statt „auf den sicheren Pfad zu fallen“. Sichere Defaults erfordern viel sorgfältigeres Design und eine bessere User Experience, aber das Ergebnis ist, dass fast jede Sprache, Plattform und Technologie voller Fußangeln ist. Im Bau- und Elektroingenieurwesen gibt es Vorschriften; in der Software lernen wir ungefähr alle 30 Jahre bei neuen Sprachen und Technologien dieselben Lektionen erneut.
Java ist wie ein Dorf in der Dritten Welt, das lernt, dass man Wasser abkochen muss, um Keime abzutöten.
Ein Großteil meiner Arbeit bei Facebook bestand darin, Hack zu verwenden. Nullability ist ein Kernelement des Hack-Typsystems und beseitigt wirklich viele unnötige Fehler.
Natürlich heißt das nicht, dass man niemals unerwartetes
nullbekommt. Da diese Funktion erst später zur Sprache hinzugefügt wurde, gab es immer noch viele Legacy-mixed-Typen, die die PHP-Wurzeln widerspiegeln und faktisch bedeuten, dass alles möglich ist.Zunächst frage ich mich, was mit nullable Arrays passiert. Das Beispiel
String![]zeigt den Fall, dass das Objektnullsein kann, aber was ist mit dem Array selbst? In Java istString labels[] = null;völlig legal. Muss man dann so etwas wieString![]! labels;deklarieren?In Hack bedeutet
vec $foo, dass wederfoonoch seine Elementenullsind, während?vec $foobedeutet, dass die Elemente non-null sind,fooselbst abernullsein kann. In der Praxis gibt es kaum einen Grund,null-Arrays zu verwenden, also sollte der Default nicht-null sein. Das Problem ist allerdings, dass Java bei allem Legacy-Code von möglichernull-Zulässigkeit ausgeht.Bei den Beispielen
Object! o1 = s,Object o2 = id(s),Object o3 = (String!) sim Vorschlag habe ich das Gefühl, dass Nummer 2 und 3 Compile-Fehler sein sollten.Schließlich gefällt mir Hacks
as-Zwangsoperator besser als Javas Casting. Zum Beispiel wärefoo($b)ein Compile-Fehler,$b as Aein Laufzeitfehler, wenn esnullist, und$a as ?Bwürde casten, wenn es einBist, andernfallsnullzurückgeben.Am Ende stellt sich die Frage, ob man das über das Java SDK legen kann und wie es in Legacy-Code aussehen wird.
https://docs.hhvm.com/hack/types/nullable-types
String![]? labels = null;.Die guten Dinge aus Kotlin scheinen nun alle in Java zu landen.
Trotzdem würde ich lieber weiter mit Kotlin arbeiten, wo ich mich nicht mit Dingen wie Lombok herumschlagen muss. Java Records sind allerdings gut.