Java Object! Die Mutter aller Klassen!

Keylearnings:

  • Welchen Zweck dient die Klasse Java Object? 
  • Was ist ein Casting?
  • Wofür ist die Java Object clone() Methode?
  • Wofür ist die Java Object equal Methode?
  • Wofür ist die toString() Methode?

Jedes Kind braucht eine Mutter. Das ist in Java nicht anders.

Alle deine Java Klassen haben eine gemeinsame Mutter. Nämlich die Oberklasse Java Object.

Und genau diese Supermutti möchte ich dir in diesem Artikel vorstellen.

Jede Klasse erbt von Java Object

Beginnen wir mit einem Beispiel und kreieren eine Klasse Hund.

class Hund{
	
}

Diese Klasse macht genau nichts! Dennoch können wir hiervon eine Instanz erzeugen.

Hund hund = new Hund();

Und sogar eine Methode toString() aufrufen.

System.out.println(hund.toString());

Wir erhalten die Bildschirmausgabe:

Hund@15db9742

Doch woher kommt die Methode toString?

Tja, das war Mutti!

Jede Klasse, bei der wir keine Oberklasse angeben erbt automatisch von der Java Klasse Object und in dieser ist unter anderem die Methode toString definiert.

Da wir bei der Definition unserer Hunde-Klasse keine Oberklasse angegeben haben, ist diese automatisch eine Unterklasse von Java Object.

Der Compiler interpretiert unsere Klasse also so, als ob wir Hund mittels des Schlüsselworts extends von der Oberklasse Object abgeleitet hätten.

class Hund extends Object{
	
}

Somit ist in Java jede Klasse eine Unterklasse von Object. Den Nutzen dieser Tatsache wollen wir uns als nächstes ansehen.

Java Object als Behälter für alles!

Da Object die Oberklasse aller Klassen ist, können wir die macht der Polymorphie nutzen und Instanzen, egal welcher Klasse, Variablen vom Typ Object zuweisen.

So können wir beispielsweise unseren Hund in einer Variablen tier vom Typ Object speichern.

Object tier = new Hund();

Soweit so gut, aber was passiert mit den Methoden und Eigenschaften unseres Hundes? Bleiben diese erhalten?

Probieren wir das mal aus und ergänzen unsere Klasse Hund um eine Methode bellen().

public void bellen(){
	System.out.println("Wuff wuff!");
}

Versuchen wir jetzt über die Variable tier vom Typ Object diese Methode aufzurufen, dann erhalten wir die Fehlermeldung, dass die Methode bellen() für Instanzen vom Typ Object nicht definiert ist.

Und wo der Compiler Recht hat, hat er Recht! Schließlich gehört die bellen() Methode zu der Klasse Hund und nicht zu Java Object.

Also was tun?

Um die bellen() Methode über die Variable tier aufzurufen, müssen wir diese in eine Instanz vom Typ Hund konvertieren.

Für diesen Zweck gibt es in Java das sogenannte Casting.

Beim Casting müssen wir lediglich den Zieltyp in runden Klammern vor die Variable schreiben, welche wir konvertieren wollen.

Wichtig hierbei ist, dass der Zieltyp eine Unterklasse von dem Typ der zu konvertierenden Variable ist. Da allerdings jede Klasse Unterklasse von Java Object ist, müssen wir uns darüber in unserem Beispiel keine Gedanken machen.

Konvertieren wir also unser Java Object tier Objekt in ein Hund Objekt.

Hund hund = (Hund) tier;

Nach Durchführung dieses Castings können wir die bellen() Methode ohne Problem über die Variable Hund aufrufen.

hund.bellen();

Wir erhalten die Bildschirmausgabe:

Wuff wuff!

Der instanceof Operator

Okay, noch bringt uns das alles nicht wirklich nach vorne.

Wir wollen Variablen vom Typ Java Object als Behälter für alles verwenden.

Aber was passiert, wenn wir der Variable tier eine Katze zuweisen? Kann die dann auch bellen?

Natürlich nicht!

Aber langsam. Wir erstellen zunächst eine Klasse Katze

class Katze{
	
	public void mieau(){
		System.out.println("Mieau");
	}
	
}

Diese Klasse enthält lediglich eine Methode mieau(), die eine katzentypische Bildschirmmeldung erzeugt.

In Abhängigkeit davon, ob die Variable tier einen Hund oder ein Katze speichert, soll unser Tier entweder bellen oder mieauen.

