Java Standard: Objekte


Klassen, Objekte, InstanzenBearbeiten

Eine Klasse beschreibt die (allgemeine) Definition. Alles mit Ausnahme der primitiven Datentypen (int, boolean, long etc.) in Java ist abgeleitet von "java.lang.Object". Das heißt, dass jede Java-Klasse, die Sie schreiben, bestimmte Methoden bereits von "Object" geerbt hat.

Ein Objekt – auch Instanz genannt – ist ein bestimmtes Exemplar einer Klasse und wird zur Laufzeit des Programms erzeugt.

KonstruktorenBearbeiten

Um eine Instanz einer Klasse zu erschaffen, wird der Konstruktor benutzt. Der Konstruktor ist namentlich wie die Klasse zu benennen. Die Syntax entspricht hierbei einer Methode jedoch gibt der Konstruktor keinen Wert, das heißt auch kein void, zurück.

 package org.wikibooks.de.javakurs.oo;
 public class MeineKlasse 
 {
   // Nun der Konstruktor
   public MeineKlasse ()
   {
   ...
   }
 }

DestruktorenBearbeiten

Destruktoren gibt es in Java nicht. Es besteht eine gewisse Wahrscheinlichkeit dafür, dass die finalize Methode einer Instanz vor dessen Zerstörung durch die Garbage Collection aufgerufen wird. Dies ist jedoch nicht sichergestellt.

Methoden in "java.lang.Object"Bearbeiten

Hier sind nun einmal die wichtigsten Methoden, die man in allen Java-Objekten finden und aufrufen kann, aufgeführt.

toString()Bearbeiten

Diese Methode ist während der Entwicklung eines Programmes von großem Nutzen. Jede Klasse, welche toString() überschreibt, kann hier den relevanten Inhalt der Klasse in Textform ausgeben. Mit System.out.println(object); kann man nun den Inhalt eines Objektes ansehen.

equals(Object)Bearbeiten

Java unterscheidet zwei Arten von Gleichheit:

Identisch: Zwei Objekte sind identisch, wenn sie beide das selbe Objekt (exakt am selben Ort im Speicher) referenzieren, was mittels "==" getestet wird. In der Objektorientierung heißt das auch, dass diese eine gemeinsame Instanz haben. Stellen Sie sich das einfach so vor, als wenn Hausnummern in einer Stadt verglichen werden: Die Hausnummern sind zwar gleich, aber es können verschiedene Leute im Haus wohnen.

Beispiel:

 Object obj1 = new Object();
 Object obj2 = obj1;
 if (obj1==obj2)
 {
     // beide Objekte obj1 und obj2 sind identisch, da sie beide am selben Ort im Speicher liegen.
 }

Gleich resp. equal: Zwei Objekte sind gleich, wenn sie denselben semantischen Inhalt repräsentieren und das wird mittels equals(..) getestet. Um beim Hausbeispiel zu bleiben: Hier vergleichen wir die Leute, die in einem Haus wohnen, mit der Liste von Leuten, die in diesem Haus wohnen sollen.

Beispiel:

 String name1 = new String("Peter Muster");
 String name2 = new String("Peter Muster");
 if (name1.equals(name2)) 
 {
      // beide Objekte repräsentieren denselben Inhalt, sie sind aber nicht am selben Ort gespeichert
      // name1 == name2 ergibt deshalb false, name1 und name2 sind gleich, aber nicht identisch.
 }

Ein Beispiel aus der realen Welt: Zwillinge sind gleich, aber nicht identisch. Weshalb: wenn man dem einen Zwilling z.B. die Haare färbe, dann behält der andere Zwilling seine Haarfarbe. Dasselbe gilt für Objekte:

Wenn zwei Objekte gleich, nicht aber identisch sind, dann kann ich im ersten Objekt etwas ändern, ohne dass sich dadurch das zweite Objekt ändert.

Wenn zwei Objekte identisch sind und ich ändere im ersten Objekt etwas, dann ist diese Änderung auch beim zweiten Objekt sofort vorhanden (beide Objekte sind eben identisch).

Wenn Objekte identisch sind, dann sind sie immer auch gleich (zumindest sollte es so sein, sofern man equals korrekt implementiert hat).

Benutzung der Methode equals: Hier muss man sicherstellen, dass das Objekt, auf welchem die Methode equals aufgerufen werden soll, nicht null ist. Typischweise sieht man dann sowas:

 if (name1!=null && name1.equals(name2)) {...}

Eigene Implementierung Wenn Sie eine eigene Klasse anlegen sollten Sie stets diese Methode implementieren.

hashCode()Bearbeiten

Die Methode hashCode() liefert einen int als Rückgabewert und wird überall dort verwendet wo direkt oder indirekt mit Hashtables oder Hash-Sets gearbeitet wird. Zur Definition einer Hashtable siehe weiter unten. Wichtig: wenn zwei Objekte gleich (equals) sind, dann sollten sie unbedingt denselben hashCode zurückliefern. Umgekehrt ist es aber erlaubt, dass, wenn zwei Objekte nicht gleich sind, ihre beiden hashCodes gleiche Werte zurückliefern. Deshalb sollte man, wenn man equals() überschreibt auch gerade hashCode() überschreiben.

