Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger. 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 5 Eigene Klassen schreiben
Pfeil 5.1 Eigene Klassen mit Eigenschaften deklarieren
Pfeil 5.1.1 Attribute deklarieren
Pfeil 5.1.2 Methoden deklarieren
Pfeil 5.1.3 Die this-Referenz
Pfeil 5.2 Privatsphäre und Sichtbarkeit
Pfeil 5.2.1 Für die Öffentlichkeit: public
Pfeil 5.2.2 Kein Public Viewing – Passwörter sind privat
Pfeil 5.2.3 Wieso nicht freie Methoden und Variablen für alle?
Pfeil 5.2.4 Privat ist nicht ganz privat: Es kommt darauf an, wer’s sieht *
Pfeil 5.2.5 Zugriffsmethoden für Attribute deklarieren
Pfeil 5.2.6 Setter und Getter nach der JavaBeans-Spezifikation
Pfeil 5.2.7 Paketsichtbar
Pfeil 5.2.8 Zusammenfassung zur Sichtbarkeit
Pfeil 5.3 Eine für alle – statische Methode und statische Attribute
Pfeil 5.3.1 Warum statische Eigenschaften sinnvoll sind
Pfeil 5.3.2 Statische Eigenschaften mit static
Pfeil 5.3.3 Statische Eigenschaften über Referenzen nutzen? *
Pfeil 5.3.4 Warum die Groß- und Kleinschreibung wichtig ist *
Pfeil 5.3.5 Statische Variablen zum Datenaustausch *
Pfeil 5.3.6 Statische Eigenschaften und Objekteigenschaften *
Pfeil 5.4 Konstanten und Aufzählungen
Pfeil 5.4.1 Konstanten über statische finale Variablen
Pfeil 5.4.2 Typunsichere Aufzählungen
Pfeil 5.4.3 Aufzählungstypen: Typsichere Aufzählungen mit enum
Pfeil 5.5 Objekte anlegen und zerstören
Pfeil 5.5.1 Konstruktoren schreiben
Pfeil 5.5.2 Verwandtschaft von Methode und Konstruktor
Pfeil 5.5.3 Der vorgegebene Konstruktor (default constructor)
Pfeil 5.5.4 Parametrisierte und überladene Konstruktoren
Pfeil 5.5.5 Copy-Konstruktor
Pfeil 5.5.6 Einen anderen Konstruktor der gleichen Klasse mit this(…) aufrufen
Pfeil 5.5.7 Ihr fehlt uns nicht – der Garbage-Collector
Pfeil 5.6 Klassen- und Objektinitialisierung *
Pfeil 5.6.1 Initialisierung von Objektvariablen
Pfeil 5.6.2 Statische Blöcke als Klasseninitialisierer
Pfeil 5.6.3 Initialisierung von Klassenvariablen
Pfeil 5.6.4 Eincompilierte Belegungen der Klassenvariablen
Pfeil 5.6.5 Exemplarinitialisierer (Instanzinitialisierer)
Pfeil 5.6.6 Finale Werte im Konstruktor und in statischen Blöcken setzen
Pfeil 5.7 Zum Weiterlesen
 

Zum Seitenanfang

5.5Objekte anlegen und zerstören Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn Objekte mit dem Schlüsselwort new angelegt werden, reserviert die Speicherverwaltung des Laufzeitsystems auf dem System-Heap Speicher. Wird das Objekt nicht mehr referenziert, so räumt die automatische Speicherbereinigung (Garbage-Collector) in bestimmten Abständen auf und gibt den Speicher an das Laufzeitsystem zurück.

 

Zum Seitenanfang

5.5.1Konstruktoren schreiben Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn das new ein Objekt anlegt, wird ein Konstruktor der Klasse automatisch aufgerufen. Mit einem eigenen Konstruktor lässt sich erreichen, dass ein Objekt nach seiner Erzeugung einen sinnvollen Anfangszustand aufweist. Dies kann bei Klassen, die Variablen beinhalten, notwendig sein, weil sie ohne vorherige Zuweisung bzw. Initialisierung keinen Sinn ergäben.