Um das zu bewerkstelligen benötigen wir eine Möglichkeit um festzustellen, ob die Java Object Variable einen Hund oder eine Katze enthält.

Glücklicherweise stellt Java genau für diesen Zweck den instanceof Operator zur Verfügung.

Der instanceof Operator ist ein logischer Operator, liefert also einen Wahrheitswert zurück und ist am einfachsten im praktischen Einsatz zu verstehen. Also auf geht’s! 🙂

1: Object[] tier = new Object[2];
		
2: tier[0] = new Hund();
3: tier[1] = new Katze();
4: for(int i=0;i<tier.length;i++){
5:	if (tier[i] instanceof Hund){
6:		Hund hund = (Hund) tier[i];
7:		hund.bellen();
8:	}else{
9:		Katze katze = (Katze) tier[i];
10:		katze.mieau();
11:	}
12: }

In Zeile Eins erzeugen wir ein zweielementiges Object Array, das wir in Zeile Zwei in der ersten Komponente mit einer Hunde-Instanz und in Zeile Drei in der zweiten Komponente mit einer Katzen-Instanz initialisieren.

Anschließend (Zeile 4) durchlaufen wir das Array mit Hilfe einer for Schleife. Inhalt der Schleife ist eine if-Kontrollstruktur, in der wir mit Hilfe des instanceof Operator überprüfen, ob das Array-Element tier[i] eine Hunde- oder Katzen-Instanz beinhaltet.

Handelt es sich um die Instanz eines Hundes, dann überführen wir mit Hilfe eines Castings (Zeile 6), das Java Object tier in eine Variable vom Typ Hund und rufen die bellen() Methode des Hund-Objektes (Zeile 7) auf.

Hierzu analog, wird in Zeile Neun ein Casting in eine Variable vom Typ Katze durchgeführt und anschließend die mieau() Methode (Zeile 10) des Katzenobjekts aufgerufen.

Die Bildschirmausgabe des Programms ist:

Wuff wuff!
Mieau

Die Methoden von Java Object

Selbstverständlich besitzt die Klasse Object auch eigene Methoden. Neben Methoden, die zur Verwaltung von Threads dienen, sind das insbesondere clone(), equals(Object) und toString().

Da die Klasse Object die allgemeinste Klasse ist, die es in Java geben kann, sind die Methoden der Object Klasse sehr allgemein und müssen häufig durch ein Überschreiben der Methode spezialisiert werden.

Im folgenden wollen wir uns die wichtigsten Methoden der Object Klasse genauer ansehen.

Die Java Object Methode clone

Die clone() Methode dient zum kopieren von Objekten. Hierbei unterscheiden wir zwischen einer flachen und einer tiefen Kopie. Um das zu demonstrieren ergänzen wir die Klasse Hund um ein Attribut name und einer zugehörigen Setter- und Getter-Methode.

1: class Hund{
	
2:	private String name = null;

3:	public void setName(String name) {
4:		this.name = name;
5:	}

6:	public String getName() {
7:		return name;
8:	}

9:	public void bellen(){
10:		System.out.println("Wuff wuff!");
11:	}
	
12:}

Hiervon erzeugen wir jetzt eine Instanz und setzen das Attribut name auf Bello.

Hund hund = new Hund();
hund.setName("Bello");

Awww, Bello ist soo süß! Ich will eine Kopie davon! Versuchen wir es mit:

Hund hund2 = hund;

Auf den ersten Blick ist alles in Ordnung, wenn wir den Namen von hund und hund2 ausgeben

System.out.println(hund.getName());
System.out.println(hund2.getName());

scheint es so als hätten wir tatsächlich zwei Bellos. Die Programmausgabe ist wie erwartet:

Bello
Bello

Wenn wir jetzt aber auf die Idee kommen hund2 in Hugo umzubennen und anschließend die Namen beider Hunde auf dem Bildschirm ausgeben

hund2.setName("Hugo");
System.out.println(hund.getName());
System.out.println(hund2.getName());

dann erhalten wir:

Hugo
Hugo

Obwohl wir nur die setName Methode von hund2 aufgerufen haben, wurde auch die zweite Instanz hund in Hugo umbenannt.

Was ist hier passiert?

Erinnern wir uns daran, was in einer Instanzvariable gespeichert wird.

Durch die Erzeugung eines Objekts mit dem Schlüsselwort new wird Speicherplatz reserviert. In der Instanzvariablen befindet sich jedoch lediglich ein Verweis auf den Speicherort, an dem Platz im Computerspeicher geschaffen wurde und durch die Anweisung

Hund hund2 = hund;

