zu Kapitel 2 zum Inhalt

3 Die Programmiersprache Java

3.1 Java als OOP-Umgebung

Java ist eine objektorientierte Sprache. Im Gegensatz zur prozeduralen Programmierung, wo versucht wird, ein komplexes Problem in immer kleinere Teilaufgaben zu zerlegen, um diese dann als Prozeduren zu implementieren, wird bei der objektorientierten Programmierung versucht, mit Hilfe von Objekten eine Abstraktion der Realität zu gewinnen.

3.1.1 Klassen und Objekte

Wenn wir uns umsehen, entdecken wir die verschiedensten Objekte wie Autos, Häuser oder Kaffeemaschinen. Sie alle besitzen ein Reihe von Eigenschaften und bieten uns auf der anderen Seite eine bestimmte Funktionalität. Genauso ist das mit Objekten in einem Programm. Deshalb wird beim Programmieren versucht die Realität durch Programmobjekte abzubilden, Objekte werden zu Klassen zusammengefaßt. Das hat u. a. den Vorteil das man bei umfangreichen Programmen den Überblick über die zahlreichen Objekte behält.
Eine Klasse definiert die gemeinsamen Eigenschaften und Funktionalitäten dieser Objekte. Ein Objekt ist dann eine Instanz einer Klasse, bzw. eine spezielle Ausprägung, und wird erst während der Abarbeitung erzeugt, und besitzt genau die beschriebenen Eigenschaften, die sich wiederum in Verhalten und Zustand gliedern.

Erzeugen von Objekten
Wie man ein Objekt erzeugt, zeigt das folgende Beispiel:
	Date today = new Date();
In diesem Beispiel wird eine Variable namens today mit dem Datentyp Date erzeugt. Durch new Date() wird ein neues Objekt initialisiert.
Nach dem oben aufgeführten Beispiel würden Zeichenketten und Felder folgendermaßen dargestellt:
	String x = new String ("Das ist eine Zeichenkette");
	int [] [] y = new int [ 5 ] [];
	for (int i = 0; i < 5; i++) y[i] = new int [i+1];
Die letzte Schleife erzeugt ein dreieckiges Feld.

Für Zeichenketten und Felder existiert jedoch eine besondere Variante der Objekterzeugung, nämlich die Angabe eines Literals. Das obige Beispiel abgewandelt:
	String x = "Das ist eine Zeichenkette";
	int[] [] y = {{1}1, {1,2}, {1,2,3}, {1,2,3,4}};
In diesem Beispiel ist zu erkennen, daß die Feldkomponenten von y schon mit Werten vorbelegt sind.

Löschen von unbenutzten Objekten
Viele objektorientierte Sprachen verlangen, daß man alle Objekte verfolgt, und diese dann löscht sobald sie nicht mehr benötigt werden. Ein Programm zu schreiben womit man Speicher verwalten möchte, ist auf diesem Weg doch recht ermüdend und fehlerträchtig. Java bewahrt uns davor, indem es erlaubt so viele Objekte zu erzeugen wie man möchte (nur begrenzt durch Systemeigenschaften), die man aber nicht löschen muß. Die Java-Laufzeitumgebung löscht die Objekte wenn es Sicher ist, daß diese nicht mehr länger benötigt werden. Dieser Vorgang ist bekannt als Garbage Collection (Müll-Sammlung). Ein Objekt wird dann als geeignet für die Garbage Collection bezeichnet, wenn zu dem Objekt keine Beziehungen mehr bestehen.

Erzeugen von Klassen
In Java deklariert der Programmierer Klassen mit dem Schlüsselwort class. Danach folgt der Name der Klasse die man definieren möchte. Der folgende Text deklariert eine neue Klasse mit dem Namen ImaginaryNumber:
	class ImaginaryNumber{
		                . . .
			     }
Deklarieren einer Superklasse
In Java hat jede Klasse eine Superklasse. Um eine Superklasse zu deklarieren ist das Schlüsselwort extends plus dem Namen der Superklasse erforderlich. Angewandt auf das Beispiel mit der Klasse ImaginaryNumber:
	class ImaginaryNumber extends Number {
          					. . .
					     }
Jetzt ist die Klasse Number die Superklasse von ImaginaryNumber (bzw. ImaginaryNumber die Subklasse von Number).
Wichtig zu erwähnen ist noch, daß die Klasse Number teil des java.lang-Paketes und die Basisklasse für Natürliche- (Integer), Fließkommazahlen (Float) und andere Werte ist.
Wenn keine Superklasse angegeben wird, ist die Superklasse automatisch die Objektklasse. Die Objektklasse ist die oberste Klasse im Hierarchie-Baum in der Java-Entwicklungsumgebung. Jede Klasse im Javasystem ist ein direkter oder indirekter Abkömmling von der Objektklasse. Die Objektklasse definiert den Basiszustand und das Verhalten, die alle Objekte haben müssen wie z. B. die Fähigkeit sich selbst mit einem anderen Objekt zu vergleichen, eine Zeichenkette zu konvertieren, auf eine Zustandsvariable zu warten, andere Objekte zu benachrichtigen wenn sich eine Zustandsvariable verändert hat.