Konstruktordeklarationen

Konstruktordeklarationen sehen ähnlich wie Methodendeklarationen aus – so gibt es auch die bekannten Sichtbarkeiten, Parameterlisten und Überladung –, doch bestehen zwei deutliche Unterschiede:

  • Konstruktoren tragen immer denselben Namen wie die Klasse.

  • Konstruktordeklarationen besitzen keinen Rückgabetyp, also noch nicht einmal void.

[zB]Beispiel

Soll eine Klasse Dungeon einen Konstruktor bekommen, schreiben wir:

class Dungeon {

Dungeon() { } // Konstruktor der Klasse Dungeon

}

Ein Konstruktor, der keinen Parameter besitzt, nennt sich Standard-Konstruktor, parameterloser Konstruktor oder auch auf Englisch no-arg-constructor oder nullary constructor.

Aufrufreihenfolge

Dass der Konstruktor während der Initialisierung und damit vor einem äußeren Methodenaufruf aufgerufen wird, soll ein kleines Beispiel zeigen:

Listing 5.31Dungeon.java

class Dungeon {

Dungeon() {

System.out.println( "2. Konstruktor" );

}



void play() {

System.out.println( "4. Spielen" );

}



public static void main( String[] args ) {

System.out.println( "1. Vor dem Konstruktor" );

Dungeon d = new Dungeon();

System.out.println( "3. Nach dem Konstruktor" );

d.play();

}

}

Die Aufrufreihenfolge auf dem Bildschirm ist:

1. Vor dem Konstruktor

2. Konstruktor

3. Nach dem Konstruktor

4. Spielen

[»]Hinweis

UML kennt zwar Attribute und Operationen, aber keine Konstruktoren im Java-Sinne.

In einem UML-Diagramm werden Konstruktoren wie Operationen gekennzeichnet, die eben nur so heißen wie die Klasse.

Die Klasse »Dungeon« mit einem Konstruktor und zwei Methoden

Abbildung 5.15Die Klasse »Dungeon« mit einem Konstruktor und zwei Methoden

 

Zum Seitenanfang

5.5.2Verwandtschaft von Methode und Konstruktor Zur vorigen ÜberschriftZur nächsten Überschrift

Methoden und Konstruktoren besitzen beide Programmcode, haben eine Parameterliste, Modifizierer, können auf Objektvariablen zugreifen und this verwenden – das sind ihre Gemeinsamkeiten. Ein schon erwähnter Unterschied ist, dass Methoden einen Rückgabetyp besitzen (auch wenn er nur void ist), Konstruktoren aber nicht. Zwei weitere Unterschiede betreffen die Syntax und Semantik.

Konstruktoren tragen immer den Namen ihrer Klasse, und da Klassennamen per Konvention großgeschrieben werden, sind auch Konstruktoren immer großgeschrieben – Methoden werden in der Regel immer kleingeschrieben. Und Methoden sind in der Regel Verben, die das Objekt anweisen, etwas zu tun; Klassennamen sind Nomen und keine Verben.

Der Programmcode eines Konstruktors wird automatisch nach dem Erzeugen eines Objekts von der JVM genau einmal aufgerufen, und zwar als Erstes vor allen anderen Methoden. Methoden lassen sich beliebig oft aufrufen und unterliegen der Kontrolle des Benutzers. Konstruktoren lassen sich später nicht noch einmal auf einem schon existierenden Objekt erneut aufrufen und so ein Objekt reinitialisieren. Der Konstruktoraufruf ist implizit und automatisch mit new verbunden und kann nicht getrennt vom new gesehen werden.

Zusammenfassend können wir sagen, dass ein Konstruktor eine Art spezielle Methode zur Initialisierung eines Objektes ist.

JVM-Interna

Ein Java-Compiler setzt Konstruktoren als void-Methoden um, die »<init>« heißen.

 

Zum Seitenanfang