wird lediglich dieser Verweis in die Variable hund2 kopiert. Daher ist es egal, ob wir die setName Methode von hund oder hund2 aufrufen. In beiden Fällen wird der gleiche Speicherbereich verändert.

Java Object clone

In diesem Fall sprechen wir von einer flachen Kopie.

Um eine echte Kopie, also zwei Speicherbereiche mit gleichem Inhalt zu erzeugen müssen wir die clone() Methode verwenden.

Also versuchen wir es mal mit:

Hund hund2 = hund.clone();

In der Klasse Hund ist keine Methode mit dem Namen clone() implementiert, daher wird die clone() Methode der Oberklasse Object aufgerufen. Diese liefert jedoch kein Hund sondern ein Object Objekt zurück, weshalb es zu einem Type mismatch Fehler kommt.

Um eine funktionierende clone() Methode zu erhalten, bleibt uns nichts anderes übrig als die clone() Methode aus Object in der Klasse Hund zu überschreiben.

Erweitern wir die Klasse Hund also um folgende Methode:

1: public Hund clone(){
2:	Hund hund = new Hund();
3:	hund.setName(this.name);
4:	return hund;
5:}

In Zeile Zwei erzeugen wir ein neues Hunde-Objekt, dem wir in Zeile Drei den Namen des Hundes, aus dem wir die clone() Methode aufrufen zuweisen und anschließend als Rückgabewert zurückliefern.

Führen wir unseren Test erneut durch:

Hund hund2 = hund.clone();
hund2.setName("Hugo");
System.out.println(hund.getName());
System.out.println(hund2.getName());

Dadurch, dass wir in der clone() Methode ein neues Hunde-Objekt erzeugt haben, zeigen die Variablen hund und hund2 auf unterschiedliche Bereiche im Speicher und der Aufruf von setName wirkt sich nur auf den Speicherbereich, auf den hund2 zeigt aus. Daher erhalten wir jetzt wie gewünscht:

Bello
Hugo

Das nennen wir eine tiefe Kopie!

Die Java Object Methode equals

Die equals Methode verwenden wir um zwei Objekte miteinander zu vergleichen. Ein Vergleich wie

hund == hund2

überprüft lediglich, ob die in hund und hund2 gespeicherten Speicheradressen übereinstimmen.

Deutlich sinnvoller ist aber, wenn wir zwei Hunde als gleich ansehen, wenn diese den gleichen Namen haben. Um dies zu erreichen müssen wir die equal Methode aus der Object in der Hund Klasse überschreiben.

Wir ergänzen die Klasse Hund also um folgende Methode.

2: public boolean equals(Object obj) {
3:      if (obj == null) 
4:             return null;
5:	if (this.name.equals(((Hund) obj).name)){
6:			return true;
7:	}else{
8:			return false;
9:	}
10:}

Die equals Methode hat einen Übergabeparameter vom Typ Object, das dem Objekt entspricht, mit dem wir den Vergleich durchführen möchten und liefert einen Wahrheitswert als Rückgabeparameter zurück.

Um eine Nullpointer Exception in Zeile Fünf zu vermeiden, prüfen wir in Zeile Drei zunächst, ob unser Vergleichsobjekt instanziiert ist.

In der if Bedingung in Zeile Fünf verwenden wir die equals Methode des String Objekts name um den Namen des Hundes mit dem Namen des zu vergleichenden Hunde-Objekts abzugleichen.

Hierbei ist zu beachten, dass das Hunde Vergleichsobjekt obj vom Typ Object ist und daher ein Casting zur Konvertierung in ein Hundeobjekt notwendig ist.

Wird eine Gleichheit der Namen festgestellt, liefert die Methode den Wahrheitswert true andernfalls den Wert false zurück.

Okay, machen wir einen Testlauf und erzeugen zwei Hundeobjekte mit gleichem Namen, vergleichen diese miteinander, ändern den Namen des zweiten Hundes und führen anschließend den Vergleich erneut durch.

Hund hund1 = new Hund();
Hund hund2 = new Hund();
hund1.setName("Bello");
hund2.setName("Bello");
if (hund1.equals(hund2)){
	System.out.println("Hunde haben den gleichen Namen");
}else{
	System.out.println("Hunde haben verschiedene Namen");
}
hund2.setName("Hugo");
if (hund1.equals(hund2)){
	System.out.println("Hunde haben den gleichen Namen");
}else{
	System.out.println("Hunde haben verschiedene Namen");
	}

Die Ausgabe des Programms ist wie erwartet.

