9.2 Java
Die Programmiersprache Java wurde 1995 von dem bekannten Server- und Workstation-Hersteller Sun Microsystems vorgestellt. Zu den wichtigsten Entwicklern des Projekts gehören James Gosling und Bill Joy. Java hat vor allen Dingen die Besonderheit, dass der kompilierte Programmcode auf verschiedenen Rechnern und Betriebssystemen ausgeführt werden kann. Für diese Systeme ist lediglich Java-Unterstützung in Form einer virtuellen Maschine (JVM – Java Virtual Machine) erforderlich. Virtuelle Java-Maschinen sind für zahlreiche verschiedene Plattformen verfügbar, unter anderem für Windows, Linux, Mac OS X und alle anderen Unix-Varianten. Dieser Ansatz wird von Sun als Write once, run everywhere bezeichnet.
Im Jahr 2009 wurde Sun vom Datenbankkonzern Oracle übernommen. Die Bedeutung dieses Ereignisses für die Zukunft von Java ist unklar, aber da auch Oracle seit Jahren massiv in Java-Technologien investiert, dürfte diese Zukunft gewährleistet sein.
Anfangs wurde Java vor allen Dingen eingesetzt, um sogenannte Applets zu schreiben. Dabei handelt es sich um kleine Java-Programme, die in einem Webbrowser ausgeführt werden, der eine JVM enthält. Da für Multimedia-Angebote im Web inzwischen erheblich bessere Lösungen wie beispielsweise Flash verfügbar sind, hat sich der Schwerpunkt der Java-Verwendung auf andere Bereiche verlagert: Java-Anwendungen werden im professionellen Serverbereich eingesetzt, weil sie aufgrund ihrer Plattformunabhängigkeit mit den verschiedensten Systemen kooperieren können; auch zur Entwicklung von Desktopanwendungen wie Office- oder Grafikprogrammen wird Java gern genutzt.
Wenn Sie Java-Programme schreiben möchten, brauchen Sie das Java Software Development Kit (Java SDK oder kurz JDK), das Sie unter http://www.oracle.com/technetwork/java/index.html für mehrere Plattformen herunterladen können. Falls Ihr System dort nicht zu finden ist, hilft in der Regel eine Web-Suchmaschine wie etwa Google weiter.
Das Java SDK wird in drei verschiedenen Varianten angeboten:
- Die Standard Edition (Java SE), auf die sich dieser Abschnitt bezieht, ist für sämtliche Desktopanwendungen geeignet. Die aktuelle Version ist 7 Update 25.
- Die Enterprise Edition (Java EE) enthält Unterstützung für die Entwicklung verteilter, datenbankgestützter Serveranwendungen; einzelne ihrer Komponenten werden kurz im nächsten Kapitel sowie in Kapitel 12, »Datenbanken«, vorgestellt.
- Die Micro Edition (Java ME) dient der Entwicklung von Anwendungen für mobile Geräte wie Smartphones oder PDAs. Insbesondere Android-Apps werden vorwiegend in Java geschrieben, allerdings in der Regel mit Googles eigenem Android SDK.
Die Installation des JDK SE ist sehr einfach: Sie laden einfach ein für Ihre Plattform geeignetes Archiv oder ausführbares Programm herunter, das sich in den meisten Fällen per Doppelklick installieren lässt. Das einzige kleine Problem besteht darin, dass Sie zwei Umgebungsvariablen setzen beziehungsweise anpassen müssen, um mit Java arbeiten zu können. Wie Systemvariablen unter verschiedenen Betriebssystemen manipuliert werden, haben Sie bereits in Kapitel 6, »Windows«, und Kapitel 7, »Linux«, erfahren.
Um den Java-Compiler und die JVM von überall her aufrufen zu können, müssen Sie das Unterverzeichnis bin Ihrer Java-Installation zur Umgebungsvariablen PATH hinzufügen. Außerdem müssen Sie eine weitere Systemvariable namens CLASSPATH einrichten, die auf die Java-Klassenbibliothek verweist – in der Regel das Verzeichnis lib innerhalb der Java-Installation. Zu CLASSPATH werden, durch Semikolon getrennt, auch andere Verzeichnisse (oder Archivdateien wie .zip oder .jar) hinzugefügt, in denen weitere Java-Klassen enthalten sind. Es empfiehlt sich, auch . als Synonym für das aktuelle Arbeitsverzeichnis hinzuzufügen, damit auch Ihre eigenen Java-Programme überall gefunden werden.
Der wichtigste Unterschied zwischen Java und C besteht darin, dass Java objektorientiert ist, während C zu den prozeduralen oder imperativen Programmiersprachen zählt (zur Systematik siehe Kapitel 1, »Einführung«). Das objektorientierte Programmieren (OOP) ist ein modernerer Ansatz der Softwareentwicklung. Während in imperativen Sprachen wie C zunächst eine Datenstruktur entwickelt wird, die mit den Programmfunktionen nicht näher verbunden ist, bilden Funktionen und Datenstrukturen bei der OOP eine untrennbare Einheit.
Ein Java-Programm ist immer eine Klassendefinition. Eine Klasse ist das übergeordnete Bauelement der Objektorientierung. Sie besteht aus einer Reihe von Variablen, die als Eigenschaften bezeichnet werden, und aus mehreren Funktionen, die Methoden genannt werden. Durch die Kapselung der Eigenschaften und Methoden zu Klassen lassen sich die verschiedenen Bestandteile der realen Welt oder der zu verwaltenden Daten realitätsnäher nachbilden: Beispielsweise müsste die imperative Nachbildung eines Autos durch externe Funktionen zum »Fahren« (Änderung von Daten wie Tankfüllung oder Kilometerstand) gebracht werden. Ein »objektorientiertes Auto« enthält dagegen einfach eine entsprechende Methode, die aufgerufen wird, sodass das Auto »selbst fährt«.
Die Klassendefinition selbst ist lediglich eine Vorlage für die Erzeugung konkreter Objekte. Es können beliebig viele Objekte einer Klasse erzeugt werden; jedes dieser Objekte besitzt alle Eigenschaften, die in der Klasse definiert wurden. Objekte werden auch als Instanzen einer Klasse bezeichnet.
Einer der wichtigsten Vorteile der Kapselung besteht darin, dass Detaildaten immer nur an einer Stelle verwaltet und verändert werden, was die Häufigkeit von Fehlern erheblich verringert. Außerhalb einer bestimmten Klasse dürfen die Werte von Daten, die Sie »nichts angehen«, nicht manipuliert werden. Um bei dem Auto-Beispiel zu bleiben, sollte beispielsweise der Kilometerstand nur durch die offizielle Methode »Fahren« geändert werden und nicht durch eine direkte Wertzuweisung.
Seit ihrer Entwicklung in den 70er-Jahren des vorigen Jahrhunderts wurde die OOP in vielen verschiedenen Programmiersprachen realisiert. Eine der ersten von ihnen war Smalltalk, die das Konzept erstmalig bekannt machte. Die verbreitetsten objektorientierten Sprachen sind heute C++, eine objektorientierte Erweiterung von C, Java und C# (»C sharp«) von Microsoft. Da C++ nicht konsequent objektorientiert ist, um die Kompatibilität mit C aufrechtzuerhalten, wird an dieser Stelle die Sprache Java besprochen, die sich zwar an C++ anlehnt, aber sämtliches imperative Erbe von C weglässt. C# ist laut Angabe von Microsoft ebenfalls eine Weiterentwicklung von C++, erinnert aber in vielen Aspekten eher an Java.
9.2.1 Grundlegende Elemente der Sprache Java
Um einen Einstieg in die Java-Programmierung zu finden, sehen Sie hier gleich das erste Beispielprogramm. Es handelt sich dabei um die Java-Entsprechung des ersten C-Beispielprogramms, was die Unterschiede deutlich macht. Geben Sie das Programm in Ihren bevorzugten Texteditor ein, und speichern Sie es unter Hallo.java. Eine Java-Quellcodedatei muss so heißen wie die Klasse, die darin definiert wird, und die Endung .java aufweisen. Dabei müssen Sie die Groß- und Kleinschreibung des Klassennamens genau übernehmen; üblicherweise beginnen Klassennamen mit einem Großbuchstaben. Hier das Listing:
import java.io.*;
public class Hallo {
public static void main (String args[]) {
BufferedReader eingabe = new BufferedReader (
new InputStreamReader (System.in));
String name = "";
System.out.println ("Hallo Welt!");
System.out.print ("Ihr Name, bitte: ");
try {
name = eingabe.readLine();
}
catch (IOException e) {}
System.out.println ("Hallo " + name + "!");
}
}
Wenn Sie zuvor Ihre path- und CLASSPATH-Einstellungen korrekt vorgenommen haben, können Sie das Programm nun folgendermaßen kompilieren, sofern Sie sich im entsprechenden Verzeichnis befinden:
$ javac Hallo.java
Falls Sie keine Fehlermeldung erhalten, finden Sie im aktuellen Verzeichnis die fertig kompilierte Datei Hallo.class vor. Sie können das Programm anschließend folgendermaßen starten:
$ java Hallo
Das ausführbare Programm java aktiviert die JVM. Als Kommandozeilenargument wird der Name einer kompilierten Java-Klasse ohne Dateiendung angegeben.
Wie Sie sehen, ist dieses Programm erheblich komplexer als die C-Version, obwohl es dieselbe Aufgabe erfüllt. Im Folgenden wird das Programm zeilenweise erläutert:
- import java.io.*;
Mithilfe der Anweisung import werden bestimmte Teile der Java-Klassenbibliothek importiert. Der Vorgang ist vergleichbar mit dem Einbinden von Header-Dateien mithilfe von #include. Allerdings ist import eine normale Java-Anweisung; einen Präprozessor gibt es nicht. Außerdem unterscheiden sich die importierten Klassen formal nicht von Ihren eigenen Programmen. Eine Aufteilung in Header- und Programmdatei ist in Java ebenfalls nicht vorgesehen.java.io.* bezeichnet alle Klassen, die im Verzeichnis io der grundlegenden Klassenbibliothek liegen. Es handelt sich um eine Sammlung von Klassen für die Ein- und Ausgabe. Solche Bestandteile der Klassenbibliothek werden als Packages bezeichnet.
- public class Hallo
Mit dem Schlüsselwort class wird eine Klassendefinition eingeleitet. Mithilfe von public wird die definierte Klasse für sämtliche anderen Klassen, das heißt für alle Programme, zugänglich. - public static void main (String args[])
Wie in C steht main() für das Hauptprogramm. Allerdings gibt es in einem Java-Programm einige wichtige formale Unterschiede: Da die JVM diese Methode von außen aufrufen muss, wird das Schlüsselwort public benötigt. Alle Bestandteile einer Klasse, die nicht public sind, stehen außerhalb der Klasse selbst nicht zur Verfügung. static wird benötigt, weil von dieser Klasse kein konkretes Objekt abgeleitet wird. Eine Klasse ist eigentlich nur eine Art Bauanleitung für Objekte; ihre static-Bestandteile können aber ohne Existenz eines konkreten Objekts verwendet werden. In der Regel hat main() in Java den Datentyp void. Das Array args[] nimmt Kommandozeilenargumente entgegen. - BufferedReader eingabe = new BufferedReader
(new InputStreamReader (System.in));
Diese verschachtelte Operation sorgt für ein Objekt namens eingabe, das zeilenweise von der Standardeingabe lesen kann. Im Kern von Java ist leider keine Möglichkeit gegeben, ganze Zeilen einzulesen. Deshalb wird die Standardeingabe – repräsentiert durch System.in – in ein Objekt vom Typ InputStreamReader (Lesevorrichtung für kontinuierliche Datenströme) verpackt, das wiederum von einem BufferedReader (zuständig für die Zwischenspeicherung von Eingabedaten) umhüllt wird. Solche verschachtelten Objektkonstruktionen machen Java relativ kompliziert, aber durch die große Auswahl spezialisierter Klassen auch sehr flexibel. - String name = "";
Im Gegensatz zu C stellt Java einen echten String-Datentyp zur Verfügung, der als Klasse realisiert ist. Diese Anweisung erzeugt eine String-Variable mit der Bezeichnung name; ihr wird als Anfangswert der leere String zugewiesen, weil es beim späteren Eingabeversuch passieren könnte, dass sie gar keinen Wert erhält. - System.out.println ("Hallo Welt!");
Die Methode println() der Standardausgabe (System.out) gibt einen übergebenen String mit anschließendem Zeilenumbruch aus. - System.out.print ("Ihr Name, bitte: ");
Die Methode print() gibt dagegen einen String ohne Zeilenumbruch aus. - try {...}
In einen try-Block werden Anweisungen immer dann geschrieben, wenn sie einen möglichen Fehler produzieren könnten. Laufzeitfehler werden in Java als sogenannte Ausnahmen (Exceptions) betrachtet, die mithilfe von try/catch (siehe unten) abgefangen und sinnvoll behandelt werden können. - name = eingabe.readLine();
Die Methode readLine() der Klasse BufferedReader liest eine Zeile aus einem Eingabestrom, in diesem Fall von der Standardeingabe. - catch (IOException e) {}
Mithilfe von catch() kann eine Ausnahme abgefangen werden, die innerhalb des vorangegangenen try-Blocks ausgelöst wurde. In diesem Fall handelt es sich um eine IOException, also einen Ein-/Ausgabefehler. In den geschweiften Klammern kann Code für spezielle Maßnahmen zur Fehlerbehandlung stehen. Die wichtigste Aufgabe von try/catch besteht darin, den sofortigen Programmabbruch bei einem Fehler zu verhindern. - System.out.println ("Hallo " + name + "!");
Das Besondere an dieser Ausgabeanweisung ist die Verkettung mehrerer Strings durch den Operator +.
Unterschiede zu C
Erfreulicherweise müssen viele grundlegende Konzepte von Java nicht mehr erläutert werden, weil sie mit C übereinstimmen. Beispielsweise sind die einfachen Datentypen, Ausdrücke, Operatoren und Kontrollstrukturen nahezu identisch, deshalb werden an dieser Stelle nur die wesentlichen Unterschiede aufgezählt. Hier ist nicht die Tatsache gemeint, dass Java objektorientiert ist, sondern es geht nur um die Unterschiede bei vergleichbaren Elementen.
- Im Gegensatz zu C haben die Integer-Datentypen in Java eine festgelegte Bit-Breite: int ist 32 Bit groß, short 16 Bit, long 64 Bit.
- Java definiert einen separaten Datentyp für 8-Bit-Integer namens byte; char wird dagegen nur zur Darstellung eines einzelnen Zeichens verwendet und besitzt eine Breite von 16 Bit, um die wichtigsten Unicode-Zeichen darzustellen.
- Java kennt einen Datentyp für boolesche Wahrheitswerte: boolean. Er kann nur die vorgegebenen Wahrheitswerte true oder false annehmen. Sämtliche Bedingungsprüfungen für Kontrollstrukturen müssen in Java einen boolean-Wert haben. Wenn Sie also beispielsweise prüfen möchten, ob die Variable a den Wert 0 hat, müssen Sie den Vergleich a == 0 explizit hinschreiben! Die typische C-Kurzschreibweise !a ist nicht zulässig. Aus diesem Grund wird Java als stark typisierte Sprache bezeichnet, weil sehr streng über die Einhaltung der korrekten Datentypen gewacht wird.
- Die Syntax für die Deklaration eines Arrays mit einer festgelegten Anzahl von Elementen
unterscheidet sich von C: Es erfolgt ein Aufruf der Objekterzeugungsmethode new, gefolgt vom Datentyp und der Elementanzahl:
int werte[] = new int[20];
- Variablen können in Java an einer beliebigen Stelle deklariert werden. Beachten Sie
allerdings, dass eine Variable, die innerhalb des Anweisungsblocks einer Kontrollstruktur
deklariert wird, nur in diesem Block gilt. Beispielsweise produziert der folgende
Code einen Fehler:
for (int i = 0; i < 10; i++) {
...
}
System.out.println (i);
// i existiert hier nicht mehr! - Der einzige wichtige Operator, den Java zusätzlich zu C definiert, ist der String-Verkettungs-Operator
+ (siehe entsprechendes Beispiel zuvor). Er ist zwar praktischer als die umständliche
C-Funktion strcat(), aber dafür sorgt er mitunter für Verwirrung, weil er mit dem arithmetischen + verwechselt werden kann. Beispielsweise gibt die folgende Anweisung Summe: 35 aus:
System.out.println ("Summe: " + 3 + 5);
- Andere Operationen mit Strings werden über Methoden der Klasse String realisiert. Jeder String-Ausdruck ist automatisch eine Instanz dieser Klasse, auch
ohne formale objektorientierte Instanzerzeugung.
Sie können zum Beispiel zwei Strings miteinander vergleichen, indem Sie string1.equals (string2) aufrufen; string1 und string2 können dabei Variablen, Literale oder Ausdrücke mit dem Datentyp String sein. Eine Variante ist string1.equalsIgnoreCase (string2) für einen Vergleich ohne Berücksichtigung von Groß- und Kleinschreibung. Das Ergebnis ist jeweils true, wenn die Strings gleich sind, oder false, wenn sie verschieden sind.
Einen allgemeineren Vergleich von Strings – analog zur C-Funktion strcmp() – bietet die Methode string1.compareTo (string2). Wie dort ist das Ergebnis kleiner als 0, wenn string1 im Zeichensatz vor string2 steht, 0, wenn die beiden Strings gleich sind, und größer als 0, wenn string1 nach string2 kommt.
Weitere interessante String-Methoden sind folgende:
- string1.charAt (pos) gibt das Zeichen an der Position pos zurück (Positionen beginnen bei 0). Zum Beispiel gibt "Köln".charAt (1) das 'ö' zurück. Beachten Sie, dass der Datentyp des Rückgabewerts char und nicht String ist.
- string1.substring (anfang, ende) liefert die Zeichen von der Position anfang bis ausschließlich ende zurück. Beispielsweise ergibt "Köln".substring (1, 3) den String "öl".
- string1.indexOf (ch) gibt die erste Position in string1 zurück, an der das Zeichen ch (Typ char) vorkommt, oder –1, wenn ch gar nicht vorkommt. Eine alternative Form sucht nach dem Vorkommen eines Teilstrings: string1.indexOf (string2). lastIndexOf() gibt dagegen die Position des letzten Vorkommens des gesuchten Zeichens oder Teilstrings zurück.
- string1.length() liefert die Länge des Strings in Zeichen.
- Im Unterschied zu C verwendet Java keine Zeiger. Dies entfernt eine der wichtigsten Quellen für Fehler und Sicherheitsprobleme aus der Sprache. Für einen Call by Reference werden stattdessen Objektreferenzen verwendet (siehe im Folgenden).
- In Java gibt es zusätzliche Arten von Kommentaren. Der mit C++ eingeführte einzeilige Kommentar beginnt mit // und reicht bis zum Ende der Zeile. Daneben existiert der spezielle JavaDoc-Kommentar, der mit /** beginnt und mit */ endet. Das mit dem Java SDK gelieferte Programm javadoc kann aus diesen Kommentaren automatisch eine Programmdokumentation generieren.
9.2.2 Objektorientierte Programmierung mit Java
Sämtliche Klassen der Java-Klassenbibliothek und alle, die Sie selbst definieren, stammen direkt oder indirekt von der Klasse Object ab. Object gehört zum Kern-Package java.lang, das nicht mithilfe von import eingebunden werden muss. Wie der Name schon sagt, stellt lang den Sprachkern von Java zur Verfügung, darunter wichtige Klassen wie String, Math (mathematische Konstanten und Funktionen) oder System (wichtige Betriebssystemschnittstellen).
Objektorientierte Programmierung lässt sich am besten anhand eines Beispiels veranschaulichen. Da das eingangs erwähnte Auto-Beispiel in fast jedem Buch über OOP verwendet wird, soll hier zur Abwechslung ein anderes zum Einsatz kommen: Es werden die verschiedenen Arten von Personen in einer Ausbildungsumgebung modelliert. Die grundlegende Klasse, von der alle anderen abgeleitet werden, heißt Person und definiert diejenigen Eigenschaften, die alle beteiligten Personen gemeinsam haben:
public class Person {
// Eigenschaften:
private String name;
private String vorname;
private int alter;
// Konstruktor:
public Person (String n, String v, int a) {
this.name = n;
this.vorname = v;
this.alter = a;
}
// Methoden:
public void geburtstag() {
this.alter++;
}
public String getName() {
return this.vorname + " " + this.name;
}
public int getAlter() {
return this.alter;
}
}
Speichern Sie diese Klassendefinition zunächst unter Person.java. Sie lässt sich ohne Weiteres kompilieren, aber natürlich nicht ausführen, da sie wegen der fehlenden main()-Methode kein ausführbares Programm ist.
Wie Sie möglicherweise bemerkt haben, handelt es sich bei diesem Beispiel um die Java-Entsprechung der C-Struktur, in der ebenfalls Personendaten abgelegt wurden. In Java gibt es struct übrigens nicht, weil es sich dabei aus der Sicht der OOP lediglich um den Sonderfall einer Klasse ohne Methoden handelt.
Die einzelnen Bestandteile der Klassendefinition werden nun kurz erläutert:
- Als Erstes werden die verschiedenen Eigenschaften der Klasse deklariert. Sie haben die Geheimhaltungsstufe private, sind also außerhalb eines Objekts dieser Klasse nicht sichtbar. Innerhalb der Methoden der Klasse können die Eigenschaften mit einem vorangestellten this. angesprochen werden; Sie können es aber auch weglassen, solange die Bezeichner eindeutig sind. this repräsentiert während der Ausführung der Konstruktoren und Methoden einer Klasse die aktuelle Instanz selbst.
- Der Konstruktor ist eine spezielle Methode, die immer dann aufgerufen wird, wenn eine Instanz der Klasse erzeugt wird. Konstruktoren tragen stets den Namen der Klasse und haben keinen Datentyp. Sie werden typischerweise verwendet, um das neu erzeugte Objekt zu initialisieren, etwa um den Eigenschaften Anfangswerte zuzuweisen. Falls Sie keine Konstruktoren definieren, besitzt die Klasse automatisch einen Standardkonstruktor, der nichts Besonderes tut.
- Die drei Methoden dieser Klasse besitzen alle die Geheimhaltungsstufe public, damit sie von außen für Instanzen der Klasse aufgerufen werden können. Diese Methoden sind die offiziellen Schnittstellen, über die die Werte der Eigenschaften gelesen oder geändert werden können. Über dieses erlaubte Maß hinaus besteht keine weitere Möglichkeit dazu.
Um die Klasse Person ausprobieren zu können, wird das folgende kleine Programm verwendet:
public class PersonenTest {
public static void main (String args[]) {
Person klaus = new Person ("Schmitz", "Klaus", 42);
System.out.println ("Person: " + klaus.getName());
klaus.geburtstag();
System.out.println ("Neues Alter: " +
klaus.getAlter());
}
}
Eine Instanz ist formal eine Variable, deren Datentyp die entsprechende Klasse ist. Der Konstruktor der Klasse wird mithilfe von new aufgerufen. Die Methoden werden durch einen . (Punkt) vom Instanznamen (in diesem Beispiel: klaus) getrennt.
Überladen von Konstruktoren und Methoden
Mitunter ist es nützlich, ein Objekt mithilfe verschiedener Eingabewerte zu erzeugen. Daher besteht die Möglichkeit, mehrere Konstruktoren zu definieren. Auch bestimmte Methoden könnten ihre Funktionalität auf verschiedene Art und Weise zur Verfügung stellen, die ebenfalls von unterschiedlichen Parametern abhängt. Die mehrfache Definition eines Konstruktors oder einer Methode mit verschiedenen Parametern wird als Überladen bezeichnet.
Innerhalb der Klasse Person könnten Sie beispielsweise einen alternativen Konstruktor definieren, der aufgerufen wird, wenn der Vorname unbekannt ist:
public Person (String n, int a) {
this.name = n;
this.alter = a;
this.vorname = "";
}
Da bereits ein anderer Konstruktor definiert ist, können Sie ihn innerhalb des neuen Konstruktors aufrufen. Dadurch lässt sich der zweite Konstruktor erheblich kürzer fassen:
public Person (String n, int a) {
this (n, "", a);
}
Je nachdem, wie Sie eine Instanz der Klasse Person erzeugen, wird einer der beiden Konstruktoren aufgerufen:
Person klaus = new Person ("Schmitz", "Klaus", 42);
// ruft den ersten Konstruktor auf
Person meyer = new Person ("Meyer", 32);
// ruft den zweiten Konstruktor auf
Das Überladen von Methoden funktioniert genauso. Beispielsweise könnte eine weitere Version der Methode geburtstag() existieren, die das Alter explizit einstellt:
public void geburtstag (int a) {
this.alter = a;
}
Wenn Sie einfach geburtstag() aufrufen, wird die Person wie gehabt ein Jahr älter; ein Aufruf wie geburtstag (33) ruft dagegen die neue Methode auf und setzt das Alter auf den angegebenen Wert.
Vererbung
Eines der wichtigsten Merkmale der OOP besteht darin, dass Sie speziellere Klassen von allgemeineren ableiten können. Dies wird als Vererbung bezeichnet. Die allgemeine übergeordnete Klasse heißt Elternklasse (Parent Class), während die speziellere untergeordnete Klasse Kindklasse (Child Class) oder abgeleitete Klasse genannt wird. In der abgeleiteten Klasse müssen nur diejenigen Eigenschaften und Methoden definiert werden, die in der Elternklasse noch nicht vorhanden waren oder geändert wurden.
In Java wird die Vererbung durch das Schlüsselwort extends gekennzeichnet. Beachten Sie, dass die abgeleitete Klasse nur diejenigen Bestandteile der Elternklasse verwenden kann, deren Geheimhaltungsstufe nicht private ist. Da es nicht empfehlenswert ist, allzu viele Komponenten einer Klasse als public zu definieren, bietet Java die spezielle Geheimhaltungsstufe protected an. Eigenschaften und Methoden, die protected sind, werden nicht nach außen veröffentlicht, können aber in Kindklassen eingesetzt werden.
Bevor Sie die folgenden Klassen von Person ableiten können, sollten Sie dort jedes Vorkommen von private durch protected ersetzen und die Datei neu kompilieren.
Die folgenden beiden Klassen Lehrer und Schueler müssen in gleichnamigen Dateien gespeichert werden. Sie können dann kompiliert werden:
public class Lehrer extends Person {
// zusätzliche Eigenschaft:
private String fach;
// Konstruktor:
public Lehrer (String n, String v, int a, String f) {
super (n, v, a);
this.fach = f;
}
// Neue Methode:
public String getFach() {
return this.fach;
}
}
public class Schueler extends Person {
// zusätzliche Eigenschaft:
private int klasse;
// Konstruktor:
public Schueler (String n, String v, int a, int k) {
super (n, v, a);
this.klasse = k;
}
// Neue Methoden:
public int getKlasse() {
return this.klasse;
}
public void versetzung() {
this.klasse++;
}
}
Die einzige erklärungsbedürftige Besonderheit in den abgeleiteten Klassen dürfte das Schlüsselwort super sein. Es ruft explizit den durch die Auswahl der Argumente spezifizierten Konstruktor der Elternklasse auf.
In einem Programm können diese beiden Klassen beispielsweise folgendermaßen verwendet werden:
Lehrer welsch = new Lehrer ("Welsch", "Jo", 64, "Mathe");
System.out.println (welsch.getName() + " unterrichtet " +
welsch.getFach());
Schueler tim = new Schueler ("Witt", "Tim", 16, 11);
tim.versetzung();
System.out.println (tim.getName()
+ " versetzt in Klasse " + tim.getKlasse());
Die Ausgabe dieser beiden Beispiele sollte so aussehen:
Jo Welsch unterrichtet Mathe
Tim Witt versetzt in Klasse 12
Interfaces
Anders als in C++ und anderen Sprachen ist in Java keine Mehrfachvererbung erlaubt; eine Klasse kann also immer nur von genau einer anderen abgeleitet werden. Mitunter kann dies sehr lästig sein: Zwei verschiedene Klassen, die ansonsten nichts miteinander zu tun haben, könnten einen gewissen gemeinsamen Aspekt aufweisen und von einer anderen Stelle aus unter diesem Aspekt betrachtet werden. Beispielsweise ist ein Buch ein völlig anderes Objekt als eine Suppenschüssel. Beide könnten aber als Artikel im gleichen Supermarkt verkauft werden und als solche gemeinsame Eigenschaften wie eine Artikelnummer oder einen Preis aufweisen.
Um Objekte beliebiger Klassen unter einem bestimmten Gesichtspunkt als die gleiche Art von Objekt betrachten zu können, verwendet Java das Verfahren der Interfaces. Ein Interface ähnelt einer Klassendefinition, enthält aber lediglich Methodendeklarationen, die nicht implementiert werden, also keine Anweisungen enthalten. Eine Klasse, die ein Objekt dieser Art sein soll, muss alle im Interface deklarierten Methoden implementieren.
Das folgende Beispiel definiert ein Interface namens Artikel, das verschiedene Methoden deklariert:
public interface Artikel {
public int getArtNr();
public int getPreis();
}
Die Klasse Buch implementiert das Interface Artikel und definiert daher die Methoden getArtNr() und getPreis():
public class Buch implements Artikel {
private int artNr;
private int preis;
...
public int getArtNr() {
return artNr;
}
public int getPreis() {
return preis;
}
}
Der Hauptnutzen dieser Interface-Implementierung besteht darin, dass eine Methode, die mit verschiedenen Artikeln arbeitet, diese alle als Daten vom gleichen Typ ansprechen kann, nämlich Artikel.
In der Java-Klassenbibliothek sind Unmengen von Interfaces enthalten, die Sie in Ihren eigenen Programmen implementieren können. Bekannte Beispiele dafür sind die Interfaces Serializable oder Runnable. Serializable wird für Klassen verwendet, deren Datenbestand sich als sequenzieller Datenstrom darstellen (serialisieren) lässt, während Runnable von Programmen implementiert wird, die als Thread laufen sollen (siehe Kapitel 10, »Konzepte der Programmierung«).
9.2.3 Dateizugriffe in Java
In Java stehen, wie bereits erwähnt, verschiedene Klassen für die unterschiedlichsten Aspekte der Ein- und -Ausgabe zur Verfügung. Dies betrifft auch den Umgang mit Dateien. Hier sehen Sie nur ein kurzes Beispiel. Das folgende Programm liest sämtliche Zeilen aus der als Kommandozeilenargument angegebenen Textdatei und gibt sie in umgekehrter Reihenfolge aus.
import java.io.*;
import java.util.*;
public class FileTurner {
public static void main (String args[]) {
try {
turnFile (args[0]);
}
catch (FileNotFoundException e) {
System.out.println ("Datei nicht gefunden!");
}
catch (IOException e) {
System.out.println ("Dateifehler!");
}
}
static void turnFile (String filename) throws
FileNotFoundException, IOException {
BufferedReader reader = new BufferedReader
(new FileReader (filename));
String zeile = "";
Stack zeilen = new Stack();
while ((zeile = reader.readLine()) != null) {
zeilen.push (zeile);
}
while (!zeilen.empty()) {
zeile = (String)zeilen.pop();
System.out.println (zeile);
}
}
}
Das Programm demonstriert allerdings nicht nur, wie Sie in Java aus einer Textdatei lesen können, sondern zeigt noch einige andere interessante Aspekte der Java-Programmierung:
- Innerhalb der Methode turnFile() werden verschiedene Methoden aufgerufen, die Ausnahmen auslösen können. Damit nicht jede dieser Anweisungen innerhalb eines try/catch-Blocks stehen muss, werden sämtliche möglichen Ausnahmen mithilfe einer throws-Klausel an die aufrufende Stelle delegiert. Aus diesem Grund steht der Aufruf von turnFile() von main() innerhalb eines try-Blocks; die anschließenden catch-Blöcke fangen die entsprechenden Ausnahmen ab: java.io.FileNotFoundException (Datei nicht gefunden) und java.io.IOException (Schreib-/Lesefehler).
- Für das Umkehren der Zeilen aus der Textdatei wird die spezielle Datenstruktur java.util.Stack verwendet, mit deren Hilfe in Java die im nächsten Kapitel besprochene Stack-Funktionalität implementiert wird: Die Methode push() fügt ein beliebiges Objekt (eine Instanz der allgemeinsten Klasse java.lang.Object) am Ende des Stacks hinzu, pop() entnimmt das letzte Objekt und gibt es zurück. Mithilfe von Typecasting kann es in ein Objekt seiner ursprünglichen Klasse zurückverwandelt werden: im vorliegenden Fall (String)zeilen.pop().
- Zum Lesen ganzer Zeilen aus der Textdatei wird der java.io.FileReader, der dem Lesen aus einer Datei dient, von einem java.io.BufferedReader umhüllt, dessen Methode readLine() jeweils eine Zeile aus dem zugrunde liegenden Eingabestrom liest. Die Bedingung der
while()-Schleife macht es sich übrigens zunutze, dass readLine() am Dateiende null zurückgibt: Die Wertzuweisung zeile = reader.readLine() wird dazu unmittelbar mit null verglichen.
Und der Rest der Java-Klassenbibliothek?
Eine ausführlichere Beschreibung von Java würde nach der Darstellung der bisher gebotenen Grundlagen dazu übergehen, verschiedene Komponenten der mit dem JDK gelieferten Klassenbibliothek zu beschreiben. Diese Bibliothek enthält Dutzende von Packages mit einer Vielzahl von Klassen, die wiederum unzählige Methoden besitzen.
Da nicht die geringste Chance besteht, einer solchen Informationsmenge in einem Buch wie diesem gerecht zu werden, habe ich mich entschlossen, an dieser Stelle gar nicht weiter auf die Klassenbibliothek einzugehen. Stattdessen lernen Sie im nächsten Kapitel und in einigen späteren Kapiteln jeweils thematisch passende Beispielklassen kennen. Für eine ausführliche Darstellung der Programmiersprache Java empfehle ich beispielsweise das hervorragende Buch »Java ist auch eine Insel« von Christian Ullenboom (Galileo Computing).
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.