5.5.3Der vorgegebene Konstruktor (default constructor) Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn wir in unserer Klasse überhaupt keinen Konstruktor angeben, legt der Compiler automatisch einen an. Diesen Konstruktor nennt die Java Sprachdefinition (JLS) default constructor, was wir als vorgegebener Konstruktor (selten auch Vorgabekonstruktor) eindeutschen wollen. Schreiben wir

class Player { }

dann macht der Compiler daraus eine Version, die im Bytecode identisch ist mit:

class Player {

Player() { }

}

Der vorgegebene Konstruktor hat immer die gleiche Sichtbarkeit wie die Klasse. Ist die Klasse paketsichtbar, ist es auch der Konstruktor. Und setzen die Modifizierer public/private/protected[ 146 ](Nur innere Typen können private oder protected sein. ) die Typsichtbarkeit, wird auch der automatisch eingeführte Konstruktor public/private/protected sein.

Vorgegebener und expliziter Standard-Konstruktor

Ob ein parameterloser Konstruktor vom Compiler oder Entwickler angelegt wurde, ist ein Implementierungsdetail, das für Nutzer der Klasse irrelevant ist. Daher ist es im Grunde egal, ob wir einen Standard-Konstruktor selbst anlegen oder ob wir uns einen vorgegebenen Konstruktor vom Compiler generieren lassen: Im Bytecode lässt sich das nicht mehr unterscheiden. Selbst eine generierte Javadoc-API-Dokumentation von einer public class C1 {} und public class C2 { public C2(){} } wäre strukturell gleich.

Es gibt keinen Unterschied im Bytecode bezüglich der Konstruktoren in den Klassen C1 und C2.

Abbildung 5.16Es gibt keinen Unterschied im Bytecode bezüglich der Konstruktoren in den Klassen C1 und C2.

In der Begriffswelt der Insel heißt ein parameterloser Konstruktor immer Standard-Konstruktor, was natürlich den Unterschied verschwimmen lässt, ob der Standard-Konstruktor von Hand angelegt wurde oder als (impliziter) vorgegebener Konstruktor vom Compiler eingeführt wurde. Um das noch klarer zu unterscheiden, können wir diesen Umstand mit vorgegebener (Standard-)Konstruktor und expliziter Standard-Konstruktor weiter präzisieren.

Auch wenn der Compiler einen vorgegebenen Konstruktor anlegt, ist es oft sinnvoll, einen eigenen Standard-Konstruktor anzugeben, auch wenn der Rumpf leer ist. Ein Grund ist, ihn mit Javadoc zu dokumentieren, ein anderer Grund ist, die Sichtbarkeit explizit zu wählen, etwa wenn die Klasse public ist, aber der Konstruktor privat sein soll, dazu gleich mehr.

Begrifflichkeit I

In der Java Language Specification gibt es bei den Konstruktoren nur die Trennungen in no-arg-constructor (parameterloser Konstruktor) und default constructor (vorgegebener Konstruktor), aber den Begriff »standard constructor« gibt es nicht. Viele Autoren übersetzen die englische Bezeichnung »default constructor« (unseren vorgegebenen Konstruktor) einfach nur mit »Standard-Konstruktor«.

Begrifflichkeit II

Einige Autoren nennen nur den vom Entwickler explizit geschriebenen parameterlosen Konstruktor »Standard-Konstruktor« und trennen dies sprachlich von dem Konstruktor, den der Compiler generiert hat, den sie weiterhin »Default-Konstruktor« nennen. Beide werden dann zusammengefasst einfach »parameterlose Konstruktoren« genannt. Wenn also etwa die Frage gestellt wird, ob die Deklaration class C { } einen Standard-Konstruktor enthält, ist die Begrifflichkeit des Autors zu prüfen. Wenn der Autor nur den ausprogrammierten parameterlosen Konstruktor »Standard-Konstruktor« genannt hat, so hätte die Klasse C nach seiner Definition keinen »Standard-Konstruktor«. Nach der Insel-Definition hätte die Klasse zwar einen vorgegebenen (Standard-)Konstruktor, aber keinen expliziten Standard-Konstruktor.