Hunde haben den gleichen Namen
Hunde haben verschiedene Namen

Die Java Object Methode hashCode

Ich gebe es zu! Ich habe dir die Oldschool Methode gezeigt um zwei Objekte miteinander zu vergleichen.

Es gibt noch einen moderneren und effektiveren Weg dies zu erreichen. Und zwar mit Hilfe eines sogenannten Hashwerts.

Für jedes Objekt kann mithilfe einer mathematischen Hashfunktion ein Zahlenwert berechnet werden. – Der sogenannte Hashwert – Das besondere an diesem Wert ist, dass dieser für unterschiedliche Objekte vom gleichen Typ verschieden ist.

In unserem Hundebeispiel bedeutet dies, dass Bello einen anderen Hashwert als Hugo hat.

Glücklicherweise müssen wir uns über die notwendige Mathematik zur Berechnung dieses Werts keine Sorgen machen.

Alles was wir tun müssen, ist die Methode hashcode aus der Klasse Object zu überschreiben.

Wir ergänzen unsere Hunde-Klasse also um folgende Methode.

public int hashCode() {
       return name.hashCode();
}

Der Hashwert ist eine Zahl, weshalb die  hashCode() Methode einen Integerwert als Rückgabe besitzt.

Innerhalb der überschriebenen Methode rufen wir lediglich die hashCode Methode des String-Attributs auf und geben den berechneten Hashwert zurück.

Um eine Vorstellung davon zu bekommen was diese Methode macht, erzeugen wir zwei Hundeobjekte Bello und Hugo und geben deren Hashwerte auf dem Bildschirm aus.

Hund hund = new Hund();
Hund hund2 = new Hund();
hund.setName("Bello");
hund.setName("Hugo");
		
System.out.println(hund.hashCode());
System.out.println(hund2.hashCode());

Die Bildschirmausgabe dieses Programms lautet:

64068524
2260693 

Die Tatsache, dass zwei unterschiedliche Objekte vom gleichen Typ, unterschiedliche Hashwerte haben, können wir verwenden um die equals Mehode zu implementieren.

public boolean equals( Objekt obj ){
return object != null && obj instanceof Hund && this.hashCode() == obj.hashCode();
}

Sieht kompliziert aus. Sieht aber auch nur so aus 🙂

Alles was wir hier tun, ist zu überprüfen, ob unser Vergleichsobjekt initialisiert ist und ob es sich hierbei um eine Hunde-Instanz handelt. Ist eines dieser beiden Bedingungen nicht erfüllt, brechen wir den Vergleich ab und geben den Wahrheitswert false zurück, da wir ansonsten Äpfel mit Birnen vergleichen würden.

Sind obige Kriterien erfüllt, berechnen und vergleichen wir die Hashwerte der Namensattribute der zu vergleichenden Hunde. Stimmen diese überein, so haben beide Hundeobjekte den gleichen Namen.

Die Java Object Methode toString

Als letztes wollen wir die Java Object Methode toString() besprechen. Diese erzeugt den Text für eine Bildschirmausgabe wie:

System.out.println(hund1.toString());

Standardmäßig wird die Speicheradresse, die in der Instanzvariable gespeichert ist ausgegeben. So erhalten wir in unserem Beispiel die Ausgabe:

Hund@3d19bcb

Schöner wäre es allerdings auch hier den Namen des Hundes auszugeben. Und sicher weißt du schon was wir dafür machen müssen.

Korrekt! Wir müssen die toString Methode der Klasse Object überschreiben. Wir fügen der Klasse Hund also folgende Methode hinzu:

public String toString() {
	return "Hund [name=" + name + "]";
}

Rückgabewert der toString Methode ist ein String. In unserem Fall das Namensattribut des Hundes. Die Bildschirmausgabe unseres Programmes lautet jetzt:

Hund [name=Bello]

Fazit: Die Java Object Klasse ist Oberklasse jeder Java Klasse. Definieren wir eine Klasse ohne, dass wir mittels des Schlüsselworts extends eine direkte Oberklasse angeben, dann ist diese Klasse eine direkte Unterklasse von Java Object.

Aufgrund der Allgemeinheit von Java Object können wir Variablen vom Typ Object als Behälter für alles auffassen. Mit Hilfe eines Casting kann man den allgemeinen Object Typ konvertieren. Des Weiteren stellt die Klasse Java Object Methoden zur Verfügung mit deren Hilfe Objekte kopiert, verglichen und um eine Standardausgabe ergänzt werden können.