Und was geschieht, wenn man equals überschreibt, aber den hashCode nicht? Solange man die Objekte nicht in einer Hashtable/HashMap oder einem HashSet unterbringt hat man keine Probleme. Das ändert sich aber, sobald man die Objekte in Hashtables/HashMap oder HashSets unterbringen will. Um zu verstehen, was dabei "schiefgeht", muss man verstehen, wie Hashtables funktionieren.

Zur Definition einer Hashtable: Es ist eine Datenstruktur, welche erlaubt, sehr schnell Objekte anhand ihres Schlüssels abzulegen und anhand dieses Schlüssels wieder aufzufinden. Einfach ausgedrückt ist eine Hashtable ein Array von Schlüssel-Objekt-Paaren einer bestimmten Größe. Der Hash-Wert des Schlüssels modulo Größe der Hashtable dient dabei als Index, wo der Schlüssel selbst und das dazugehörige Objekt abgelegt werden. Falls nun mehrere Schlüssel an denselben Platz in der Hashtable platziert werden, so wird (in Java, nicht zwingend aber in anderen Programmiersprachen) einfach eine Liste benutzt, wo alle Schlüssel-Werte-Paare der entsprechenden Hashtable-Position gespeichert werden. Um Objekte aus der Hashtable herauszuholen, wird fast dasselbe gemacht wie beim Einfügen. Es wird die Position in der Hashtable berechnet (hash-Code des Schlüssels modulo Hashtable-Größe) und dann alle dort gefunden Objekte mittels der equals-Methode verglichen. Sobald die equals-Methode erfolgreich war, so hat man das richtige Objekte gefunden. Das Ganze ist dann besonders effizient, wenn die Hashtable klein ist und alle Schlüssel genau einen eigenen Index rsp. Hashtable-Position haben. Als Faustregel gilt: Ist eine Hashtable zu 50% gefüllt, sinkt die Effizienz bei weiterem Einfügen schnell. Zudem sollten die hashCode-Werte möglichst gut verteilt sein. Am schlimmsten wäre es, wenn alle Objekte derselben Klasse als hashCode denselben Wert zurückliefern. Die Größe einer Hashtable kann man beim Erzeugen definieren. Zusätzlich kann man ihr einen Füllgrad angeben, ab wann sie sich vergrößern soll. Die Vergrößerung einer Hashtable ist eine sehr teure Operation, die man indirekt durch Hinzufügen eines neuen Schlüssel-Werte-Paares auslöst. Dabei werden alle Schlüssel-Werte-Paare in einer neuen, größeren Hashtable abgelegt. Das geschieht zwar automatisch, ist aber zeitaufwändig.

Was geschieht nun, wenn man in einer Klasse K equals überschreibt, nicht aber hashCode? Dann wird der hashCode der Oberklasse benutzt (was bei der Objekt-Klasse mehr oder weniger der Speicheradresse gleichkommt). Wenn nun als Schlüssel Instanzen der Klasse K benutzt werden, dann werden alle Instanzen mit großer Wahrscheinlichkeit gleichmäßig in der Hashtable abgelegt (das wäre ja noch gut). Beim Auffinden eines Objektes anhand eines Schlüssels wird der hashCode des Schlüssels berechnet, um die Position in der Hashtable herauszufinden. Wenn nun zwei Schlüssel gleich sind, aber unterschiedliche hashCodes haben, dann wird jedes Schlüssel-Wert-Paar an eine andere Position abgelegt. Die Folge: man findet Objekte mit großer Wahrscheinlichkeit nicht mehr, da man am "falschen" Ort sucht.

Beispiel, was falsch läuft:

 import java.util.*;
 
 public class K extends Object {
   private String content;
  
   public K(String content) {
      this.content = content;
   }
  
   public boolean equals(Object obj) {
      if (this==obj) {
         return true;
      }
      if (obj==null) {
         return false;
      }
      if (!(obj instanceof K )) {
         return false; // different class
      }
      K other = (K) obj;
      if (this.content==other.content) {
         return true;
      }
      if (this.content==null && other.content!=null) {
         return false;
      }
      // this.content can't be null
      return this.content.equals(other.content);
   }
 
   public static void main(String[] args) {
      K k1 = new K("k"); // let's say has hashCode 13 
      K k2 = new K("k"); // let's say has hashCode 19
 
      Map map = new HashMap();
      map.put(k1, "this is k1");
 
      String str = (String) map.get(k2);
      // str will be null because of different hashCode's
      System.out.println(str);
      // next line will print "true" because k1 and k2 are the same, so they should also return the same hashCode
      System.out.println(k1.equals(k2));
   }
 }

Was fehlt, ist der hashCode():

 public class K extends Object {
 ...
 
  public int hashCode() {
     if (this.content==null) {
        return 0;
     }
     return this.content.hashCode();
  }
 }

wait(), notify() und notifyAll()Bearbeiten