Private Konstruktoren

Ein Konstruktor kann privat sein, was verhindert, dass von außen ein Exemplar dieser Klasse gebildet werden kann. Was auf den ersten Blick ziemlich beschränkt erscheint, erweist sich als ziemlich clever, wenn damit die Exemplarbildung bewusst verhindert werden soll. Sinnvoll ist das etwa bei den so genannten Utility-Klassen. Das sind Klassen, die nur statische Methoden besitzen, also Hilfsklassen sind. Beispiele für diese Hilfsklassen gibt es zur Genüge, zum Beispiel Math. Warum sollte es hier Exemplare geben? Für den Aufruf von max(…) ist das nicht nötig. Also wird die Bildung von Objekten erfolgreich mit einem privaten Konstruktor unterbunden.

 

Zum Seitenanfang

5.5.4Parametrisierte und überladene Konstruktoren Zur vorigen ÜberschriftZur nächsten Überschrift

Der Standard-Konstruktor hatte keine Parameter, und daher hatten wir ihn auch parameterlosen Konstruktor genannt. Ein Konstruktor kann aber wie eine Methode auch eine Parameterliste besitzen: Er heißt dann parametrisierter Konstruktor oder allgemeiner Konstruktor. Konstruktoren können wie Methoden überladen, also mit unterschiedlichen Parameterlisten deklariert sein. Dies soll auch für den Spieler gelten:

  • Player( String name )

  • Player( String name, String item )

Der Player soll sich mit einem Namen und alternativ auch mit einem Gegenstand initialisieren lassen:

Listing 5.32com/tutego/insel/game/v6/Player.java, Player

public class Player {



public String name;

public String item;



public Player( String name ) {

this.name = name;

}



public Player( String name, String item ) {

this.name = name;

this.item = item;

}

}
UML-Diagramm für Player mit zwei Konstruktoren

Abbildung 5.17UML-Diagramm für Player mit zwei Konstruktoren

Die Nutzung kann so aussehen:

Listing 5.33com/tutego/insel/game/v6/Playground.java, main()

Player spuderman = new Player( "Spuderman" );

System.out.println( spuderman.name ); // Spuderman

System.out.println( spuderman.item ); // null



Player holk = new Player( "Holk", "green color" );

System.out.println( holk.name ); // Holk

System.out.println( holk.item ); // green color

Die parametrisierten Konstruktoren verbinden sozusagen einen Standard-Konstruktor mit den Settern. Es spricht auch nichts dagegen, in diesen Konstruktoren an die Setter zum Setzen der Werte weiterzuleiten, denn so kann der Setter gleich validieren und die Aufgabe muss dann nicht noch der Konstruktor mit erledigen.

Wann der Compiler keinen vorgegebenen Konstruktor einfügt

Wenn es mindestens einen ausprogrammierten Konstruktor gibt, gibt der Compiler keinen eigenen Standard-Konstruktor mehr vor. Wenn wir also nur parametrisierte Konstruktoren haben – wie in unserem obigen Beispiel – führt der Versuch, bei unserer Spieler-Klasse ein Objekt einfach mit dem Standard-Konstruktor über new Player() zu erzeugen, zu einem Übersetzungsfehler, da es eben keinen vom Compiler generierten Standard-Konstruktor gibt:

Player p = new Player(); // inline The constructor Player() is undefined

Dass der Compiler keinen vorgegebenen Konstruktor anlegt, hat seinen guten Grund: Es ließe sich sonst ein Objekt anlegen, ohne dass vielleicht wichtige Variablen initialisiert worden wären. So ist das bei unserem Spieler. Die parametrisierten Konstruktoren erzwingen, dass beim Erzeugen ein Spielername angegeben wird, sodass nach dem Aufbau auf jeden Fall ein Spielername vorhanden ist. Wenn wir es ermöglichen wollen, dass Entwickler neben den parametrisierten Konstruktoren auch einen parameterlosen Standard-Konstruktor nutzen können, müssten wir diesen per Hand hinzufügen.

