# Null Pointer Exception in der "ob Null"-Abfrage?



## j-frost (8. Dez 2010)

Hallo liebe Leute, 

ich bin grade eben ganz frisch ins Forum gejoin't, vor allem um folgende Frage loszuwerden (und natürlich in Zukunft, wenn ich denn kann, auch selbst mal zu helfen): 

Ich soll für einen Kurs an der Uni einen binären Baum realisieren, iterativ. Dazu waren zwei Klassen vorgegeben, die Nodeklasse und BinTree, letztere abstract.*

Jetzt will ich natürlich diverse Male schauen, ob es einen Knoten schon gibt - also etwa in der Art: "Ist der Knoten X ein linkes Kind (... oder ein rechtes)?". Dazu frage ich erstmal folgendes: 

```
if (foo.parent.left != null)
```
Warum wirft das eine NullPointerException? 

LG und danke schonmal im Voraus, 

j-frost


* Ich weiß gar nicht, ob man diese Infos braucht, und ob das ein Anfängerthema ist, aber ich schreibs trotzdem erstmal hier hin.


----------



## eRaaaa (8. Dez 2010)

foo oder parent ist null


----------



## j-frost (8. Dez 2010)

eRaaaa hat gesagt.:


> foo oder parent ist null



Aaa~~hh! Oh mann, das habe ich nicht bedacht. Jedes verflixte Objekt kann ja null sein T_T
Habe das angepasst und frage ergo vorher auch noch ab, ob's parent null ist (foo-Abfrage hatte ich immerhin schon). 

Jetzt mal 'ne strukturelle Frage: Ich weiß nicht wie sehr ich mich auf meine Testklasse verlassen kann. Die liefert im Moment immer true zurück und die JVM meckert auch nicht. Finde ich gut. Die Qualität dieser Aussage steht und fällt aber mit der Qualität meiner Testklasse. 

Wenn jetzt einer möchte und ein bisschen Zeit hat wäre es sehr freundlich, wenn er sich mal das Folgende anschaut. Die Methode soll prüfen, ob der Baum tatsächlich binär ist. Wenn diese Methode logisch korrekt ist, gehe ich davon aus, dass ich gute Arbeit geleistet habe und die Sache stimmt. 


```
protected boolean checkBinTree() {
		Node foo = this.root;
		while (foo.right != null) {
			while (foo.left != null)
				foo = foo.left;

			if (foo.parent != null) {
				foo = foo.parent;
				if ((foo.left.key > foo.key) || (foo.right.key < foo.key))
					return false;
			}

			if (foo.right != null) {
				foo = foo.right;
				if ((foo.left.key > foo.key) || (foo.right.key < foo.key))
					return false;
			} 
			else {
				while (foo.right == null) {
					if (foo.parent != null) {
						foo = foo.parent;
						if ((foo.left.key > foo.key)
								|| (foo.right.key < foo.key))
							return false;
					}
					if (foo.right != null) {
						foo = foo.right;
						if ((foo.left.key > foo.key)
								|| (foo.right.key < foo.key))
							return false;
					}
					if (foo == this.root)
						if (foo.left.key > foo.key)
							return false;
				}
			}
		}
		return true;
	}
```

Und irgendwie will meine inorder-Methode nichts drucken. Woran liegt das denn, man kann doch aus 'nem instanziierten Objekt auf die Konsole drucken? 

LG und vielen Dank im Voraus, 

j-frost


----------



## WIaimy (8. Dez 2010)

hast du denn eine toString-Methode geschrieben? Oder was willst du in deiner inorder-Methode ausgeben?


----------



## j-frost (8. Dez 2010)

WIaimy hat gesagt.:


> hast du denn eine toString-Methode geschrieben? Oder was willst du in deiner inorder-Methode ausgeben?



Naja, da das hier nicht besonders Eingaberobust oder sonstwie langzeitig sinnvoll sein soll, hab ich der MyBinTree-Klasse einfach 'ne print-Methode mitgegeben, die den key eines Knotens nimmt und ihn print'et: 

```
private void print(Node node) {
		System.out.print(node.key+" ");
	}
```

Die inorder benutzt dieses print dann: 


```
public void inorder() {
		Node foo = this.root;

		while (true) {
			while ((foo.right == null) && (foo.left != null)) {
				foo = foo.left;
			}
			print(foo);

			while (foo.right != null) {
				while (foo.left != null)
					foo = foo.left;
				print(foo);

				if (foo.parent != null) {
					foo = foo.parent;
					print(foo);
				}

				if (foo.right != null) {
					foo = foo.right;
				} else {
					while (foo.right == null) {
						if (foo.parent != null) {
							foo = foo.parent;
							print(foo);
						}
						if (foo.right != null)
							foo = foo.right;
						if (foo == this.root)
							return;
					}
				}
			}
			Node tmp = this.root;
			while (tmp.right != null)
				tmp = tmp.right;
			if (tmp == foo)
				break;
		}
	}
```

Das hier ist eine korrigierte Variante, eigentlich gibt es das umfassende while(true) nicht, so wollte ich mal versuchen zu fixen, dass die nicht print'et. Hilft aber irgendwie nicht so ganz


----------



## tfa (8. Dez 2010)

Hast du mal dran gedacht, das rekursiv zu lösen? Das dürfte den Code sehr viel durchschaubarer machen. Gerade bei Bäumen bietet sich das an.


----------



## j-frost (8. Dez 2010)

tfa hat gesagt.:


> Hast du mal dran gedacht, das rekursiv zu lösen? Das dürfte den Code sehr viel durchschaubarer machen. Gerade bei Bäumen bietet sich das an.



