Java-Kurs: Klassen
Willemers Informatik-Ecke
Klassen als Datenstruktur Vererbung

Konzeption einer Klasse

Die Klasse stellt die Basis der objektorientierten Programmierung dar. Java definiert jedes Programmmodul als Klasse und damit sich selbst als objektorientierte Programmiersprache.

Eine Klasse enthält Daten

Klassen sind vor allem selbstdefinierte Datenstrukturen. Dazu werden mehrere Variablen zusammengefasst und bilden damit eine Einheit. Auf diese Weise kann die reale Welt modelliert werden.

Weitere Informationen unter Java: Klasse als Datenstruktur.

Eine Klasse verfügt über Methoden

Daneben definiert die Klasse Methoden, die freien Zugriff auf die Attribute haben. Die Methoden beschreiben die Funktionalität der Klasse.

Methoden sind eng mit den Datentypen verbunden. Eine Addition um 1 wird aus einer 5 in einer int-Variable eine 6 machen. Bei einem String wird der Buchstabe 1 angehängt. Bei einem Bruch wird der Zähler um den Gegenwert des Nenners erhöht und bei einem Datum der Tagesanteil inkrementiert. Allerdings wird bei Erreichen des höchstmöglichen Tags dieser wieder auf 1 gesetzt und der Monat erhöht, der bei Überschreiten der 12 wiederum bei 1 beginnt und in das Jahr überläuft.

Jede Java-Klasse in ihrer Datei

Eine öffentliche Klasse ist immer in einer eigenen Datei definiert, deren Namen aus dem der Klasse und der Erweiterung .java zusammengesetzt ist. Nach der Übersetzung durch den Compiler hat das Ergebnis die Extension .class.

Beispielsweise würde die Klasse Auto in der Datei Auto.java realisiert und könnte folgenden Code enthalten.

class Auto {
  String modell;
  String hersteller;
  int zulassung; // jahr der zulassung
  double neuwert;

  double getWert(int jahr) {
      return neuwert - ((0.0+jahr-zulassung)/20)*neuwert;
  }
}
Das Auto besitzt mehrere Attribute und die Methode getWert, die den aktuellen Autos berechnen soll. Dazu wird pro Jahr 5 Prozent vom Neuwert abgezogen. Falls Sie das unrealistisch finden, ersetzen Sie doch einfach die Formel in der Methode!

Objekte entstehen, Objekte vergehen

Die Klasse ist nur eine Blaupause für das Objekt.

Beispiel Auto

Gehen wir davon aus, dass es eine Klasse namens Auto gibt, erzeugen wir eine Variable davon wie eine Variable jeden anderen Typs.
Auto r4;
Die Variable vom Typ Auto enthält allerdings nicht die Daten des Autos, sondern ist nur ein Verweis - eine sogenannte Referenz - auf das Objekt, das aber leider noch gar nicht exisitiert. Diese Referenz speichert nicht die eigentlichen Informationen, sondern verweist nur auf ein Objekt, das aber erst mit dem Befehl new angelegt werden muss.

Auto r4;
r4 = new Auto();
Auto maserati = new Auto();
Hier wurden zwei Objekte mit new angelegt: Ein r4 und ein maserati. Wir können davon ausgehen, dass der maserati eine höhere kW-Wert und einen höheren Preis hat.

Hat eine Referenz noch kein zugehöriges Objekt, solle man es mit null vorbesetzen. Objekte, die keine Referenz mehr besitzen, werden vom Runtime System mit der sogenannten Garbage Collection entsorgt. Dies ist ein niedrig priorisierter Thread im Hintergrund, der prüft, ob ein herrenloses Objekt entsorgt werden kann.

Insofern müssen Sie sich in Java um die Erstellung der Objekte kümmern, aber nicht um deren Entsorgung. Sollten Sie das new eines Tages vergessen, wird Sie das Programm mit einer NullpointerException erinnern.

Zugriff auf die Objektelemente

Auf die Elemente eines Objekts wird zugegriffen, indem erst der Objektname, dann ein Punkt und anschließend das Attribut oder die Methode genannt wird.

Im folgenden Beispiel wird ein Objekt von Auto erzeugt, das über die Referenz r4 zugegriffen werden kann.

Auto r4 = new Auto();
r4.zulassung = 1984;
r4.neuwert = 8000.0;
double restwert = r4.getWert(1994);
Die Attribute werden mit Werten belegt. In der letzten Zeile wird die Methode getWert aufgerufen, die sich zur Berechnung des Restwertes an die Attribute des Objekts wendet, das über r4 referenziert wird.