Der Rumpf einer Klasse
Der Rumpf einer Klasse kann zwei verschiedene Bereiche enthalten: Variablendeklarationen und Methoden. Eine Variable der Klasse (Membervariable) repräsentiert den Zustand der Klasse, und seine Methode implementiert das Verhalten der Klasse. An einem Beispiel soll dies deutlich werden:
	class beispiel{  
		/*Membervariable*/
		static String text = "Dies ist ein Beispiel";
		/*Methode*/  
		static public void main (String args[]) {   
			System.out.println(text);  
		}
	}


3.1.2 Vererbung

Wenn eine Klasse nur wenige abstrakte Eigenschaften beschreibt, lassen sich ihr sehr viele Objekte zuordnen. Als Beispiel sei hier die Klasse Fahrzeug angegeben. In dieser Klasse befinden sich alle Fahrzeuge wie Zweirad, PKW und LKW. Die zunehmende Präzisierung der Eigenschaften führt dazu, daß man zu neuen Klassen kommt. Diese haben zwar die gleichen Grundeigenschaften, unterscheiden sich aber trotzdem in Bezug auf andere Eigenschaften. Eine hierarchische Struktur ist dann die Folge. Die untergeordneten Klassen übernehmen Eigenschaften und Funktionalität der übergeordneten Klasse. Untergeordnete Klassen werden als Unter- oder Subklassen, übergeordnete als Ober- oder Superklassen bezeichnet. Als Beispiel dient hier folgende Abbildung:
Grafik 1
Bild 3.1: Klassenhierachie

Der Vorteil dieses Konzepts ist, daß man beim Erstellen einer abgeleiteten Klasse nur die Unterschiede zu deren Superklasse spezifizieren muß. Außerdem können bei den Subklassen, zu den bereits von der Superklasse geerbeten, Variablen und Methoden hinzugefügt werden. Auch das Überschreiben (override) von geerbten Methoden ist möglich.
Zusammenfassend gesagt, mit dem Konzept der Vererbung ist man in der Lage, möglichst viel Programmcode wiederverwendbar zu machen, und damit die Programmierung insgesamt effizienter zu gestalten. In Java kennzeichnet das Schlüsselwort extends die Vererbungsbeziehung (siehe 3.1.1; Deklarieren einer Superklasse).

Mehrfachvererbung und Schnittstellen
Unter Mehrfachvererbung versteht man, daß eine Klasse von mehreren Klassen (Elternklassen) gleichzeitig erben kann. Die Mehrfachvererbung ist als solches in Java nicht vorhanden. In Java gibt es einen Ersatzmechanismus, die sogenannten Interfaces, welche als Zusammenfassung von Konstanten und Methodendeklarationen verstanden werden können. Die Schnittstellen entsprechen in etwa einer abstrakten Klasse. In einer Klasse können mehrere Schnittstellen implementiert werden, indem man ihren abstrakten Methoden eine Funktionalität zuweist. Somit regeln die Schnittstellen das Verhalten der Klassen nach außen. Dies ist besonders wichtig, um eine vereinheitlichte Sprache für alle Klassen zu finden, welche in einem Programm vorkommen. Sollten nun alle Klassen eines Programms eine bestimmte Schnittstelle implementieren, und deren Methodendeklaration mit Code ausfüllen, können die Daten sämtlicher Objekte aller Klassen mit dem gleichen Befehl angesprochen werden. In Java werden Schnittstellen mit dem Schlüsselwort interface deklariert und sie werden mit implements in anderen Klassen eingebunden.


3.1.3 Abstraktion, Kapselung, Polymorphismus