Und wie ich daran gedacht habe. Es wäre so viel einfacher! Aber in der Aufgabenstellung steht, dass wir das iterativ machen sollen. (Sorry, hab' ich vorhin gar nicht erwähnt  )


----------



## timbeau (9. Dez 2010)

```
while (foo.left != null)
                    foo = foo.left;
                print(foo);
```

Da fehlt mE eine Klammer. 

Wenn du einen Baum hast der einen linken Zweig als einziges Element hat müsste er drucken. Schon probiert?

edit: Was ich nicht verstehe ist diese Prüfung auf parent != null. Du kommst doch nur über not-null Parents in die Child-Nodes.


----------



## Landei (9. Dez 2010)

tfa hat gesagt.:


> Hast du mal dran gedacht, das rekursiv zu lösen?



Schon richtig, aber die wichtigere Frage ist: Hast du mal daran gedacht, das *ohne null* zu lösen?

Z.B. über einen speziellen leeren Knoten (statisch, damit man ihn wiederverwenden kann), und der seine Methoden so überschrieben hat, dass er einfach immer "das Richtige" tut?


----------



## j-frost (9. Dez 2010)

timbeau hat gesagt.:


> ```
> while (foo.left != null)
> foo = foo.left;
> print(foo);
> ...


Ich glaube nicht, dass die Klammer fehlt, weil ich doch erst nach ganz links gehen will/muss, um das kleinste Element zu selektieren, und erst das darf ich dann drucken. 
Und parent != null muss ich prüfen, damit's keine NullPointerExceptions gibt. Hast du da vielleicht 'nen besseren Vorschlag? Eleganz ist was anderes, auch meiner Meinung nach...


Landei hat gesagt.:


> Schon richtig, aber die wichtigere Frage ist: Hast du mal daran gedacht, das *ohne null* zu lösen?
> 
> Z.B. über einen speziellen leeren Knoten (statisch, damit man ihn wiederverwenden kann), und der seine Methoden so überschrieben hat, dass er einfach immer "das Richtige" tut?



Ich muss zugeben, dass ich nicht darüber nachgedacht habe, das ohne null zu lösen, aber ich schätze mal, dass ich das auch nicht darf, denn die vorgegebene Node-Klasse sieht so aus: 

```
public class Node {

	Node left, right, parent;
	int key; 
	
	public Node(){	
		left = null;
		right = null;
		parent = null;
		key = 0;
	}
	
	public Node(int key){
		left = null;
		right = null;
		parent = null;
		this.key = key;
	}	
		
	boolean isLeaf(){
		return ((left==null) && (right==null));
	}
	
}
```


----------



## Landei (10. Dez 2010)

j-frost hat gesagt.:


> Ich muss zugeben, dass ich nicht darüber nachgedacht habe, das ohne null zu lösen, aber ich schätze mal, dass ich das auch nicht darf, denn die vorgegebene Node-Klasse sieht so aus:
> ...


Stimmt. Schade, dass da deine Leerkräfte nicht etwas progressiver sind. Der "Erfinder" der Null-Referenz Tony Hoare nennt sie jedenfalls seinen "Milliarden-Dollar-Fehler".

Ich selbst würde null nur akzeptieren, wenn es auf Grund einer vorgegebenen Bibliothek etc. verwendet werden muss, oder aus Performance-Gründen. Alle anderen Verwendengen würde ich als Hinweis auf einen Design-Fehler ansehen.


----------



## timbeau (10. Dez 2010)

Die Prüfung der null-Parents ist meines Erachtens nicht notwendig, da du niemals in ein Node kommen kannst ohne durch sein Parent zu laufen. 

Damit kann kein Parent je null sein. Was du prüfen solltest ist ob dein Node root ist. 

Mit der Schleife hast du wahrscheinlich rech, Klammern und auch Kommentare machen Code immer lesbarer.


----------



## Landei (11. Dez 2010)

"Normalerweise" hat man in Bäumen keine Referenz zum parent, und kommt auch für alle üblichen Operationen ohne aus.


----------



## timbeau (11. Dez 2010)

Ich will das nicht direkt wiederlegen aber beim  sortierten Binärbaum z.B. muss ich doch schauen, dass die Kinder links < als root < als rechts sind.


----------



## Landei (11. Dez 2010)

Hmm? Wenn man von obigen Code ausgeht...


```
public class Node {
 
    Node left, right, parent;
    int key; 
    ...
}
```

...kann man doch einfach mit key vergleichen. Das Vergleichen wird ja schon beim Einfügen erledigt, und da geht man von der Wurzel aus, und da braucht man nur die left und right-Links.


----------



## Ark (11. Dez 2010)

@Landei: Es gibt schon Fälle, wo Backpointer (Referenz zum Elternknoten) Sinn ergeben (hier wahrscheinlich nicht). Allerdings verstehe ich bis heute nicht, wo das Problem bei null sein soll.

Ark


----------



## Landei (11. Dez 2010)

null hat mehrere Probleme:
1) Eine Variable jeder Klasse kann null sein, selbst wenn das für die jeweilige Klasse vollkommener Schwachsinn ist. Bei Collections kann z.B. eine "leere" Instanz viel besser ausdrücken, dass da "nichts ist". Für andere Klassen, etwa enums, ist ein "leerer" Wert oft _überhaupt nicht_ sinnvoll, also immer ein Design-Fehler.
2) Eine Nullreferenz ist ein Loch im Typsystem, null hat eigentlich keinen "richtigen" Typ. Man nehme z.B. zwei überladene Methoden, die einen String bzw. ein Date als Argument nehmen. Übergibt man null, muss man dem Compiler erst mit einem Cast "helfen", damit er die richtige Methode erkennt. Man kann auch ein null, das für einen String steht, problemlos in ein null casten, dass für ein Date gebraucht wird. All das sind Symptome dafür, dass mit dem Typ von null etwas nicht stimmt.
3) Da null ein ganz normaler Wert ist, kann es sein, dass man an einem Ende des Systems ein null hereinwirft, die null versehentlich nicht geprüft wird, jede Menge andere Verarbeitungsschritte erfolgen, und dann erst die NPE-Bombe hochgeht, weit weg von der Stelle, wo der _eigentliche_ Fehler (nämlich die Übergabe von null an eine Methode, die nicht dafür gedacht war) passiert ist.
4) Der Wert null ist kein Objekt, ich kann z.B. keine Methoden daran aufrufen. Damit muss jedes Objekt erst einmal getestet werden, ob es wirklich eins ist, oder nur eine Null-Mogelpackung. Das Problem wäre nicht ganz so schlimm, wenn man einer Variable oder einem Argument ansehen kann, ob es auch null werden kann oder nicht, aber das ist in Java, C++ usw. nicht der Fall.
5) Viele Verwendungen von null sind unnötig und oft ein Zeichen für schlechtes Design. Abhilfe können leere Collections, lazy initialization, Vererbung (etwa eine Unterklasse, die ein Feld enthält, und eine, in der es komplett fehlt), das typsichere Builder-Pattern und andere Techniken schaffen. 

Konstrukte wie Option (Haskell: Maybe) erlauben es, Variablen zu kennzeichnen, dass sie "leer" sein können, wie mit einem großen Warnaufkleber, und das typsicher. Damit nicht genug: Es lassen sich darauf sinnvolle Operationen definieren, so dass man den eigentlichen Wert für viele Sachen nicht extra testen oder "auspacken" muss, etwa: "Gib mir den ersten nicht-leeren Wert in dieser Collection von Options zurück" oder "Multipliziere diese beiden Integer-Options, falls möglich". Das funktioniert sehr elegant und abstrakt, weil Option eine Monade (und auch ein applikativer Funktor) ist. Sprachen wie Haskell beweisen, dass man sehr gut ohne null auskommt, wenn das Typsystem ausdrucksstark genug ist (wobei schon Java-Generics ausreichen).


----------



## Ark (11. Dez 2010)

Ich kann zwar jetzt nur für mich sprechen, aber ich mache das grundsätzlich so, dass Collections, Arrays etc. immer vor Gebrauch mittels des Konstruktors initialisiert werden (meist sind sie dann auch final). Außerdem schreibe ich in die Dokumentation explizit, wann wo null erlaubt ist und wie das jeweils interpretiert wird. Auch bei Rückgabewerten schreibe ich, wie null zu interpretieren ist. In allen anderen Fällen zeigen Referenzen auf echte Objekte.

Oder anders gesagt: Sammelbehälter in Objekten/Klassen sind nie null, und nur dann und dort, wenn und wo ich explizit in der Dokumentation von null rede, kann/darf auch null sein (und dann ist das Auftreten von null immer mit einer klar definierten Aussage verbunden).

Bisher bin ich mit diesem Vorgehen auf keinerlei Probleme gestoßen. Auch die von dir angebrachten "NPE-Bomben" sollten so nicht auftreten können, weil für den Fall, dass null rauskommen könnte, dies ausdrücklich und deutlich in der Dokumentation erklärt bzw. begründet wird; und das sollte meiner Meinung nach reichen.

Zu Punkt 1 und Punkt 5 gebe ich dir also Recht (siehe oben). Den Punkten 3 und 4 kann man gut mit einer klaren Dokumentation beikommen (siehe oben). Punkt 2 verstehe ich jedoch nicht ganz, Gegenbeispiel: Stimmt denn mit dem Nullvektor im R² auch etwas nicht, nur weil er keine klare Richtung hat?

Ark


----------



## j-frost (12. Dez 2010)

timbeau hat gesagt.:


> Die Prüfung der null-Parents ist meines Erachtens nicht notwendig, da du niemals in ein Node kommen kannst ohne durch sein Parent zu laufen.
> 
> Damit kann kein Parent je null sein. Was du prüfen solltest ist ob dein Node root ist.
> 
> Mit der Schleife hast du wahrscheinlich rech, Klammern und auch Kommentare machen Code immer lesbarer.



Ja~~, sorry, ich hab' da ein bisschen unsauber gearbeitet. parent == null ist natürlich äquivalent zu node == root, und das wollte ich damit auch überprüfen. 

Leider sehe ich hier nicht so viele Verbesserungsvorschläge, aber das ist schon ok, ich hab's jetzt erstmal abgegeben und am nächsten Donnerstag hab' ich wieder 'ne Übung, da poste ich dann die "Lösung" - oder zumindest, was mein ÜG-Leiter so alles zu meckern hatte. Das interessiert mich jetzt langsam so richtig brennend. 

Wegen der Nebendiskussion über "ist null brauchbar?": Ich bin ja jetzt noch nicht ansatzweise ein erfahrener Programmierer, ich hab' nur in der Schule gern gecode't und dann beschlossen, dass ich Informatik studieren möchte, aber ich möchte da jetzt auch mal meinen Senf dazugeben: 

Soweit ich das bis jetzt verstanden habe, geht es in der Informatik um Präzision oder Pedanz, je nachdem, was mach für einen Blickwinkel darauf hat, und Fantasie, und um deren Kombination. Das hat die Informatik auch noch am ehesten mit der Mathematik gemein, denke ich. Im Prinzip sind Nachlässigkeiten (sei es z.B. beim Autofahren, wenn man die gelbe Ampel überfährt, oder beim Programmieren, wenn man doch mal schnell was code-n-fixt) nichts Schlimmes, sie sollten sich nur nicht allzu sehr häufen. Darin liegt dann das Problem: null ist eingebaut. null kann sich also potenziell unendlich häufen. null ist nachlässig konstruiert. Ergo ist null ein Problem. 

Das heißt aber nicht, dass man null nicht konstant work-around'en kann, wie von Ark vorgeschlagen. Es bedeutet aber zusätzliche Arbeit für den Programmierer. Der muss die Nachlässigkeit an anderer Stelle "wieder aufholen".


----------



## Landei (12. Dez 2010)

@Ark: Kurz gesagt, du hältst dich an die "best practices" für Java, und natürlich funktioniert das. Aber warum sollte man selbst "aufpassen", wo man doch ein statisches Typsystem und einen Compiler hat? 

Das ist ein wenig wie der Wechsel von JUnit 3 zu 4: Vorher hatte man Namenskonventionen ("Test-Methoden beginnen mit 'test'"), die man kennen und einhalten musste. Danach gab es Annotations, womit die Sprache selber ausdrucksstark genug war, um eine Test-Methode als solche zu kennzeichnen. Die Konventionen, denen du folgst, sind gut und richtig, aber letzen Endes ein Symptom dafür, das der Sprache Ausdrucksmöglichkeiten fehlen, oder diese nicht richtig ausgenutzt werden.

Sieh es mal von einem anderen Blickwinkel heraus: Für was kann null alles stehen? Für einen optionalen Wert, für einen (noch) unbekannten Wert, für ein Problem bei der Berechnung, für eine Berechnung, die "nichts" als Ergebnis hat, für einen Wert, der nicht mehr benötigt wird ("ausnullen") und noch ein paar andere Sachen. Wird null all diesen Aufgaben gerecht? Ich denke, eher nicht - insbesondere da es ja noch nicht einmal ein richtiges Objekt ist.

Nebenbei stehe ich mit dieser Meinung nicht ganz allein da. Einige funktionale Sprachen wie Haskell kennen kein null. Scala hat null aus Gründen der Java-Interoperabilität, bietet aber Alternativen. Bei Nice kann man kennzeichnen, ob Variablen null enthalten können (wohl auch in Lava). Sprachen wie Groovy haben zumindest Konstrukte wie die "Elvis-Operatoren" [c]?.[/c] und [c]?:[/c], um sich besser gegen null abzusichern, aber selbst gegen diese kleine Hilfe haben sich Sun und Oracle bisher gesträubt.

Siehe auch: One Div Zero: Martians vs Monads: Null Considered Harmful


----------



## Ark (12. Dez 2010)

Ich muss zugeben, dass ich nicht alles in dem Artikel verstanden habe, weil ich Scala so gut wie gar nicht kenne. Aber so, wie ich es bisher verstanden habe, erinnert es eher an die "checked exceptions" in Java. (Ehrlich: auch bei checked exceptions habe ich noch nicht richtig verstanden, wo da das Problem mit denen sein soll. Vielleicht kann da jemand Licht ins Dunkle bringen.)

Auf jeden Fall klingt es interessant, und für Java könnte ich mir so etwas wie hier beschrieben vorstellen. (Na ja, schauen wir mal, wann vielleicht irgendwas in der Richtung kommt. ) Aber null komplett aus einer Sprache zu verbannen, halte ich für wenig zielführend.

Ark


----------



## Landei (13. Dez 2010)

Ark hat gesagt.:


> Aber null komplett aus einer Sprache zu verbannen, halte ich für wenig zielführend.



Warum nicht? Was genau spricht dagegen?


----------



## SlaterB (13. Dez 2010)

ich erinnere gerne an folgendes Beispiel aus einer älteren Diskussion
http://www.java-forum.org/java-basi...string-equals-null-angenommen.html#post558053

ein anderes wäre 
[c]List<Number> l = new ArrayList<Number>(20);[/c]
womit ist die Liste belegt wenn nicht mit null, mit Option? passt das zum generischen Typ der Liste?

ich wüßte schon gerne wie ganz normale Programme ohne null aussehen, hab noch keine gesehen,
die meisten Scala-Beispiele sind ja sehr durchgestylt geradelinig, 
vielleicht ist aber aber eben die gute Folge der besseren Programmierung

--------

ein spezielles Objekt statt einer leeren Liste ist ein netter Trick, aber eben nur in speziellen Situationen möglich,
hier beim Baum oder ähnlichen Netzen durchaus, aber auch nur bei Teilaufgaben wie der Ausgabe besonders elegant,
wenn man die add-Methode baut muss man doch wieder mit if testen ob das aktuelle Blatt schon ein richtiger Node ist oder nur ein Dummy

in Java gibt es zurecht EMPTY_COLLECTION & Co. aber auch 9999 Klassen ohne ein solches Objekt


----------



## Landei (13. Dez 2010)

SlaterB hat gesagt.:


> ich wüßte schon gerne wie ganz normale Programme ohne null aussehen, hab noch keine gesehen,
> die meisten Scala-Beispiele sind ja sehr durchgestylt geradelinig,
> vielleicht ist aber aber eben die gute Folge der besseren Programmierung



Möglich. Gib doch einfach ein Stück Code voller nulls vor, und ich versuche mal, das zu entflechten (in Java mit Option und dann in Scala)...


----------



## SlaterB (13. Dez 2010)

na zwei Beispiele stehen ja oben, die Liste und die Person, und sag nicht dass die vielen Options darin eine Alternative sind

-----

beim Thema LazyLoading kam ich vorhin an
Lazy Values in Scala  Life With Code
vorbei, erstmals ein Scala-Programm was einigermaßen normale Dinge wie Student, Person usw. enthält,

aber richtig uninitialisiertes gibt es da nicht, 
dass das Bean selber eine Lade-Methode aufruft ist auch bedenklich

was ist dort mit

```
var result = new Array[Course](1)
// X
result(0) = c
```
welchen Wert hat Array-Position 0 an Stelle X?


----------



## Ark (13. Dez 2010)

Landei hat gesagt.:


> Warum nicht? Was genau spricht dagegen?


Na ja, was spricht denn dagegen? null ist insofern typsicher, als dass Folgendes nicht geht:

```
BufferedReader r=null;
System x = r; // nix da!
```
Und dass man bei Parametern tricksen muss, indem man null entsprechend castet, sehe ich auch nicht als Problem (eher das Gegenteil, es beweist vielmehr die Typsicherheit). Bei einem Nullvektor lässt sich ja auch schlecht eine eindeutige Richtung angeben, und trotzdem hat der Nullvektor seine Existenzberechtigung, oder noch besser: er _ist_ die Existenzberechtigung aller Vektoren.

null ist das NaN der Objekte, es ist kein "Pseudo-Objekt", das deine Anwendung in die Luft jagen will. Eine NPE besagt einfach nur, dass die gewünschte Methode, das gewünschte Feld etc. garantiert ein falsches Ergebnis liefert bzw. geliefert hätte: [c]null.IRGENDETWAS[/c] ist falsch für alle Typen von null und alle IRGENDETWAS (auch Methoden).

Stell dir einen binären Baum vor. Die Knoten dieses Baumes werden durch Objekte repräsentiert, wobei jeder Knoten Referenzen auf seine Kinder hat. Frage: wie sieht ein Blatt node aus?

Nehmen wir an, es gäbe kein null. Dann müsste [c]node.leftChild[/c] irgendetwas anderes sein, aber was? Wenn es ein "richtiger" Knoten wäre, müsste auch dieser richtige Kinder haben: rekursive Datenstrukturen wären nicht darstellbar, da sie sofort unendlich viel Speicher benötigen würden. Wie wäre es mit einer Art "Pseudo-Knoten"? Der dürfte dann nicht von der Struktur eines "gewöhnlichen" Knotens sein, denn ein gewöhnlicher hat Kinder, und damit wären wir wieder bei demselben Problem. Also dürfte dieser Pseudo-Knoten kein Kind-Feld haben, aber dann ist er nicht von der Struktur eines Knotens, und damit ist die Typsicherheit hinfällig. Einfaches Ableiten über Vererbung (Knoten extends Blatt) geht nicht, weil die Aussage "ein Knoten ist ein Blatt" Murks ist. Eine abstrakte Superklasse für beide Knotenarten geht auch nicht, weil ein Baum nur noch über Änderung der Knotentypen veränderbar wäre (man müsste aus Blatt-Objekten Knoten-Objekte machen, auch hier würde die Typsicherheit versagen).

Die bisher einzige Lösung (trotz der aufgezeigten Widersprüche) dazu wäre, eine Art Terminator-Objekt anzulegen, auf das dann [c]node.leftChild[/c] verweisen darf, falls node ein Blatt ist. Aber dann lautet die Frage: Was ergibt [c]node.leftChild.isLeaf()[/c]? Ist sie wahr, oder ist sie falsch? Ist die Aussage [c]node.leftChild.getClass() == Node.class[/c] wahr oder falsch?

Meine Antwort: jede dieser Aussagen ist immer Murks, ganz egal, ob wahr oder falsch ausgespuckt werden würde. Und genauso sehe ich auch eine NPE: Stell dir vor, null wäre immer vom Typ deiner Wahl, und ein Knoten node wär ein Blatt. Dann sei [c]node.getLeftChild() == null[/c], was du dir vorstellen kannst als: Das Kind eines Blattes ist von einem Typ, der gar keine Rolle spielt, weil jede Methode, die ich aufrufen würde, mit folgender Implementierung überschrieben wurde:

```
throw new UnsupportedOperationException("Was auch immer hier rausgekommen wäre, es wäre Murks.");
```
Und genau das ist es, was eine NPE darstellen soll. (Denke ich. )

Ark


----------



## timbeau (13. Dez 2010)

Ich hatte gerade ein Problem welches ich zwar jetzt lösen konnte und wahrscheinlich nur auf Unwissenheit über JUnit4 beruhte aber ich hatte eine Klasse welche weitere Attribute hat. Diese werden erst im Laufe des Ablaufs gefüllt und werden zwar deklariert aber noch nicht mittels new initialisiert wobei ich mir nicht sicher bin ob Java da nicht schon von vorneherein eine interne Initialisierung macht. 

Ich kann diese Attribute (alles Listen) mittels setter-Methode setzen. Das ruft keine NPE auf. Aber bevor ich das erste Mal die setter-Methode aufrufe und eine Liste übergebe ist das Attribut noch null und führt auch zu NPE auch wenn ich mir die komplette Liste zurückgeben lasse. 

Könnt ihr euch das soweit vorstellen und wie wäre das ohne null? Objekte die leer sind? Leere Listen?


----------



## SlaterB (13. Dez 2010)

Ark hat gesagt.:


> Die bisher einzige Lösung (trotz der aufgezeigten Widersprüche) dazu wäre, eine Art Terminator-Objekt anzulegen, auf das dann [c]node.leftChild[/c] verweisen darf, falls node ein Blatt ist. Aber dann lautet die Frage: Was ergibt [c]node.leftChild.isLeaf()[/c]? Ist sie wahr, oder ist sie falsch? Ist die Aussage [c]node.leftChild.getClass() == Node.class[/c] wahr oder falsch?
> 
> Meine Antwort: jede dieser Aussagen ist immer Murks, ganz egal, ob wahr oder falsch ausgespuckt werden würde. Und genauso sehe ich auch eine NPE


das erinnert mich an ein weiteres altes Beispiel von mir, 
http://www.java-forum.org/allgemeine-java-themen/87667-java-7-auslese-3.html#post552932


----------



## maki (13. Dez 2010)

> Stell dir einen binären Baum vor. Die Knoten dieses Baumes werden durch Objekte repräsentiert, wobei jeder Knoten Referenzen auf seine Kinder hat. Frage: wie sieht ein Blatt node aus?
> 
> Nehmen wir an, es gäbe kein null. Dann müsste node.leftChild irgendetwas anderes sein, aber was? Wenn es ein "richtiger" Knoten wäre, müsste auch dieser richtige Kinder haben: rekursive Datenstrukturen wären nicht darstellbar, da sie sofort unendlich viel Speicher benötigen würden.


Nicht wirklich, einen sog. Pseudoknoten muss man nicht durch null darstellen, ist der ideale Kandidat für ein "NullObject", dieser Pseudoknoten kann immer wieder verwendet werden und hat eben keine Children, aber nicht als null, sondern als leere Collection, dafür bieten sich zB. die Java Empty Collections an.

Macht 0% Unterschied im Speicherverbrauch, man hat weniger Code und kann nie vergessen gegen null zu prüfen.


----------



## SlaterB (13. Dez 2010)

@maki
was liefert denn bei diesem Blatt node.left.left.left.left.left?
geht es immr weiter, wenn man eine Schleife hat, läuft die ewig?
wenn es anderenfalls eine Exception gibt, ähnlich add() bei Collections.EMPTY_LIST, dann ist gegenüber einer NPE auch nichts gewonnen,

eine solches unerwartet nicht erkennbar unfunktioniales Objekt kann übrigens wie null als 'Zeitbombe' durchs halbe Programm getragen werden und irgendwo anders explodieren, einer der Punkte von Landeis Liste


Landei hat gesagt.:


> 3) Da null ein ganz normaler Wert ist, kann es sein, dass man an einem Ende des Systems ein null hereinwirft, die null versehentlich nicht geprüft wird, jede Menge andere Verarbeitungsschritte erfolgen, und dann erst die NPE-Bombe hochgeht, weit weg von der Stelle, wo der _eigentliche_ Fehler (nämlich die Übergabe von null an eine Methode, die nicht dafür gedacht war) passiert ist.


----------



## maki (13. Dez 2010)

> @maki
> was liefert denn bei diesem Blatt node.left.left.left.left.left?


Einen Pseudoknoten.
Aber wer solchen Code schreibt hat ein ganz anderes Problem 



> geht es immr weiter, wenn man eine Schleife hat, läuft die ewig?


Normalerweise würde man rekursiv durch einen Baum traversieren, oder gff. iterativ, da stellt sich dieses "Problem" doch gar nicht.



> wenn es anderenfalls eine Exception gibt, ähnlich add() bei Collections.EMPTY_LIST, dann ist gegenüber einer NPE auch nichts gewonnen,


Es würde eben keine Exception geworfen, man bekommt einen Pseudoknoten.
Collection.EMPTY_LIST#add könnte natürlich auch eine neue Collection zurückliefern bei entsprechender Implementierung, ist aber gefährlich wenn "normale" Collections  immutable sind, denke ich. Waren Collections nicht immutable in Scala? 



> eine solches unerwartet nicht erkennbar unfunktioniales Objekt kann übrigens wie null als 'Zeitbombe' durchs halbe Programm getragen werden und irgendwo anders explodieren, einer der Punkte von Landeis Liste


Nö, ich denke es geht hier mehr um die Gewohnheit die man hat wenn man lange mit Sprachen gearbeitet hat die ein null Referenz bieten wie Java, C++ etc.
Die Prüfung gegen null ersetzt doch nur eine Abbruchbedingung die anders - ohne null Referenz - aussagekräftiger Formuliert werden müsste, am besten nur einmal und nicht wie bei einem null Vergleich fast immer redundant.


----------



## SlaterB (13. Dez 2010)

mal abgesehen davon dass folgendes konstruiert ist, 
wäre die entstehende Endlosschleife also nur dumm programmiert, kein strukturelles Problem solcher NULL-Objekte?

```
public class Test {
    public static void main(String[] args) {
        Node tree = Node.DUMMY;
        System.out.println(searchMin(tree));
    }
    static Node searchMin(Node node) {
        Node n = node;
        while (n.getLeft().isSmaller(node))  {
            n = n.getLeft();
        }
        return n;
    }
}
abstract class Node {
    public static Node DUMMY = new Node()  {
            @Override
            public Node getLeft() {
                return DUMMY;
            }
            @Override
            public Node getRight() {
                return DUMMY;
            }
            @Override
            public boolean isSmaller(Node other) {
                return true;
            }
        };

    public abstract Node getLeft();
    public abstract Node getRight();
    public abstract boolean isSmaller(Node other);
}
```


----------



## maki (13. Dez 2010)

> while (n.getLeft().isSmaller(node))  {
> n = n.getLeft();
> }


Da fehlt eine Abbruchbedingung, zB. die prüfung gegen die Pseuoknotenreferenz 
Aber ein [c]while(n.hasLeftChild() && n.getLeft().isSmaller(node))[/c] wäre auch nicht verkehrt imho.

Ob man jetzt null komplett aus Java streichen könnte würde ich nicht unterschreiben, dafür ist null zu tief verwurzelt.


----------



## SlaterB (13. Dez 2010)

tja,
[c]while(n.hasLeftChild() && n.getLeft().isSmaller(node))[/c]
entspricht
[c]while(n.getChild() != null && n.getLeft().isSmaller(node))[/c]
nur dass es mit null keine Endlosschleife gibt,

aber bringt eigentlich nix, dass NULL-Objekte oft genug funktionieren helfen, gerade auch hier in diesem Thema bei Tree, ist doch relativ akzeptiert, auch wenn ich hier
http://www.java-forum.org/allgemeine-java-themen/87667-java-7-auslese-3.html#post552932
glaube ich keine endgültige Lösung bekommen habe 

aber was ist wenn es kein NULL-Objekt gibt, wie von all den tausenden Klassen in der API..

und was ist mit einer ArrayList<Node>(20), wer sagt dem Programm, dass es da ein bestimmtes Objekt einfügen soll, was ist stattdessen drin?


----------



## Ark (13. Dez 2010)

Mir fällt gerade noch etwas ein bzw. auf, das ich so noch nicht erwähnt habe:

Von den Vertretern der Anti-null-Fraktion höre ich häufig was in der Richtung "und wenn man da vergisst, auf null zu prüfen, fliegt einem eine NPE um die Ohren". Ehrlich: in meinem Code ist so gut wie nie eine null-Überprüfung drin, und trotzdem "explodiert" da nichts. Okay, ich verwende Überprüfungen auf null, aber nicht, um mich vor Blindgängern zu schützen, sondern weil damit immer klar definierte Aussagen verbunden sind (wie: ein Knoten ist ein Blatt, wenn alle Kinder null sind). Auch gibt es bei mir manchmal NPEs, aber das nur in den allerersten und einfachsten Tests, und der Fehler ist dann praktisch nie die fehlende Überprüfung auf null, sondern eine nicht korrekt durchgeführte Initialisierung.

Wie schafft man das überhaupt, eine Methode null so zurückgeben zu lassen, dass dies als Fehler nicht schon in den ersten Tests eliminiert werden könnte? Nehmen wir mal folgendes Beispiel an:

```
public Irgendetwas methode(){
	Irgendetwas a = ???;
	return null;
	return new Irgendetwas();
	return a;
}
```
Nur eine der drei return-Anweisungen darf da stehen. Wenn es [c]return null[/c] ist, hat der Programmierer dieser Methode ganz bewusst gesagt, dass hier null auftreten kann; so was kann man genauso bewusst auch dokumentieren. Wenn es [c]return new Irgendetwas()[/c] ist, kann es niemals null sei. Spannend wird es also nur bei [c]return a[/c], aber so richtig spannend wird es da eigentlich auch nicht. Wieder gibt es drei Fälle:

```
public Irgendetwas methode(){
	Irgendetwas a;
	Irgendetwas a = new Irgendetwas();
	Irgendetwas a = null;
	return a;
}
```
Ohne die Initialisierung von a lässt sich dieser Code gar nicht kompilieren. Wenn es [c]Irgendetwas a = new Irgendetwas()[/c] heißt, kann a nicht null sein. Wenn es [c]Irgendetwas a = null[/c] heißt, kann mit a niemals irgendetwas Sinnvolles in der Methode gemacht worden sein, also war a so niemals der Wert, der hätte zurückgegeben werden sollen, und die Methode bzw. ihr Rückgabewert/-typ wäre sinnlos (die Methode würde immer nur null zurückgeben, und dann hätte man es auch so schreiben können).

Dieses "Problem" kann nur noch dadurch behoben werden, dass a irgendwann später mal ein Wert zugewiesen wird. Wenn das aber in dieser Methode sowieso immer der Fall wäre, hätte a gar nicht mit null initialisiert werden dürfen (denn dadurch hebelt man den Schutz durch den Compiler vor fehlender Initialisierung aus). Also kann so etwas nur sinnvoll auftreten, wenn a _nur unter bestimmten Bedingungen_ von null auf einen (wahrscheinlich von null verschiedenen) "richtigen" Wert gesetzt wird.

Und gerade diese Bedingung ist die Aussage, die mit null verknüpft werden kann. Da es keine weiteren Fälle gibt, muss jeder auftretende null-Wert mit einer Aussage verknüpft sein. Alles andere sind Fehler.

Deswegen behaupte ich mal, dass in 99% aller Fälle, die die Anti-null-Fraktion anführt, eine Überprüfung auf null nur ein Workaround ist, und der eigentliche Fehler z.B. bei der Methode liegt, die null zurückgegeben hat, oder (noch schlimmer) im Entwurf zu suchen ist.

Ark


----------



## Landei (14. Dez 2010)

@Ark: Es gibt einige Szenarios, in dem null-Werte ungewollt entstehen können: Man fragt einen nicht-existenten Wert aus einer Map ab, irrt sich im catch-Block einer Fehlerbehandlung oder man macht einen Fehler in einer Schleife, etwa

```
Foo foo() {
   Foo result = null;
   boolean found = true;
   while(! found) {
      ...
   } 
   return result;
}
```

Auch einige API-Methoden geben null zurück, wenn man eher eine Exception erwarten würde (z.B. wenn man versucht, in Swing ein Image zu laden).

Und hat man erst mal einen null-Wert, wird darauf oft nichts ausgeführt, sondern er wird z.B. zur Konstruktion eines Objekts verwendet, das dann durch das System wandern kann.

Das Baum-Beispiel braucht nicht unbedingt einen "Dummy-Knoten", man kann auch einfach left() und right() als Option<Node> definieren (für eine "ordentliche" Option-Implementierung siehe Functional Java ). Option implementiert z.B. Iterable, funktioniert also wie eine 0- oder 1-elementige Collection. Eine Funktion toList könnte dann so aussehen:

```
public static <T> List<T> toList(Node<T> root) {
  List<T> list = new ArrayList<T>();
  toList(root, list);
  return list;
}

private static <T> void toList(Node<T> node, List<T> list) {
   for(Node<T> left : node.left()) toList(left, list);
   list.add(node.value());
   for(Node<T> right : node.right()) toList(right, list);
}
```
Würde das mit null klarer oder kürzer?


----------



## Ark (14. Dez 2010)

Landei hat gesagt.:


> Man fragt einen nicht-existenten Wert aus einer Map ab, irrt sich im catch-Block einer Fehlerbehandlung oder man macht einen Fehler in einer Schleife


Deswegen mein "Alles andere sind Fehler": Das mit der Map ist klar und deutlich dokumentiert. Das mit dem catch-Block verstehe ich gerade nicht; müsstest du evtl. noch einmal genauer ausführen. Und in deinem Codebeispiel findet sich der Fehler bereits in der Initialisierung von result und found: Wenn [c]result = null[/c] bedeutet, dass noch nichts gefunden wurde (einfach weil noch nichts durchsucht wurde), kann man doch nicht direkt darunter das Gegenteil behaupten.  Außerdem ist [c]found[/c] redundant (soweit aus der Skizze ersichtlich), weil die Schleife wahrscheinlich mit [c]result != null[/c] (äquivalent zu [c]found[/c]) abbricht. Und dann kann man das auch im Code deutlich machen, indem man die Schleife mit [c]break[/c] beendet, sobald result den richtigen Wert bekommen hat.



Landei hat gesagt.:


> Auch einige API-Methoden geben null zurück, wenn man eher eine Exception erwarten würde (z.B. wenn man versucht, in Swing ein Image zu laden).


Welche Methode meinst du genau? Ich nehme indes stark an, dass auch das so dokumentiert sein sollte.



Landei hat gesagt.:


> Und hat man erst mal einen null-Wert, wird darauf oft nichts ausgeführt, sondern er wird z.B. zur Konstruktion eines Objekts verwendet, das dann durch das System wandern kann.


Ich finde es auch nervig, praktisch jedes Mal im Konstruktor auf null überprüfen zu müssen, wenn ich nicht sofort etwas mit dem Übergebenen mache. An solchen Stellen wünsche ich mir dann manchmal solche Überprüfungen auf null durch den Compiler (wie in den weiter oben angeführten Beispielen).



Landei hat gesagt.:


> Würde das mit null klarer oder kürzer?


So vielleicht:

```
public static <T> List<T> toList(Node<T> root) {
  List<T> list = new ArrayList<T>();
  toList(root, list);
  return list;
}

private static <T> void toList(Node<T> node, List<T> list) {
   if(node.left() != null) toList(node.left(), list);
   list.add(node.value());
   if(node.right() != null) toList(node.right(), list);
}
```
Ich weiß gerade nicht, was bei deinem Code passiert, wenn [c]root == null[/c] gilt. Bei meinem gibt es jedenfalls eine NPE. Der Rückgabewert meiner Implementierung wird jedenfalls nicht null sein, und sehr wahrscheinlich ist es ein Fehler, die Methode überhaupt mit null aufzurufen. (Gegenfrage: Wie sollte es anders sein?)

Zu deiner Implementierung: Sie erzeugt für nahezu jeden Knoten Iteratoren (Wirth  ) und lässt jeden einzelnen Knoten als Menge erscheinen. Für meine Begriffe ist diese Darstellung vor allem komplizierter. Als ich jedenfalls deinen Code spontan das erste Mal las, dachte ich nur: "Meine Güte, für einen Binärbaum hat ein Knoten hier aber verdammt viele Kinder." Denn was nicht aus deinem Code hervorgeht: [c]node.left()[/c] bzw. [c]node.right()[/c] sind jeweils höchstens einelementige Mengen (oder leer).

Wenn aber z.B. ich in meinem Code von Mengen rede, dann meine ich vor allem Mengen mit beliebig vielen Elementen. Wenn ich dagegen ausdrücken möchte, dass (für deine Begriffe) eine Menge leer ist oder höchstens ein Element besitzt, dann nehme ich mir einfach eine Referenz auf ein Objekt, denn diese ist entweder null (entspricht dem Fall, dass die Menge leer ist) oder von null verschieden (dann ist sie einelementig). Das ist doch wie gemacht dafür! Und dann kann ich auch nicht z.B. aus der höchstens einelementigen Menge aus Versehen eine zweielementige machen.

Ark


----------



## Landei (14. Dez 2010)

Ich denke, das führt zu nichts: Du denkst, wenn es in der Doc steht, ist alles gut, ich denke, guter Code sollte mehr oder weniger selbsterklärend sein. Du denkst, wenn man sich an Schema F hält, ist alles gut, ich denke, der Compiler sollte soviel Fehler wie möglich abfangen. Du wirst mit deiner Methode solange gut zurechtkommen, wie es nur um eigenen Code geht. Wenn du dich erst einmal durch ein paar hunderttausend Zeilen "Experten"-Code deiner Kollegen gewurstelt hast, denkst du vielleicht anders.

Nur ganz kurz zu den Punkten: Im catch-Block kann man z.B. ein Fehlerfenster zeigen, aber eventuell vergessen, eine Variable auf einen vernünftigen Wert zu setzen. Sicher sind die API-Methoden dokumentiert, aber wie gesagt halte ich das nicht für ausreichend. Mein root-Node wäre innerhalb des Baums kein null (ich wollte ja null-frei programmieren), sondern auch eine Option. Die Abfrage einer Option über Iteratoren ist nur eine Realisierungs-Möglichkeit, allerdings ist man mit Javas Sprachmitteln etwas eingeschränkt.

Meine Erfahrung in Scala ist: Es geht auch ohne null, und ohne dass man sich dabei einschränken muss. In Java ist das etwas komplizierter, aber auch hier habe ich die Erfahrung gemacht, dass man normalerweise ohne null auskommen kann, und dass das Design meiner Meinung nach dadurch besser wird. Ich denke, einige Programmierer haben genauso am goto gehangen wie du jetzt am null. Aber warum soll man sich freiwillig einen Besen auf den Buckel binden?


----------