Wie ein nützlicher Konstruktor aussehen kann

Besitzt ein Objekt eine Reihe von Attributen, so wird ein Konstruktor in der Regel diese Attribute initialisieren wollen. Wenn wir eine Unmenge von Attributen in einer Klasse haben, sollten wir dann auch endlos viele Konstruktoren schreiben? Besitzt eine Klasse Attribute, die durch setXXX(…)-Methoden gesetzt und durch getXXX()-Methoden gelesen werden, so ist es nicht unbedingt nötig, dass diese Attribute im Konstruktor gesetzt werden. Ein Standard-Konstruktor, der das Objekt in einen Initialzustand setzt, ist angebracht; anschließend können die Zustände mit den Zugriffsmethoden verändert werden. Das sagt auch die JavaBean-Konvention. Praktisch sind sicherlich auch Konstruktoren, die die häufigsten Initialisierungsszenarien abdecken. Das Punkt-Objekt der Klasse java.awt.Point lässt sich mit dem Standard-Konstruktor erzeugen, aber auch mit einem parametrisierten, der gleich die Koordinatenwerte entgegennimmt; so sind vor dem ersten Zugriff alle Werte gegeben.

Wenn ein Objekt Attribute besitzt, die nicht über setXXX(…)-Methoden modifiziert werden können, diese Werte aber bei der Objekterzeugung wichtig sind, so bleibt uns nichts anderes übrig, als die Werte im Konstruktor einzufügen (eine setXXX(…)-Methode, die nur einmalig eine Schreiboperation zulässt, ist nicht wirklich schön). So arbeiten zum Beispiel unveränderbare (immutable) Werteobjekte, die einmal im Konstruktor einen Wert bekommen und ihn beibehalten. In der Java-Bibliothek gibt es eine Reihe solcher Klassen, die keinen Standard-Konstruktor besitzen, und nur einige parametrisierte, die Werte erwarten. Die im Konstruktor übergebenen Werte initialisieren das Objekt, und es behält diese Werte sein ganzes Leben lang. Zu den Klassen gehören zum Beispiel Integer, Double, Color, File oder Font.

 

Zum Seitenanfang

5.5.5Copy-Konstruktor Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Konstruktor ist außerordentlich praktisch, wenn er ein typgleiches Objekt über seinen Parameter entgegennimmt und aus diesem Objekt die Startwerte für seinen eigenen Zustand nimmt. Ein solcher Konstruktor heißt Copy-Konstruktor.

Dazu ein Beispiel: Die Klasse Player bekommt einen Konstruktor, der einen anderen Spieler als Parameter entgegennimmt. Auf diese Weise lässt sich ein schon initialisierter Spieler als Vorlage für die Attributwerte nutzen. Alle Eigenschaften des existierenden Spielers können so auf den neuen Spieler übertragen werden. Die Implementierung kann so aussehen:

Listing 5.34com/tutego/insel/game/v7/Player.java, Player

public class Player {



public String name;

public String item;



public Player() { }



public Player( Player player ) {

name = player.name;

item = player.item;

}

}
UML-Klassendiagramm für Player mit Standard- und parametrisierten Konstruktoren

Abbildung 5.18UML-Klassendiagramm für Player mit Standard- und parametrisierten Konstruktoren

Die statische main(…)-Methode soll jetzt einen neuen Spieler patric erzeugen und anschließend wiederum einen neuen Spieler tryk mit den Werten von patric initialisieren:

Listing 5.35com/tutego/insel/game/v7/Playground.java, main()

Player patric = new Player();

patric.name = "Patric Circle";

patric.item = "Knoten";



Player tryk = new Player( patric );

System.out.println( tryk.name ); // Patric Circle

System.out.println( tryk.item ); // Knoten

[»]Hinweis