Ich freue mich auf deine Fragen im Kommentarbereich!

Hat dir der Artikel gefallen? Dann folge uns doch am besten gleich auf Facebook!

Hallo ich bin Kim und ich möchte ein großer Programmierer werden. Machst du mit?

Kommentare (11)

  • Antworte

    Sehr hilfreich und gut geschrieben, danke!

    • Sehr gerne!

  • Antworte

    Sehr gute Website 🙂

    • Ich danke dir!

  • Antworte

    „Jedes Kind braucht eine Mutter.“ – und was ist mit den Väter

    • Natürlich! Ich wollte nicht Geschlechterdiskriminierend sein. In der Literatur wird in der Regel auch einfach von Parent gesprochen.

  • Antworte

    Hallo Kim,

    deine Erläuterungen sind ausgezeichnet und sehr verständlich geschrieben! Zu der „clone() Methode“ habe ich allerdings eine Verständnisschwierigkeit.

    In der „clone() Methode“ wird selbst wiederum die „setName(String name) Methode“ in der Form „hund.setName(this.name);“ aufgerufen. Ich frage mich nun, wozu diese Methode innerhalb der „clone() Methode“ aufgerufen wird? Die Referenz/Adresse „this.name“ enthält für mein Verständnis beim Aufruf der „clone() Methode“ den Wert „null“.

    Erst nach Aufruf der „clone() Methode“ in der Form „Hund hund2 = hund.clone();“ wird für die Instanz „hund2“ durch Aufruf der Methode „hund2.setName(„Hugo“);“ das Attribut „name“ auf „Hugo“ gesetzt:

    Hund hund2 = hund.clone();
    hund2.setName(„Hugo“);
    System.out.println(hund.getName());
    System.out.println(hund2.getName());

    Möglicherweise habe ich hier auch eine generelle Verständnisschwierigkeit. Ich würde mich freuen, wenn du mir dazu Rückmeldung geben könntest.

    Vielen Dank,
    Harald

    • Hallo Harald, vielen Dank für deine Rückmeldung. Der this.name verweist auf das Attribut name der Hunde-Instanz. In unserem Fall ist das der Wert Bello. Viele Grüße Kim

    • Hallo Harald, wir rufen die clone() Methode für ein bereits instanziertes Objekt auf, in welchem wir das Namens Attribut bereits gesetzt haben und dieser Wert wird dann durch this.name in das neue Objekt geschrieben. Viele Grüße Kim

  • Antworte

    „Das nennen wir eine tiefe Kopie!“
    Leider nein. Das ist eine flache (shallow) Kopie. Denn alle Attribute des geklonten Objektes haben denselben Wert wir das Original. Nicht den gleichen. Bei beiden Hunde-Instanzen zeigt this.name nach dem Klonen auf dasselbe String-Objekt. Würde es geändert, würde es sich für beide Hunde gleich ändern.
    Zwar wird im Beispiel hund2 mittels hund2.setName ein neuer Name zugewiesen, aber das hat nichts mit dem Klonen zu tun. Wir wissen ja schon, dass hund1 und hund2 zwei verschiedene Objekte sind man ihrer name-Variablen daher verschiedene Namen zuweisen kann.
    Nun mag es bei Strings in Java egal sein, da man sie nicht so einfach ändern kann, aber anderen Objekttypen sieht das anders aus.
    Gäbe es also eine Liste (Array etc.) von Abkömmlingen (Hund.welpen), so würde Hund.welpen=this.welpen die einen Zeiger auf die Liste selbst kopieren, nicht deren Inhalt. Bekommt hund1 dann einen Sohn, so wird plötzlich auch hund2 auf magische Weise Vater. Denn bei beiden verweist das Attribut welpen auf dieselbe Liste.

    Bei einer tiefen (deep) Kopie müssten allen Instanzvariablen der Kopie Klone der Instanzvariablen des Originals zugewiesen werden. Also im Falle von Welpen eine neu erstellte Liste mit einer Kopie der im Original eingetragenen Welpen. Wie tief diese Rekursion von Kopien reicht, hängt von der Implementation und dem Anwendungszweck ab.

    • Hallo Peter, vielen Dank für deinen Kommentar. Du hast natürlich recht wenn Hund Atrribute hätte die wiederum selber Objekte sind, dann muss man jedes dieser Objekte mit Hilfe von new erzeugen und zuweisen. Das Problem ist, dass ich einen String vereinfacht als Primitivtyp angenommen habe. Viele Grüße Kim

Hinterlasse ein Kommentar