Die Methode getWert kann nur über ein Objekt aufgerufen werden, weil jedes Objekt seine eigenen Attribute hat. Ohne ein Objekt ist es sinnlos, eine Methode aufzurufen, weil sie nicht weiß, woher sie ihre Attribute bekommen soll.

Methoden

Neben den Daten - also den Attributen - stellt eine Klasse gewöhnlich auch Methoden zur Verfügung. Die Methoden werden wie die Attribute über ein Objekt gerufen. Als Trennzeichen dient wiederum der Punkt.
Auto r4 = new Auto();
int distanz = 12;
int literBenzin = r4.berechneVerbrauch(distanz);
Für die Verbrauchsberechnung wird die Methode auf die Attribute des Objekts zugreifen, über die sie aufgerufen wurde. Beispielsweise würde die Methode berechneVerbrauch vielleicht den Durchschnittsverbrauch eines r4 zugrunde legen, der niedriger sein dürfte als der eines Maseratis.

Eine Methode hat also den Zugriff auf alle Attribute der Klasse. Sie kann diese direkt per Name ansprechen. Sie kann allerdings auch die Referenz this verwenden, die immer auf das Attribut des Objekts verweist.

Konstruktor

Der Aufruf hinter dem new hat verdächtige Klammern. Das sieht beinahe aus wie ein Methodenaufruf. Und tatsächlich wird damit eine sehr spezielle Methode aufgerufen: der Konstruktor.

Der Konstruktor ist eine Methode, die beim Erstellen eines Objekts durch new aufgerufen wird. Syntaktisch hat er besondere Eigenschaften:

Die Aufgabe des Konstruktors ist es, ein Objekt zu initialisieren. So könnte er beispielsweise Attribute auf Vorgabewerte setzen. Die Initialisierung durch den Konstruktor.
public class Auto {
    int zulassung;
    public Auto() {
        zulassung = 2000;
    }
}

Das ist allerdings auch durch eine direkte Initialisierung möglich. Darum wird ein Konstruktor meist nur dann gesetzt, wenn man ihn für andere Initialisierungen benötigt.

public class Auto {
    int zulassung = 2000;
}
Der Konstruktor wird ausgeführt, sobald ein Objekt mit new angelegt wird, bevor der erste Zugriff auf das Objekt erfolgt. Der Konstruktor kann beispielsweise Verbindungen über das Netzwerk herstellen oder Dateien öffnen, also Dinge tun, die nicht mit einer Variableninitialisierung getan ist.

Der Standardkonstruktor

Der normale Konstruktor hat keine Parameter. Da dieser immer durch den Befehl new aufgerufen wird, stellt ihn Java selbst zur Verfügung, wenn er im Programm nicht implementiert wird.

Diesen normalen Konstruktor ohne Parameter nennt man Standardkonstruktor.

Konstruktor mit Parametern

Es kann weitere (überladene) Konstruktoren mit Parametern geben. Im folgenden Beispiel werden im Standardkonstruktor die Variablen vorbelegt. Im Konstruktor mit dem Stringparameter wird der Name des Autos beim new bereits festgelegt.
public class Auto {
  String modell;
  int zulassung = 2000;

  public Auto(String name, int jahr) {
    zulassung = jahr;
    modell = name;
  }

  public Auto(String name) {
    modell = name;
  }
}
Um einen der beiden Konstruktoren aufzurufen, werden die Parameter zwischen die Klammern geschrieben.
Auto r4 = new Auto("Renault R4");
Auto nochEinR4 = new Auto("Renault R4", 1984);
Tatsächlich ist es nun aber nicht mehr möglich, ein Auto-Objekt zu erzeugen, ohne dass der Konstruktor einen Parameter erhält. Durch Weglassen des Standardkonstruktors wird also verhindert, dass ein Auto ohne Namen erzeugt wird.

Soll trotz überladener Konstruktoren der Aufruf von new Auto() möglich sein, muss ein Standardkonstruktor angelegt werden.

public? Gibt es auch private?

In den Listings steht immer wieder das Schlüsselwort public. Alles, was public ist, kann von anderen Klassen direkt zugegriffen werden.

Attribute stellen den Status einer Klasse dar und da ist es ein guter Gedanke, dass die nur kontrolliert verändert werden dürfen. Die Kontrolle sollten die Methoden der Klasse übernehmen.

Als Grundsatz gilt, dass alle Attribute als private deklariert sein sollten. Die Methoden sollten nur dann public sein, wenn sie wirklich anderen Klassen zur Verfügung stehen sollen.

Etwas, was vorher privat war, kann nachträglich veröffentlicht werden. Der umgekehrte Weg ist unsagbar schwieriger.

Weitere Informationen zum Thema public und private