Wenn die Klasse Player neben dem parametrisierten Konstruktor Player(Player) einen zweiten, Player(Object), deklarieren würde, käme es bei einer Verwendung durch new Player(patric) auf den ersten Blick zu einem Konflikt, denn beide Konstruktoren würden passen. Der Java-Compiler löst das so, dass er immer den spezifischsten Konstruktor aufruft, also Player(Player) und nicht Player(Object). Das gilt auch für new Player(null) – auch hier wird der Konstruktor Player(Player) bemüht. Während diese Frage für den Alltag nicht so bedeutend ist, müssen sich Kandidaten der Java-Zertifizierung Oracle Certified Java Programmer auf eine solche Frage einstellen. Im Übrigen gilt bei den Methoden das gleiche Prinzip.

 

Zum Seitenanfang

5.5.6Einen anderen Konstruktor der gleichen Klasse mit this(…) aufrufen Zur vorigen ÜberschriftZur nächsten Überschrift

Mitunter werden zwar verschiedene Konstruktoren angeboten, aber nur in einem Konstruktor verbirgt sich die tatsächliche Initialisierung des Objekts. Nehmen wir unser Beispiel mit dem Konstruktor, der einen Spieler als Vorlage über einen Parameter entgegennimmt, aber auch einen anderen Konstruktor, der den Namen und den Gegenstand direkt entgegennimmt:

Listing 5.36com/tutego/insel/game/v8/Player.java, main()

public class Player {



public String name;

public String item;



public Player( Player player ) {

name = player.name.trim();

item = player.item.trim();

}



public Player( String name, String item ) {

this.name = name.trim();

this.item = item.trim();

}

}

Zu erkennen ist, dass beide Konstruktoren die Objektvariablen initialisieren und zudem den Weißraum entfernen, also letztlich sehr ähnlich sind. Schlauer ist es, wenn der Konstruktor Player(Player) den Konstruktor Player(String, String) der eigenen Klasse aufruft. Dann muss nicht gleicher Programmcode für die Initialisierung und Weißraumentfernung mehrfach ausprogrammiert werden. Java lässt eine solche Konstruktorverkettung mit dem Schlüsselwort this zu:

Listing 5.37com/tutego/insel/game/v9/Player.java, Player

public class Player {



public String name;

public String item;



public Player() {

this( "", "" );

}



public Player( Player player ) {

this( player.name, player.item );

}



public Player( String name, String item ) {

this.name = name.trim();

this.item = item.trim();

}

}
Das Sequenzdiagramm mit dem Aufruf von zwei Konstruktoren

Abbildung 5.19Das Sequenzdiagramm mit dem Aufruf von zwei Konstruktoren

Der Gewinn gegenüber der vorherigen Lösung ist, dass es nur eine zentrale Stelle gibt, die im Fall von Änderungen angefasst werden müsste. An trim() lässt sich der Vorteil schon ablesen: Vorher war das Trimmen in jedem Konstruktor eingepflegt, nach der Änderung nur lokal an einer Stelle. Nehmen wir an, wir hätten zehn Konstruktoren für alle erdenklichen Fälle in genau diesem Stil implementiert. Tritt der Fall ein, dass wir auf einmal in jedem Konstruktor etwas initialisieren müssen, so muss der Programmcode – etwa ein Aufruf der Methode init(…) – in jeden der Konstruktoren eingefügt werden. Dieses Problem umgehen wir einfach, indem wir die Arbeit auf einen speziellen Konstruktor verschieben. Ändert sich nun das Programm in der Weise, dass beim Initialisieren überall zusätzlicher Programmcode ausgeführt werden muss, dann ändern wir eine Zeile in dem konkreten, von allen benutzten Konstruktor. Damit fällt für uns wenig Änderungsarbeit an – unter softwaretechnischen Gesichtspunkten ein großer Vorteil. Überall in den Java-Bibliotheken lässt sich diese Technik wiedererkennen.

[»]Hinweis