Abstraktion
Zur Bewältigung komplexer Sachverhalte wendet man das Prinzip der Abstraktion an. Durch Vernachlässigung von Details, das heißt dem Herausheben des Wesentlichen, schafft man ein überschaubares System, dessen grundlegende Merkmale und Funktionsweisen leichter verständlich sind.
  • Abstrakte Klassen
    Manchmal repräsentiert eine Klasse, die man definiert hat, ein abstraktes Konzept, und als solches kann es nicht instanziert werden. Als Beispiel dient hier die Nahrung. Eine Instanz der Nahrung gibt es nicht, jedoch eine Instanz der Karotten, Äpfel, Schokolade, usw. Nahrung repräsentiert das abstrakte Konzept von Dingen die man essen kann. Ähnlich ist es in der objektorientierten Programmierung, in der man ein abstraktes Konzept bilden möchte, jedoch nicht in der Lage ist eine Instanz von ihr zu erzeugen. Wie z.B. die NumberClass in dem java.lang Paket, die das abstrakte Konzept von Zahlen repräsentiert. Die NumberClass macht nur als Superklasse von Klassen wie Integer und Float sinn. Klassen, wie die NumberClass, welche abstrakte Konzepte implementiert, die nicht instanziert werden sollten, werden abstrakte Klassen genannt. Um eine Klasse in Java als abstrakte Klasse zu deklarieren wird das Schlüsselwort abstract benötigt:
    	abstract class Number  {
    				. . .   
    			       }
  • Abstrakte Methoden
    Eine abstrakte Klasse kann abstrakte Methoden enthalten. Abstrakte Methoden sind Methoden ohne Ausführung.

    Kapselung
    Wie bei der Vererbung dient das Konzept der Kapselung der Erstellung wiederverwendbaren Programmcodes. Kapselung bedeutet, daß nicht alle Bestandteile eines Objekts nach außen sichtbar sind. Vielmehr unterscheidet man zwischen der Schnittstelle eines Objekts (bzw. der Klasse) und deren Implementierung. Ziel ist es, alle, von anderen Objekten nicht benötigten Daten und Methoden einer Klasse, zu verbergen. Dieses Konzept wird auch "information hiding" genannt. Daraus ergeben sich zwei Vorteile:

  • Ein Programmierer, der die Klasse nicht implementiert hat, sie aber verwenden will, muß deren Implementierungsdetails nicht kennen und verstehen.

  • Der Compiler sorgt bei entsprechender Unterstützung dieses Konzepts dafür, daß ein versehentliches Manipulieren interner Daten nicht möglich ist.

    Die Definition der Klassen kann nun in zwei Bereiche gegliedert werden:
    Grafik 2
    Bild 3.2: Schnittstelle und Implementierung

    Im strengen Sinne darf die Schnittstelle eines Objekts nur Methoden enthalten. Daten dürfen dann nur durch das Zustellen von Nachrichten (dem Aufruf von Methoden) verändert werden. Java gibt dem Programmierer eine ganze Reihe von Modifizierern zur Hand, um eine saubere Trennung zwischen Schnittstelle und Implementierung verwirklichen zu können. Für die Definition von Variablen und Methoden stehen drei Modifizierer zur Verfügung, die den Zugriff auf diese Elemente regeln:

  • public:
    Elemente, die public deklariert sind, unterliegen keiner Zugriffsberechtigung. Überall dort, wo die Klasse bekannt ist, können diese Elemente angesprochen werden.

  • protected:
    Um auf ein protected-deklariertes Element zugreifen zu können, muß mindestens eine der folgenden Bedingungen erfüllt sein:
    	- der Zugriff erfolgt innerhalb einer Klasse, welche von der Klasse, die dieses 
    	  Element deklariert, abgeleitet ist, 
    	- die Definition der zugreifenden Klasse befindet sich im gleichen 
    	  Paket (package) wie die Klasse, die dieses Element beinhaltet.
  • private:
    Derart deklarierte Objekte unterliegen der strengsten Zugriffsbeschränkung. Nur Elemente der gleichen Klasse können auf ein private-deklariertes Objekt zugreifen.

    Diese drei Modifizierer schließen sich gegenseitig aus. Es kann je Element immer nur eine der drei Varianten gewählt werden. Ist ein Element weder public noch protected noch private deklariert, entspricht dies einer public-Deklaration.

    Polymorphismus
    Der Begriff Polymorphismus kommt aus dem Griechischen und bedeutet soviel wie "viele Formen haben". Als Polymorphismus bezeichnet man die Möglichkeit, mit derselben Nachricht verschiedene Aktionen auszulösen. Oder anders ausgedrückt, unterschiedliche Objekte können auf die gleiche Botschaft mit unterschiedlichen Methoden reagieren. Dieses Konzept korrespondiert direkt mit dem Konzept der Abstraktion. So kann die gleiche Nachricht bei verschiedenen Objekten unterschiedlich bearbeitet werden. Im Alltag verwenden wir gleiche Beziehungen für unterschiedliche Aktivitäten. Wir fahren mit dem Auto, aber wir fahren auch mit dem Fahrrad, obwohl die beiden Tätigkeiten ziemlich verschieden sind. Polymorphismus spielt bei der Vererbung eine große Rolle. Abgeleitete Klassen implementieren Methoden häufig anders als ihre Superklasse.


    3.1.4 Aufbau eines einfachen Java-Programms

    	/* HelloWorld.java */
    	class HelloWorld {   
    		static public void main (String args[]){   
    			System.out.println ("Hello World");
    		} 
    	}
    Bild 3.3: Programm "Hello World"

    An diesem Beispiel sind bereits die wichtigsten Bestandteile eines Java-Programms zu erkennen. Der eigentliche Programmcode besteht nur aus einer Klassendefinition. Zwar kann man in einer Datei auch mehrere Klassen definieren, es ist aber üblich, bei nicht trivialen Klassen, für jede Klassendefinition eine eigene Datei, auch Übersetzungseinheit genannt, anzulegen. Am Anfang der Datei steht immer ein Kommentar.
    Neben den Klassendefinitionen und Kommentaren kann eine Übersetzungseinheit noch weitere Elemente enthalten. Sie sollen hier nur der Vollständigkeit wegen erwähnt werden:
    	- Importdeklarationen teilen dem Compiler mit, welche anderen 
    	  Klassen noch benötigt werden,
    	- Interfacedefinitionen sind Klassendefinitionen sehr ähnlich,
    	  weisen aber einige Besonderheiten auf, 
    	- Package-Deklarationen dienen dazu, mehrere Übersetzungseinhei-
    	  ten zu einer Gruppe zusammenzufassen.
    In dem obigen Beispiel erkennt man als erstes Element einen Kommentar. Java kennt verschiedene Arten von Kommentaren, die sich auch in ihrer Syntax unterscheiden.

  • Kommentare, wie in der Programmiersprache C:
    Diese Kommentare beginnen mit den Zeichen /* und enden mit den Zeichen */. Zwischen Kommentarbeginn und -ende dürfen alle Zeichen mit Ausnahme von /* und */ auftreten. Sie können sich aber durchaus über mehrere Zeilen erstrecken.

  • Kommentare zur automatischen Dokumentationsgenerierung:
    Im Vergleich zu den eben erwähnten Kommentaren gibt es hier syntaktisch nur einen kleinen Unterschied. Anstelle der Zeichen /* dienen die Zeichen /** zur Definition des Kommentarbeginns. Derartige Kommentare stehen vor einer Deklaration und werden von einem Werkzeug zur automatischen Dokumentationsgenerierung verwendet.

  • einzeilige Kommentare wie sie in C++ verwendet werden:
    Analog zu C++ beginnen diese Kommentare mit den Zeichen // und enden mit der Zeile (Kommentarende ist das Zeichen 'Newline').

    Für alle drei Kommentararten gilt, daß sie nicht geschachtelt werden können und innerhalb von Stringkonstanten keine Bedeutung haben. Die einzelnen Konstrukte haben folgende Bedeutung:

  • class HelloWorld
    Implizit wird von der Superklasse aller Klassen Object eine Subklasse mit Namen HelloWorld erzeugt.

  • static public void main
    Die Methode ist nur einmalig (static) im System vorhanden. Die Methode steht zur Verfügung unabhängig davon, ob es auch Objekte zu dieser Klasse gibt. Demgegenüber sind virtuelle Methoden den einzelnen Instanzen zugeordnet. Mit public wird der Zugriff auch aus anderen Objekten auf die aktuelle Klasse freigeschaltet. Der Bezeichner void gibt wie bei C++/C an, daß für diese Methode kein Rückgabewert vorgesehen ist.

  • String args[]
    Anstelle über die Parameter argc und argv wie bei C und C++ werden die Argumente für das Programm über ein Feld von Zeichenketten übergeben.

  • System.out.println ("Hello World");
    Hinter System verbirgt sich die Klasse, die alle direkten Schnittstellen zur aktuellen Plattform verwaltet. Mit System.out wird der Standardausgabekanal (vergleichbar mit stdout) referenziert. Die Methode println wird für die Ausgabe von Zeichenketten mit abschließendem Zeilenvorschub verwendet.


    3.2 Java im Detail

    3.2.1 Variablen

    Variablendeklarationen halten sich an die C-Syntax und bestehen im wesentlichen aus der Typangabe und einem Bezeichner, gefolgt von einem Semikolon. Man kann innerhalb einer Deklaration mehrere Variablen des gleichen Typs anlegen und diese auch direkt mit einem Anfangswert initialisieren. Variablen, die bei der Deklaration nicht initialisiert worden sind, bekommen automatisch den Standardwert zugewiesen (z.B. bei ganzzahligen Datentypen den Wert `0`). Konstanten werden mit dem Schlüsselwort final deklariert.


    3.2.2 Typen

    Bei den Datentypen unterscheidet Java zwischen Standardtypen und Referenztypen. Standardtypen sind: byte(8-Bit), char(16-Bit), short(16-Bit), int(32-Bit), long(64-Bit), float(32-Bit), double(64-Bit), boolean, die fast alle (außer boolean) in C bekannt sind. Zeichen werden im Unicode(16-Bit) gespeichert und nicht im gewohnten ASCII-Code (8-Bit). Zu den Referenztypen gehören Klassen-, Schnittstellen- (werden später erklärt) und Feldtypen. Klassen und Schnittstellen werden in Java auch als Typen bezeichnet, wobei diese aus den bereitgestellten, oder aus eigenen Bibliotheken importiert werden können. In Java gibt es z.B. den Klassentyp String, mit dem Zeichenketten verwaltet werden.
    Feldtypen sind Felder und werden mit eckigen Klammern gekennzeichnet. Die Referenztypen kann man mit den Zeigern in C vergleichen. Eine Deklaration einer Referenzvariablen sorgt im Allgemeinen nicht dafür, daß für diese Variable ein entsprechendes Objekt oder Vektor angelegt wird. Siehe Abschnitt "Dynamische Speicherverwaltung".


    3.2.3 Operatoren

    Es folgt eine Auflistung der Operatoren in der Rangfolge, in welcher sie ausgewertet werden und mit den Datentypen, auf die sie angewendet werden können [5] [5]:

    Rang: Operator: Datentyp: Assoz. Beschreibung 1 ++ numerisch R unäre Pre- bzw. Postinkrementierung -- numerisch R unäre Pre- bzw Postinkrementierung + numerisch R unäres Plus (Vorzeichen positiv) - numerisch R unäres Minus (Vorzeichen negativ) ganzzahlig R unäres bitweises Komplement (NOT) ! boolean R unäres logisches Komplement (NOT) (Typ:) alle R casten in einen anderen (passenden) Typ 2 * numerisch L Multiplikation / numerisch L Division % ganzzahlig L Rest einer Division (Modulo) 3 + numerisch L Addition - numerisch L Subtraktion + String L Addition von Strings 4 << ganzzahlig L bitweise nach links schieben >> ganzzahlig L bitweise nach rechts schieben mit Vorzeichen >>> ganzzahlig L bitweise nach rechts schieben ohne Vorzeichen 5 < numerisch L kleiner als > numerisch L größer als <= numerisch L kleiner oder gleich >= numerisch L größer oder gleich instanceof Objekte L Typüberprüfung 6 = = einfach L gleicher Wert != einfach L ungleicher Wert = = Objekte L referenziert gleiches Objekt != Objekte L referenziert nicht das gleiche Objekt 7 & ganzzahlig L bitweises und (AND) & boolean L logisches und (OR) 8 ^ ganzzahlig L bitweises exklusives oder (XOR) ^ boolean L logisches exklusives oder (OR) 9 | ganzzahlig L bitweises oder (OR) | ganzzahlig L logisches oder (OR) 10 && boolean L logisches und (AND) 11 !! boolean L logisches oder (OR) 12 ?: boolean R bedingte Ausführung eines beliebigen Ausdrucks 13 = Variable R Zuweisung eines passenden Wertes *= numerisch R Zuweisung mit Multiplikation /= numerisch R Zuweisung mit Division += numerisch R Zuweisung mit Addition -= numerisch R Zuweisung mit Subtraktion <<= ganzzahlig R Zuweisung mit Linksverschiebung >>= ganzzahlig R Zuweisung mit Rechtsverschiebung >>>= ganzzahlig R Zuweisung mit Rechtsverschiebung (s. o.) &= ganzzahlig R Zuweisung mit bitweisem und (AND) &= boolean R Zuweisung mit logischem und (AND) ^= ganzzahlig R Zuweisung mit bitweisem exklusiven oder (XOR) ^= boolean R Zuweisung mit logischem exklusiven oder (XOR) != ganzzahlig R Zuweisung mit bitweisem oder (OR) != boolean R Zuweisung mit logischem oder (OR)
    Typkonvertierung: Der neue Typ wird in runden Klammern vor die Variable oder vor das Objekt geschrieben. Wichtig: boolean kann nicht umgewandelt werden; Referenztypen können umgewandelt werden, wenn der Ausgangstyp vom Zieltyp abgeleitet wurde; numerische Werte können umgewandelt werden.
    Standard-Operatoren: Mit dem Punkt-Operator (.) können Elemente einer Klasse angesprochen werden. Der linke Operand muß ein Referenztyp sein, der rechte ein Bezeichner. Der Zugriff auf ein Element eines Vektors wird mit [] formuliert. Der linke Operand muß eine Variable sein, der Ausdruck in den Klammern muß einen numerischen Wert ergeben.


    3.2.4 Kontrollstrukturen

    Java hat im Wesentlichen die gleichen Kontrollstrukturen wie C bzw. C++. Hier nur eine kurze Syntax-Auflistung:

    Blöcke:
    {} (Blöcke repräsentieren eine Anweisung; hier kann beliebiger Code untergebracht werden; Variablendeklarationen gelten nur lokal für diesen Block im Gegenstatz zu C++.)

    Mehrfachauswahl:
    switch (Ausdruck) { case Konstantenausdruck : Anweisung; ... ; default : Anweisung}

    while-, do-while-Schleife:
    while (Bedingung) Anweisung
    do Anweisung while (Ausdruck)

    Zählschleife:
    for (Initialisierung; Bedingung; Reinitialisierung) Anweisung

    Bedingte Anweisung:
    if (Bedingung) Anweisung1 [else Anweisung2]

    Sprunganweisungen:
    break bricht eine Schleife oder eine Mehrfachauswahl ab. Folgt hinter dem break ein Label, so wird zu dieser Stelle gesprungen.
    continue springt an das Ende der Schleife. Mit einem zusätzlichen Label wird an das Ende der mit dem Label benannten Schleife gesprungen.
    return verläßt die aktuelle Methode. Erwartet die Methode eine Rückgabe, so muß dieser Parameter dahinter folgen.



    3.3 Klassenkonzept

    3.3.1 Klassen

    Eine Klasse beschreibt die Eigenschaften gleichartiger Objekte, ein Objektmuster. Ein Objekt ist eine Instanz einer Klasse; eine spezielle Ausprägung, wird erst während der Abarbeitung erzeugt und besitzt genau die beschriebenen Eigenschaften. Diese Eigenschaften gliedern sich in Verhalten und Zustand. Daneben definiert eine Klasse auch Schritte, die zur Erzeugung und zur Beseitigung ihrer Objekte abgearbeitet werden müssen. Genauso wie den Objekten kann auch der Klasse ein Zustand und ein Verhalten zugeordnet werden. Objekte werden erzeugt und wieder gelöscht. Eine Klassendefinition besteht grob aus dem Schlüsselwort class, einem Bezeichner und dem Klassenrumpf, der innerhalb der geschweiften Klammern spezifiziert wird.


    3.3.2 Methoden

    Methoden sorgen für die Funktionalität einer Klasse und können deren Daten modifizieren. Die wichtigsten Bestandteile einer Methodendefinition sind wie bei gewöhnlichen Funktionen: der Rückgabetyp, der Bezeichner, die Parameterliste und der Rumpf. Der Methodenrumpf ist ein sogenannter Block. Die hier deklarierten Variablen gelten nur für diesen Bereich. Wichtige Modifizierer von Methoden sind:

    static: Eine static-Methode betrachtet man als zur Klasse gehörend. Sie ist unabhängig einer Instanz dieser Klasse. Diese Methoden können nur auf Variablen dieser Klasse zugreifen, die ebenfalls als static deklariert wurden.

    abstract: Wird eine Methode mit diesem Modifizierer versehen, darf sie keinen normalen Rumpf haben. Statt dessen muß auf die Parameterliste ein Semikolon folgen.


    3.3.3 Konstruktoren und Destruktoren

    Der Konstruktor ist die Methode, die beim Instanzieren automatisch aufgerufen wird und hat den gleichen Bezeichner wie die Klasse selbst. Diese spezielle Methode darf keinen Wert zurückgeben und darf nur mit public, protected und private modifiziert werden. Durch den Konstruktor werden wichtige Voreinstellungen vorgenommen, um das Objekt in einem gewünschten Zustand zu bringen.
    Das Gegenstück ist der Destruktor, der mit void finalize (); deklariert wird. Diese Methode wird automatisch beim Entfernen des Objektes aufgerufen und kann z.B. benötigte Ressourcen freigeben.


    3.3.4 Abgeleitete Klassen und Superklassen

    In Java kennzeichnet das Schlüsselwort extends die Vererbungsbeziehung (vergl. in C++ "::"). Dahinter folgt der Name der Superklasse (in Java werden die "Eltern" der vererbten Klassen Superklassen genannt). Durch die Vererbung werden alle Methoden und Variablendeklarationen auf die neue Klasse übertragen. In Java erben alle Klassen automatisch die Klasse Object, sie ist also die Superklasse aller Klassen.


    3.3.5 Abstrakte Klassen und Methoden

    Der Modifizierer abstract kann sowohl auf Klassen-, als auch auf Methodendeklarationen angewandt werden. Nur Klassen, die abstract deklariert wurden, dürfen auch abstrakte Methoden enthalten. Eine abstrakte Klasse kann niemals instanziert werden, denn sie dient immer nur als Superklasse für andere Klassen. Davon abgeleitete Klassen können nur dann instanziert werden, wenn alle abstrakten Methoden implementiert worden sind. Abstrakte Methoden haben keinen Rumpf, denn sie werden später erst von abgeleiteten Klassen implementiert.


    3.3.6 `this` und `super`

    Die beiden Bezeichner this und super referenzieren Objekte im Körper einer Objektmethode, nur dort dürfen sie verwendet werden. Der Bezeichner this hat als Typ die Klasse des Objekts, super hat den Typ der Superklasse.


    3.3.7 Statische Initialisierungsblöcke

    Um statische Variablen beim Anlegen eines Objektes automatisch zu initialisieren, wird ein statischer Block innerhalb der Klasse angelegt. Innerhalb dieses Blocks kann nur auf statische Methoden und Variablen zugegriffen werden. Syntax: static {...}



    3.4 Interfaces, Packages und Importanweisungen

    3.4.1 Interfaces (Schnittstellen)

    Ein Interface ist grob eine abstrakte Klasse, welche nur abstrakte Methoden enthält. Da in Java Klassen nicht direkt von mehreren Klassen gleichzeitig erben können (im Gegensatz zu C++), gibt es die Möglichkeit mehrere Interfaces zu erben. Beispiel einer solchen Vererbung:
    	class C extends A implements B;
    Dies bedeutet, daß die Klasse C die Klassen A und B erbt, wobei Klasse B ein Interface ist, das später noch implementiert werden muß. Eine Interface-Deklaration beginnt allgemein mit dem Schlüsselwort interface gefolgt von dem Bezeichner der Schnittstelle. Dahinter kann mit extends die Schnittstelle erweitert werden.


    3.4.2 Packages

    Sie dienen zur Kapselung und Sammlung von Klassen. Da Java-Programmteile in Netzwerken weit verteilt sein können, benutzt man in Java für diese Packages Internet-Domainnames, um weltweiten Zugriff, ohne Konflikte bei der Bezeichnung, zu ermöglichen. Für die Bezeichnung wird der Domainname in der Reihenfolge umgedreht, sowie globale Bezeichner groß und lokale Bezeichner klein geschrieben (Bsp.: aus sunsoft.sun.com wird com.sun.sunsoft). Um ein Package auf einem Rechner zu finden, müssen nur die Punkte unter Unix durch /, auf dem PC durch \ ersetzt werden. Es sollte eine Umgebungsvariable CLASSPATH angelegt werden, um dem Compiler mitzuteilen, wo sich mögliche Packages befinden. Soll eine Quelldatei zu einem bestimmten Package gehören, wird am Anfang der Datei das Schlüsselwort package mit der Bezeichnung angegeben. Die Klassenbibliothek umfaßt neun Pakete:
    java.applet für die Implementierung von Applets und für die Soundverarbeitung,
    java.awt ist für die Gestaltung graphischer Benutzeroberflächen,
    java.awt.image enthält Klassen für die Bildverarbeitung,
    java.awt.peer ist für die Abbildung des AWT auf das Wirtfenstersystem,
    java.io beinhaltet ein umfangreiches Angebot von Ein- und Ausgaberoutinen,
    java.lang `Hauptteil` der Sprache (z.B. Klassen: String, Thread, ... ),
    java.net beinhaltet Netzwerkroutinen, um z.B. Interaktionen im WWW zu tätigen,
    java.util enthält wichtige Datenstrukturen (Hashtable, Stack) und z.B. Zufallsroutinen,
    sun.tools.debug für die Inspektion von Programmen.


    3.4.3 Imports

    Java benutzt Importanweisungen, um zusätzlichen Code aus Packages einzubinden. Durch den Schlüsselbegriff import und einem Package-Bezeichner erkennt der Compiler, wo Definitionen verwendeter Klassentypen zu finden sind. Als Erweiterung kann auch ein Typbezeichner getrennt durch einen Punkt folgen, damit auch einzelne Referenztypen eingebunden werden können. Wird als Typbezeichner ein `*` angegeben, so durchsucht der Compiler bei Bedarf dieses Package nach Definitionen (import on demand).


    3.5 Dynamische Speicherverwaltung

    Variablen und instanzierte Klassen bekommen bei direkter Initialisierung automatisch Speicher zugeordnet (Bsp.: int a=5; oder int steps[]={1,2,3,4};). Anderenfalls muß durch das Schlüsselwort new Speicher für das Objekt angefordert werden (Bsp.: int[] feld; später: feld = new int[3];). Bei einer solchen Allokierung folgt nach dem new ein Typname und wenn der Konstruktor des Typs Übergabeparameter erwartet, müssen diese in Klammern als Liste in der richtigen Reihenfolge übergeben werden. Bei Vektoren gibt es allerdings keine Parameterliste, denn hier wird an dieser Stelle eine Dimensionsangabe verlangt. Die Anzahl der anzulegenden Felder steht in eckigen Klammern. Hier ein Beispiel für das Erstellen eines zweidimensionalen 3x4 Feldes vom Typ int: int[][] xy=new int[3][4];



    3.6 Threads

    Java unterstützt parallel ablaufende Steuerflüsse innerhalb eines Programms (Nebenläufigkeit). Für die Parallelität sorgt der Java-Scheduler, der jedem Prozeß eine bestimmte Zeit zuordnet. Der Scheduler läßt einen aktiven Thread so lange laufen, bis er entweder selber die Kontrolle abgibt, bis er bei einer Ein- oder Ausgabe blockiert, bis er auf die Freigabe einer Ressource wartet oder bis ein Thread mit höherer Priorität abgearbeitet werden soll.
    Die erste mögliche Realisierung der Nebenläufigkeit ist die Benutzung der Klasse thread. Dabei geht man wie folgt vor. Zuerst wird eine Klasse definiert, die von thread abgeleitet ist. Diese Klasse besitzt jetzt eine Methode void run(), die nur noch mit eigenem Code überschrieben werden muß. Im nächsten Schritt wird die Klasse instanziert. Es muß noch die Methode void start() aufgerufen werden, und der Thread ist gestartet. Er läuft solange, bis die run-Methode abbricht, oder die Methode void stop() aufgerufen wird. Die Klasse thread besitzt natürlich noch weitere Methoden, die aber hier nicht weiter aufgeführt werden.
    Vor allem für Animationen von Applets wird eine anderes Verfahren für Nebenläufigkeit benutzt. Die Klasse Applet unterstützt selbst ein Interface runnable, wobei die Methoden start, run und stop implementiert werden müssen.
    Für Kenner sei noch erwähnt, daß Java-Threads meist auf die Threads des Betriebssystems projiziert werden. Aus diesem Grund sind Java-Anwendungen MP-Hot, d. h. sie werden auf Multi-Prozessor-Computern schneller ausgeführt.



    3.7 Synchronisation

    Nebenläufige Prozesse sind nicht immer leicht zu verwalten, vor allem, wenn mehrere Threads die gleichen Daten und Ressourcen benutzen; dann müssen sie ihren Ablauf aufeinander abstimmen bzw. synchronisieren. Hierfür gibt es in Java das Schlüsselwort synchronized, daß vor eine bestimmte Methode gesetzt werden kann, um an dieser Stelle dafür zu sorgen, daß diese Methode nicht gleichzeitig abgearbeitet wird. So kann die Änderung von Daten nicht von anderen Threads gestört werden.



    3.8 `Daemonen`

    Daemonen sind Threads, die für andere Threads Dienste leisten. Ein Daemon ist natürlich überflüssig, wenn kein anderer Thread mehr vorhanden ist. Deshalb sollte er automatisch aus dem Speicher entfernt werden. Dies passiert aber nur dann automatisch, wenn ein Thread als Daemon bezeichnet wurde (durch die Methode setDaemon(true)).



    3.9 Ausnahmebehandlung

    Oft können während der Ausführung Fehler entstehen, die in irgendeiner Art abgefangen werden müssen. Es sind dabei nicht die Programmierfehler gemeint. Typische Fehler sind Ein-, Ausgabefehler oder Übertragungsfehler in Netzwerken, die jederzeit auftreten können und mit speziellen Routinen bearbeitet werden müssen. In Java gibt es für diesen Zweck Exceptions (deutsch: Ausnahmen), die folgende Vorteile haben:
    - Normaler Programmcode und Fehlerbehandlung lassen sich in separate Teile gliedern,
    - Beim Auftreten eines Fehlers kann eine Methode genauere Informationen über den Fehler übermitteln,
    - Aufräumarbeiten lassen sich einfacher organisieren.

    An einem kleinen Beispiel läßt sich die Ausnahmebehandlung am besten erklären:
    	static int teile(int x, int y) throws ArithmeticException {
    		if(y==0)
    		throw(new ArithmeticException("Division durch 0"));     
    		else return x/y;
    	}
    
    	public static void main(String[] arg){     
    		int x=1, y=0;
    		try {        
    			x=teile(x, y);     
    		}     
    		catch(ArithmeticExceptione){        
    			System.out.println("Ausnahme bei der Division"
                   		+e.getMessage());     
    		}
    	}
    Bild 3.4: Ausnahmebehandlung

    Die Methode main soll eine Division ausführen und den Fehler "Division durch Null" durch eine Fehlermeldung abfangen. Zuerst wird x mit 1 und y mit 0 initialisiert. Dann folgt ein sogenannter try-Block, der angibt, wo ein Fehler auftreten kann. Hier steht die Methode "teile(x,y)". Die Methode wird aufgerufen und erkennt, das y=0 ist und ruft die throw-Methode auf, die jeder Klasse zur Verfügung steht. Sie sendet eine mit new erzeugte Fehlermeldung an eine untere Ebene, wo sie von einem catch-Block abgefangen werden kann. Alle bekannten Fehlerbezeichnungen befinden sich in dem Package java.lang. Am Ende erscheint mit println und mit Hilfe von getMessage() die genaue Meldung auf dem Bildschirm. Dieses kleine Beispiel erklärt zwar nicht alle Probleme der Ausnahmebehandlung, es zeigt aber schon das Prinzip.

    zu Kapitel 4 Kapitelanfang zum Inhalt