Referenzen

Die Variablen einer Klasse sind keine Objekte, sondern Referenzen. Sie enthalten also nicht selbst die Objektdaten, sondern verweisen auf eine Stelle im Speicher, in der die Objektdaten stehen.

Dieser Speicher für die Objektdaten entsteht durch die Anweisung new. Da der Speicher vor der Ausführung von new nicht zur Verfügung steht, führt ein Zugriff über die Referenz dann zu einem Fehler, der als NullpointerException gemeldet wird.

Wenn Sie eine Referenz per System.out.println ausgeben, erhalten Sie den Klassennamen und eine Zahl. Das ist die Speicheradresse, wo der Objektspeicher steht.

Nehmen wir als Beispiel eine Klasse Mine. Eine Mine hat eine x- und eine y-Koordinate. In der folgenden Klasse legen wir fest, dass alle Minen erst einmal an der gleichen Stelle 3,4 liegen:

class Mine {
    int x=3, y=4;
}
Wird nun eine Variable von Mine angelegt, ist dies eine Referenz. Durch den Befehl new wird ein Objekt irgendwo im Speicher angelegt, auf das die Referenz verweist. Diese Informationen können Sie sogar auf dem Bildschirm sehen, wenn Sie die Referenz direkt ausgeben.
Mine m = new Mine();
System.out.println(m);
Auf dem Bildschirm erscheint:
Mine@4aa298b7
Die Referenz m verweist also auf ein Objekt der Klasse Mine, das sich an der Speicherstelle 4aa298b7 befindet.

Vergleich

Referenzen sind Verweise und enthalten selbst keine Daten. Darum kann man weder == noch einen anderen Vergleichsoperatoren anwenden. Zwei Referenzvariablen sind nämlich nur dann gleich, wenn sie auf die gleiche Speicheradresse verweisen und das trifft nur dann zu, wenn sie auf dasselbe Objekt verweisen.

Mine m = new Mine();
Mine b = new Mine();
System.out.println(m==b);
Das Ergebnis ist false. Zwar haben beide Objekte den gleichen Inhalt, sie befinden sich aber an unterschiedlichen Stellen im Speicher. Und genau diese Information ist der Inhalt einer Referenz.

Nur wenn die Referenzen auf dasselbe Objekt zeigen, beispielsweise nach einer Zuweisung, sind sie gleich:

Mine m = new Mine(); 
Mine b = m;
System.out.println(m==b);

equals

Vom String kennen wir die Methode equals, die zur Prüfung auf Gleichheit angewandt wird. Diese Methode ist in der Basisklasse aller Klassen namens Object ebenfalls implementiert. Darum können Sie equals für jede Klasse aufrufen. Allerdings wird sie für eigene Klassen nichts anderes liefern als den Vergleich mit dem Gleichheitsoperatoren.
Mine m = new Mine();
Mine b = new Mine();
System.out.println(m.equals(b));
Auch in diesem Fall erscheint false auf dem Bildschirm.

Wird eine Prüfung auf Gleichheit benötigt, sollte die Klasse die Methode equals selbst implementieren. Der Rückgabetyp ist boolean und der Parameter vom Klassentyp.

class Mine {
    int x=3, y=4;

    boolean equals(Mine m) {
        return m.x==x && m.y==y;
    }
}
Letztlich kann nur die Klasse entscheiden, wann zwei Objekte als identisch anzusehen sind. Darum kann nur die Implementierung von equals ein eindeutiges Ergebnis bringen.

Kopie und Zuweisung

Eine normale Variable kann durch Zuweisung an eine andere typkompatible Variable kopiert werden. Bei Referenzen führt eine Zuweisung dazu, dass anschließend beide Referenzen auf dasselbe Objekt verweisen, da der Inhalt einer Referenz lediglich die Speicheradresse des Objekts ist.

Änderungen über eine der beiden Referenzen erfolgen dann auch in dem Bereich, auf den die zweite Referenz verweist. Der gleiche Effekt tritt auf, wenn Referenzen als Parameter an eine Methode übergeben werden. Ändert die Methode Daten der Referenz, betrifft das auch die Daten des Aufrufers.

Will man tatsächlich eine Kopie des Objekts, muss ein neues Objekt angelegt werden und die Attribute kopiert werden. Dazu sollte die Klasse eine Methode zur Verfügung stellen. Man pflegt eine solche Methode clone zu nennen. Java bietet sogar standardmäßig eine solche Methode an, ihre Benutzung ist aber nicht ganz trivial.

Videos

Klassen als Basis für die objektorientierte Programmierung

Privatspäre und Referenzprobleme


Klassen als Datenstruktur Vererbung