Das Schlüsselwort this ist in Java mit zwei Funktionen belegt: Zum einen »zeigt« es als Referenz auf das aktuelle Objekt, und zum anderen formt es einen Aufruf zu einem anderen Konstruktor der gleichen Klasse, wenn es mit Klammern genutzt wird.[ 147 ](Denksportaufgabe: Lässt sich irgendwie this.(Ausdruck) als Erstes im Konstruktor schreiben, um einen Pokal für die schlechteste Zeile Code zu gewinnen? ) Den Klassennamen als Methodenaufruf zu verwenden, also statt this(player.name, player.item) etwa Player(player.name, player.item) zu schreiben, funktioniert syntaktisch nicht, denn es könnte tatsächlich eine großgeschriebene Methode Player(…) geben, die jedoch mit dem Konstruktor nichts zu tun hat.

Einschränkungen von this(…) *

Beim Aufruf eines anderen Konstruktors mittels this(…) gibt es zwei wichtige Beschränkungen:

  • Der Aufruf von this(…) muss die erste Anweisung des Konstruktors sein.

  • Vor dem Aufruf von this(…) im Konstruktor können keine Objekteigenschaften angesprochen werden. Das heißt, es darf weder eine Objektvariable als Argument an this(…) übergeben werden noch darf eine andere Objektmethode der Klasse aufgerufen werden, die etwa das Argument berechnen möchte. Erlaubt ist nur der Zugriff auf statische Variablen (etwa finale Variablen, die Konstanten sind) oder der Aufruf von statischen Methoden.

Die erste Einschränkung besagt, dass das Erzeugen eines Objekts immer das Erste ist, was ein Konstruktor leisten muss. Nichts darf vor der Initialisierung ausgeführt werden. Die zweite Einschränkung hat damit zu tun, dass die Objektvariablen erst nach dem Aufruf von this(…) initialisiert werden, sodass ein Zugriff unsinnig wäre – die Werte wären im Allgemeinen null:

Listing 5.38Stereo.java

public class Stereo {



static final int STANDARD = 1000;

/*non-static*/ final int standard = 1000;

public int watt;



public Stereo() {

// this( standard ); // inline Führt auskommentiert zum Compilerfehler:

// ^ Cannot refer to an instance field standard while

// explicitly invoking a constructor



this( STANDARD );

}



public Stereo( int watt ) {

this.watt = watt;

}

}

Da Objektvariablen bis zu einem bestimmten Punkt noch nicht initialisiert sind (was der nächste Abschnitt erklärt), lässt uns der Compiler nicht darauf zugreifen – nur statische Variablen sind als Übergabeparameter erlaubt. Daher ist der Aufruf this(standard) nicht gültig, da standard eine Objektvariable ist; this(STANDARD) ist jedoch in Ordnung, weil STANDARD eine statische Variable ist.

 

Zum Seitenanfang

5.5.7Ihr fehlt uns nicht – der Garbage-Collector Zur vorigen ÜberschriftZur nächsten Überschrift

Glücklicherweise werden wir beim Programmieren von der lästigen Aufgabe befreit, Speicher von Objekten freizugeben. Wird ein Objekt nicht mehr referenziert, findet der Garbage-Collector[ 148 ](Eine lange Tradition hat die automatische Speicherbereinigung unter LISP und unter Smalltalk, aber auch Visual Basic benutzt einen GC, und selbst das C64-BASIC nutzte eine Garbage-Collection für nicht mehr benötigte Zeichenketten. ) dieses Objekt und kümmert sich um alles Weitere – der Entwicklungsprozess wird dadurch natürlich vereinfacht. Der Einsatz der automatischen Speicherbereinigung verhindert zwei große Probleme:

  • Ein Objekt kann gelöscht werden, aber die Referenz existiert noch (engl. dangling pointer).

  • Kein Zeiger verweist auf ein bestimmtes Objekt, dieses existiert aber noch im Speicher (engl. memory leak).

[»]Hinweis

