In meiner Anfangszeit als Javaentwickler habe ich mir mehrmals die Frage gestellt, was der Wert null eigentlich soll und was sich die Architekten dieser Sprache mal gedacht haben, um es einzuführen. Vor allem natürlich dann, wenn meine mühevoll programmierte Anwendung mal wieder mit einer NullPointerException abstürzte…
Nun, die Architekten haben sich damals etwas gedacht. Ich weiß sogar jetzt, mit meiner mehrjährigen Erfahrung als Javaprogrammierer, null zu schätzen.
Häufige Verwendung der Null ist dann, wenn man feststellen möchte, ob ein Element zu einer angedachten Funktionalität passt. Nehmen wir beispielsweise die folgende Funktion. Sie ist sehr einfach und liefert lediglich den Integerwert eines übergebenen Strings:

Dieser Code funktioniert wunderbar solange, solange value nicht null ist (Na ja, es kracht natürlich auch, wenn wir einen nichtnumerischen Wert übergeben, aber ignorieren wir dieses hier mal einfachheitshalber).
Also ändern wir die Funktion zu:

Etwas besser. Wir bekommen jetzt eine Null geliefert, wenn tatsächlich null übergeben wird. Setzt aber natürlich voraus, dass der Programmierer, welche diese Methode benutzt, auch tatsächlich auf null programmiert! Verwendet er beispielsweise einen solchen Konstrukt:

dann wird er uns für ewig verdammen, wenn sein Code kracht.
Wieso dann Null, die eine NullPointerException wirft und nicht zum Beispiel -1? Man könnte das wunderbar auffangen und hätte keine NullPointerException:

Ist das eine gute Lösung? Immerhin funktioniert sie wie vom Entwickler gedacht: wird ein valider Wert übergeben, wird ein passender Integer geliefert. Sonst eben -1. Keine Null, keine NullPointerException.
Eine solche Lösung mag zwar für den Entwickler funktionieren, auf diese Weise entstehen aber unsaubere und fürchterliche APIs: ohne einen notwendigen Grund wird hier eine Einschränkung der übergebenen Werte auf positive Zahlen vorgenommen, die dazu noch aus dem Namen der Funktion überhaupt nicht ersichtlich ist! Sieht ein anderer Entwickler eine solche API, fühlt er sich durchaus auch geneigt, negative Werte zu übergeben, wenn es ihm passt, und schon entstehen falsche Ergebnisse!
Null ist aber ein weitaus haarigeres Thema, als das oben ersichtliche Problem. Schauen wir uns den folgenden Code an:

Solche Konstrukte werden gern in Programmen eingesetzt, da sie unnötigen Code vermeiden und keine Zwischenvariablen brauchen. Was aber, wenn solche Funktion eine NullPointerException wirft?
Jetzt wird es problematisch: aus der einfachen Exception heraus ist es überhaupt nicht erkennbar, was Null ist! Es kann die Variable myServer sein. Es kann sein, dass getConnection() eine Null geliefert hatte. Oder dass in der Connection die ConnectionID nicht gesetzt ist, die getConnectionID().toString() daher den Fehler wirft.
Solche Probleme kenne ich häufig aus Kundenprojekten. Sie sind wirklich doof: das Suchen nach der tatsächlich fehlender Variable ähnelt hier der Suche nach einer Nadel im Strohaufen.
Was macht man in der Regel in einem solchen Fall? Schnell entstehen solche Konstrukte:

Klar, man weiß damit, welche Variable tatsächlich null ist und zu der Exception geführt hatte. Aber viel Spaß dabei, solchen Code zu lesen und zu warten…
Wie macht man es besser?
Erstaunlicherweise ist es kaum bekannt, aber Java bietet hier ein paar sehr schöne und hilfreiche Mittel, die direkt in der Klasse Object enthalten sind. Schauen wir uns die folgende Methode an:

Sie kann in drei Fällen problematisch sein: startDate kann null sein, endDate kann null sein, oder beide können null sein. Wie erfahren wir also, welcher Wert null ist?
Java bietet hierzu die Methode requireNonNull aus der Basisklasse Objects. Diese ist generisch und prüft eine Variable auf ihren Nullwert. Falls diese tatsächlich null ist, wird eine NullPointerException geworfen mit dem entsprechenden Text, welches wir loggen können und damit besseren Hinweis auf die Fehlerursache bekommen:

Schon wissen wir, ob startDate oder endDate null sind. Wir bekommen zwar weiterhin eine NullPointerException geworfen, aber mit einem qualifizierten Text.
Ganz cool ist diese Lösung, wenn wir sie bereits im Constructor einer Klasse einsetzen:

Warum? Ja, der Constructor kann natürlich jetzt eine NullPointerException werfen, aber dafür können wir uns überall im Code darauf verlassen, dass diese Variablen nicht mehr null sind! Sie wurden ja bei der Initialisierung geprüft und sind final, können also nicht mehr durch andere ersetzt werden.