Konstruktoren sind besondere Anweisungsblöcke, die die Laufzeitumgebung immer im Zuge der Objekterzeugung aufruft. Sprachen wie C++ kennen auch das Konzept eines Dekonstruktors, also eines besonderen Anweisungsblocks, der immer dann aufgerufen wird, wenn die Laufzeitumgebung erkennt, dass das Objekt nicht mehr benötigt wird. Allgemeine Dekonstruktoren kennt Java nicht. Es gibt jedoch mit der finalize()-Methode (sie wird in Abschnitt 9.1.7, »Aufräumen mit finalize() *«, vorgestellt) eine Möglichkeit, die an einen Dekonstruktor erinnert, allerdings mit einigen Einschränkungen. Zudem ruft try mit Ressourcen (vorgestellt in Abschnitt 7.6, »Automatisches Ressourcen-Management (try mit Ressourcen)«) eine close()-Methode auf, sodass sich auf diese Weise wieder Ressourcen freigeben lassen.

Prinzipielle Arbeitsweise des Müllaufsammlers

Die automatische Speicherbereinigung erscheint hier als ominöses Ding, das die Objekte clever verwaltet. Doch wie arbeitet ein Garbage-Collector? Implementiert wird er als unabhängiger Thread mit niedriger Priorität. Er verwaltet die Wurzelobjekte, von denen aus das gesamte Geflecht der lebendigen Objekte (der so genannte Objektgraph) erreicht werden kann. Dazu gehören die Wurzel des Thread-Gruppen-Baums und die lokalen Variablen aller aktiven Methodenaufrufe (Stack aller Threads). In regelmäßigen Abständen markiert der GC nicht benötigte Objekte und entfernt sie.

Dank der HotSpot-Technologie geschieht das Anlegen von Objekten unter der Java-VM von Oracle sehr schnell. HotSpot verwendet einen generationenorientierten GC, der den Umstand ausnutzt, dass zwei Gruppen von Objekten mit deutlich unterschiedlicher Lebensdauer existieren. Die meisten Objekte sterben sehr jung, die wenigen überlebenden Objekte werden hingegen sehr alt. Die Strategie dabei ist, dass Objekte im »Kindergarten« erzeugt werden, der sehr oft nach toten Objekten durchsucht wird und in der Größe beschränkt ist. Überlebende Objekte kommen nach einiger Zeit aus dem Kindergarten in eine andere Generation, die nur selten vom GC durchsucht wird. Damit folgt der GC der Philosophie von Auffenberg, der meinte: »Verbesserungen müssen zeitig glücken; im Sturm kann man nicht mehr die Segel flicken.« Das heißt, die automatische Speicherbereinigung arbeitet ununterbrochen und räumt auf. Sie beginnt nicht erst mit der Arbeit, wenn es zu spät und der Speicher schon voll ist.

Die manuelle Nullung und Speicherlecks

Im folgenden Szenario wird die automatische Speicherbereinigung das nicht mehr benötigte Objekt hinter der Referenzvariablen ref entfernen, wenn die Laufzeitumgebung den inneren Block verlässt:

{

{

StringBuilder ref = new StringBuilder();

}

// StringBuilder ref ist frei für den GC

}

In fremden Programmen sind mitunter Anweisungen wie die folgende zu lesen:

ref = null;

Oftmals sind sie unnötig, denn wie im Fall unseres Blocks weiß der GC, wann der letzte Verweis vom Objekt genommen wurde. Anders sieht das aus, wenn die Lebensdauer der Variablen größer ist, etwa bei einer Objekt- oder sogar bei einer statischen Variablen, oder wenn sie in einem Array referenziert wird. Wenn dann das referenzierte Objekt nicht mehr benötigt wird, sollte die Variable (oder der Array-Eintrag) mit null belegt werden, da andernfalls die automatische Speicherbereinigung das Objekt aufgrund der starken Referenzierung nicht wegräumen würde. Zwar findet die automatische Speicherbereinigung jedes nicht mehr referenzierte Objekt, aber die Fähigkeit zur Divination[ 149 ](Wahrsagen ), Speicherlecks durch unbenutzte, aber referenzierte Objekte aufzuspüren, hat er nicht.

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück

 

 


Copyright © Rheinwerk Verlag GmbH 2017

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de