Java auf den Punkt gebracht Michael Kofler Variablen Operatoren Klassen Exceptions Multi-Threading Generics und Collections Lambda-Ausdrücke Aktuell zu Java 8! ebooks.kofler Java auf den Punkt gebracht (aktuell zu Java 8) Ursprünglicher Titel: Die Java-Syntax Stichworte: Java 8, Variablen, Arrays und Enums, Elementare Datentypen, Strings, Datum und Uhrzeit, Operatoren, Verzweigungen, Schleifen, Methoden, Klassen, Schnittstellen, Vererbung, Exceptions, Pakete, Bibliotheken, Multi-Threading, Generics, Collections, Lambda-Ausdrücke, Annotationen. © Michael Kofler und ebooks.kofler 2013, 2014 Autor Michael Kofler Korrektorat Markus Hinterreither ISBN (PDF) 978-3-902643-09-4 ISBN (EPUB) 978-3-902643-15-5 Verlag ebooks.kofler, Schönbrunngasse 54c, 8010 Graz, Austria Die PDF- und EPUB-Ausgabe dieses Buchs können Sie hier kaufen: http://kofler.info/ebooks/java/ Viele in diesem eBook genannten Hard- und Software-Bezeichnungen sind geschützte Markennamen. Dieses eBook wurde mit großer Sorgfalt verfasst. Dennoch sind Fehler nicht ganz auszuschließen. Für allfällige Fehler kann keine Verantwortung oder Haftung übernommen werden. Verbesserungsvorschläge oder Korrekturen sind selbstverständlich willkommen ([email protected]). Vielen Dank dafür! Dieses eBook ist durch das österreichische Urheberrecht geschützt. Sie dürfen das eBook für den persönlichen Gebrauch kopieren und ausdrucken, aber nicht an andere Personen weitergeben, weder in elektronischer noch in anderer Form. Inhaltsverzeichnis Vorwort 7 1 Hello World! 9 1.1 Der Programmcode 9 1.2 Der Java-Compiler javac 10 1.3 Der Java-Interpreter java 12 1.4 Elementare Syntaxregeln 12 2 Variablen, Arrays und Enums 16 2.1 Variablen 16 2.2 Arrays 27 2.3 Konstanten und Enums 33 3 Zeichenketten (char und String) 37 3.1 Der char-Datentyp 37 3.2 Die String-Klasse 39 3.3 Die StringBuilder-Klasse 46 3.4 Zeichensatzprobleme 47 4 Datum und Uhrzeit 52 4.1 Die Date-Klasse 53 4.2 Die SimpleDateFormat-Klasse 55 4.3 Die Calendar-Klasse 57 4.4 ThreeTen (Java 8) 60 Java auf den Punkt gebracht ebooks.kofler INHALTSVERZEICHNIS 4 5 Operatoren 64 6 Verzweigungen (if, switch) 72 6.1 if 72 6.2 if-Kurzschreibweise (Ternary Operator) 74 6.3 switch 74 7 Schleifen 77 7.1 for 77 7.2 for-each 79 7.3 while und do-while 81 7.4 break und continue 82 8 Methoden 84 8.1 Parameterliste 86 8.2 Rückgabewert und return 91 8.3 Modifizierer 91 8.4 get- und set-Methoden 93 9 Klassen 9.1 Top-Level-Klassen 95 95 9.2 Lokale Klassen 105 9.3 Anonyme Klassen 107 9.4 Statische geschachtelte Klassen 110 10 Vererbung und Schnittstellen 112 10.1 Vererbung 112 10.2 Schnittstellen 119 10.3 Die Object-Klasse 126 Java auf den Punkt gebracht INHALTSVERZEICHNIS ebooks.kofler 11 Lambda-Ausdrücke 5 130 11.1 Hello Lambda-World! 130 11.2 Syntax von Lambda-Ausdrücken 133 11.3 War das schon alles? 139 12 Exceptions 140 12.1 Exception-Klassen 140 12.2 try-catch 143 12.3 Fehleranfällige Methoden deklarieren (throws) 148 12.4 Selbst Exceptions werfen (throw) 149 13 Pakete und Bibliotheken 151 13.1 import 152 13.2 Pakete 154 13.3 Bibliotheken 156 14 Multi-Threading 159 14.1 Multi-Threading-Syntax 159 14.2 Die java.util.concurrent-Klassen 172 15 Generics 182 15.1 Einführung 182 15.2 Deklaration generischer Klassen und Schnittstellen 186 15.3 Deklaration generischer Methoden 188 15.4 Wildcards 189 16 Collections 196 16.1 Einführung 197 16.2 Die Iterable-Schnittstelle 203 16.3 Die Collection-Schnittstelle 205 16.4 Die Set-Schnittstelle 208 Java auf den Punkt gebracht ebooks.kofler INHALTSVERZEICHNIS 6 16.5 Die List-Schnittstelle 213 16.6 Die Stream-Schnittstelle (Java 8) 218 16.7 Die Map-Schnittstelle 220 17 Annotationen 227 17.1 Vordefinierte Annotationen 228 17.2 Eigene Annotationen definieren 229 Vorwort Dieses eBook fasst die grundlegenden Elemente der Programmiersprache Java zusammen. Neben einer kompakten Beschreibung der Syntax zeigt das eBook anhand kleiner Beispiele auch die praktische Anwendung von Java. Das Buch geht auch auf elementare Klassen aus der Java-Standardbibliothek ein, etwa zur Bearbeitung von Zeichenketten, Daten, Zeiten, Arrays und Aufzählungen sowie zur Verwaltung eigener Threads. Das Buch ist in kleine Kapitel strukturiert, die jeweils unabhängig voneinander gelesen werden können. Das obligatorische Hello World!-Kapitel gibt eine Schnelleinführung in die Konzepte von Java. Anschließend folgen die prozeduralen Sprachelemente (Variablen, Operatoren, Schleifen etc.), bevor ich im Detail auf die objektorientierten Sprachelemente von Java eingehe. Die folgende Liste gibt einen ersten Überblick: Hello World Lambda-Ausdrücke Variablen, Arrays, Enums Exceptions Strings, Datum und Uhrzeit Pakete und Bibliotheken Operatoren Multi-Threading Verzweigungen und Schleifen Generics und Collections Methoden Annotationen Klassen, Vererbung, Schnittstellen Nicht Thema dieses eBooks sind die Installation von Java, eine Einführung in Eclipse oder andere grafische Entwicklungsumgebungen sowie eine umfassende Beschreibung der Java-Standardbibliotheken. Ebenfalls unbehandelt bleiben alle spezifischen Besonderheiten, die für die Entwicklung von Enterprise-Anwendungen (Java EE) bzw. von Android-Apps gelten. Das eBook konzentriert sich somit auf die elementaren Sprachelemente von Java. Java auf den Punkt gebracht INHALTSVERZEICHNIS ebooks.kofler 8 Java 8 Dieses eBook ist aktuell zu Java 8 und berücksichtigt alle wesentlichen Neuerungen. Damit Ihnen dieses eBook auch dann weiterhilft, wenn Sie noch mit Java 7 arbeiten oder alten Java-6-Code warten müssen, weise ich bei neuen Sprachelementen darauf hin, mit welcher Java-Version diese eingeführt wurden. Zielgruppe Mein primäres Ziel beim Verfassen dieses eBooks war es, die wichtigsten bzw. am häufigsten eingesetzten Sprachelemente von Java knapp und klar darzustellen. Insofern kann und will dieses eBook nicht 1000-seitige Java-Bücher ersetzen, die auch auf unzählige Spezialfälle eingehen, möglichst viele Klassen der Java-Standardbibliothek vorstellen oder eine Hilfestellung beim Programmieren-Lernen bieten. Dieses eBook richtet sich somit an Programmierer/-innen, die mit den Grundideen objektorientierter Programmierung bereits vertraut sind – sei es in Java oder in einer anderen objektorientierten Programmiersprache. Die Gliederung in überschaubare Kapitel erleichtert ein rasches Nachschlagen im Sinne einer Java-Syntaxreferenz. Zugleich ist dieses eBook eine ideale Hilfestellung, wenn Sie von einer anderen Programmiersprache auf Java umsteigen oder häufig zwischen verschiedenen Programmiersprachen wechseln. Michael Kofler im Januar 2014 http://kofler.info PS: Zu besonderem Dank bin ich Christian Ullenboom verpflichtet. Er hat die erste Fassung dieses eBooks noch unter dem damaligen Titel Die Java-Syntax durchgesehen und mich auf diverse Ungenauigkeiten hingewiesen. (Für alle Fehler, die eventuell weiterhin vorhanden sind, bin natürlich alleine ich verantwortlich!) Seine online und bei Galileo erschienen Bücher Java ist auch eine Insel und Java – Mehr als eine Insel mit einem Gesamtumfang von mehr als 2700 Seiten gelten als die deutschsprachige JavaReferenz. 1 Hello World! Eine minimalistische Version von Hello World! für Java sieht so aus: public class H e l l o W orld { public static void main( String [] args ) { System . out . println ( " Hello World ! " ) ; } } Um dieses Programm zu kompilieren und auszuführen, müssen Sie die folgenden Kommandos eingeben: javac H e l l o W o rld. java java H e l l o W o rld Alternativ können Sie dieses Programm natürlich auch in einen Entwicklungsumgebung wie Eclipse oder Java Beans entwickeln. IDEs (also Integrated Development Environments) sind aber nicht Thema dieses eBooks. 1.1 Der Programmcode Obwohl der Code nur drei Zeilen umfasst (wenn man einmal von den Klammern absieht), gibt es dazu eine Menge zu sagen: Jedes Java-Programm muss in Klassen organisiert werden. Hello World besteht aus einer einzigen öffentlichen Klasse (public class HelloWorld). Java auf den Punkt gebracht ebooks.kofler 1 Hello World! 1.2 Der Java-Compiler javac 10 Java-Code wird in Textdateien mit der Endung *.java gespeichert. Dabei ist es entscheidend, dass der Dateiname exakt mit dem Klassennamen übereinstimmt, in diesem Beispiel also HelloWorld.java. Achten Sie auch auf die Groß- und Kleinschreibung! (Es ist üblich, dass die Namen von Klassen mit einem Großbuchstaben beginnen.) Der Startpunkt eines Java-Programms ist die Methode main. Diese Methode muss sich in einer öffentlichen Klasse befinden, und sie muss exakt wie im obigen Beispiel deklariert werden: Die Methode muss öffentlich sein (public). Sie muss statisch definiert werden (static), damit sie verwendet werden kann, ohne vorher ein Objekt der Klasse zu erzeugen. Die Methode darf nichts zurückgeben (void). Und sie muss die an das Java-Programm übergebenen Parameter in einem Zeichenketten-Array entgegennehmen (String [] args). Die Auswertung von args ist optional; das HelloWorld-Beispiel verzichtet darauf. Innerhalb von main wird mit println die Zeichenkette ‘Hello World!’ im Terminal bzw. unter Windows im Eingabeaufforderungsfenster ausgegeben. println ist eine Methode der Klasse PrintStream. Der Zugriff auf ein Objekt der PrintStream-Klasse erfolgt mit System.out. Dabei ist System eine Klasse des Pakets java.lang der Java-Standardbibliothek. Alle Klassen dieses Pakets stehen Java-Programmen standardmäßig zur Verfügung. out ist ein statisches Feld der System-Klasse. Es verweist auf ein PrintStream-Objekt zur Ausgabe von Texten auf der Standardausgabe. Dieses Objekt wird beim Start des JavaProgramms automatisch erzeugt. 1.2 Der Java-Compiler javac An den Java-Compiler wird im einfachsten Fall der Name der zu kompilierenden Codedatei übergeben. Sofern javac keine Syntaxfehler bemerkt, erzeugt es eine neue Datei mit der Endung *.class. Diese Datei enthält den Java-Byte-Code. Dabei handelt es sich um einen Zwischencode, der es dem Java-Interpreter ermöglicht, das Programm effizient auszuführen. Java auf den Punkt gebracht ebooks.kofler 1 Hello World! 1.2 Der Java-Compiler javac 11 Die vom Java-Compiler produzierten Dateien sind somit nicht unmittelbar ausführbar. Die Verwendung eines Zwischencodes hat den Vorteil, dass ein unter Windows kompiliertes Java-Programm auch unter Linux oder OS X ausgeführt werden kann. Diese Plattformunabhängigkeit zählte ursprünglich zu den wichtigsten Faktoren für den Erfolg von Java. Jede öffentliche Java-Klasse, -Schnittstelle oder -Enumeration muss in einer eigenen Datei definiert werden. Der Quellcode eines Java-Programms besteht also zumeist aus mehreren Dateien. Zum Kompilieren reicht es dennoch aus, nur die .java-Datei mit main() zu übergeben. Der Kompiler berücksichtigt automatisch alle Abhängigkeiten zu anderen .java-Dateien und kompiliert diese Dateien bei Bedarf ebenfalls neu. Das Verhalten des Java-Compiler können Sie mit diverse Optionen steuern. Besonders wichtig sind die folgenden beiden Optionen: -encoding=utf8: Diese Option gibt an, in welchem Zeichensatz die Quelltextdateien codiert sind. Es hängt von der Java-Version und dem Betriebssystem ab, welchen Zeichensatz Java erwartet. Java 7 verarbeitet standardmäßig unter Linux und OS X UTF8-Dateien, unter Windows hingegen cp1252. Dieser Windows-spezifische Zeichensatz hat Ähnlichkeiten zu ISO-8859-1, ist damit aber nicht ganz identisch. Wenn Ihre Code-Dateien einen anderen Zeichensatz verwenden als Java erwartet, müssen Sie den Zeichensatz mit der Option -encoding angeben. Ausführliche Informationen zum Umgang von Java mit unterschiedlichen Zeichensätzen bietet das Kapitel Zeichenketten. -cp: Wenn Ihr Java-Projekt auf zusätzliche Java-Dateien bzw. Bibliotheken (*.classund *.jar-Dateien) zurückgreift, geben Sie mit der Option -cp deren Ort an. Die Abkürzung cp steht dabei für classpath, also den Verzeichnispfad für Klassendateien. Tipps zum Umgang mit externen Bibliotheken finden Sie in Kapitel Pakete und Bibliotheken. Java auf den Punkt gebracht ebooks.kofler 1 Hello World! 1.4 Elementare Syntaxregeln 12 1.3 Der Java-Interpreter java Für die eigentliche Ausführung eines Java-Programms ist der Java-Interpreter java verantwortlich. An das Kommando wird der Name der Klasse übergeben, die die mainMethode enthält, also den Startpunkt des Programms. Auch das Verhalten von java kann durch diverse Optionen beeinflusst werden. Besonders wichtig ist dabei wiederum -classpath zur Angabe des Speicherorts externer Bibliotheken. Wenn Sie wissen möchten, welche Java-Version auf Ihrem Rechner installiert ist, führen Sie java mit der Option -version aus: java - version java version " 1.8.0 - ea " Java ( TM ) SE Runtime E n v i r o n men t ( build 1.8.0 - ea - b123 ) Java HotSpot ( TM ) 64 - Bit Server VM ( build 25.0 - b65 , mixed mode ) 1.4 Elementare Syntaxregeln Die Strukturierung des Codes erfolgt durch geschwungene Klammern {}. Jede Java-Anweisungen muss mit einem Strichpunkt abgeschlossen werden. Java-Anweisungen dürfen über mehrere Zeilen reichen. Der Java-Compiler ist dabei sehr flexibel, was den Umgang mit Leerzeichen und Zeilenumbrüche betrifft. So wird das folgende Kommando anstandslos akzeptiert: System . out . println ( " Hello World " ) ; Zeichenketten werden in doppelte Anführungszeichen gestellt ("abc"). Sie dürfen nicht über mehrere Zeilen reichen, können aber mit dem Operator + verbunden werden. String s = " eine lange " + " Z e i c h e n ket te" ; Java auf den Punkt gebracht ebooks.kofler 1 Hello World! 1.4 Elementare Syntaxregeln 13 Code-Einrückungen sind optional und nicht Teil der Java-Syntax. Zwischen Groß- und Kleinschreibung wird unterschieden, sowohl bei Schlüsselwörtern als auch bei Klassen-, Methoden- und Variablennamen. Der Zugriff auf Objekt- bzw. Klassenvariablen (Felder) erfolgt in der Form objekt.feld oder Klasse.statischesFeld. Methoden werden in der Form objekt.methode() oder Klasse.statischeMethode() aufgerufen. Dem Methodennamen müssen runde Klammern folgen, auch wenn keine Parameter übergeben werden. In Java ist es üblich, dass Methodennamen mit Kleinbuchstaben beginnen. Java kennt keine Funktionen. Es ist immer von Methoden die Rede, egal ob diese ein Ergebnis zurückgeben oder nicht. Java kennt keine Eigenschaften. Anstelle von Eigenschaften müssen Sie entweder Felder oder get- und set-Methoden verwenden. In der Java-Standardbibliothek sind in Paketen unzählige Klassen vordefiniert. Das Paket java.lang steht in allen Java-Programmen standardmäßig zur Verfügung, d. h., die dort enthaltenen Klassen können ohne Angabe des Paketnamens genutzt werden. Bei allen anderen Klassen müssen Sie entweder den Paketnamen voranstellen (z. B. java.util.Random) oder zu Beginn Ihres Programms einen sogenannten Import durchführen (import java.util.Random oder import java.util.*). Vergessen Sie darauf, beklagt sich der Java-Compiler darüber, dass er das Symbol (also den Klassennamen) nicht kennt. Das Kapitel Pakete und Importe geht auf die Details dieser Mechanismen ein. Java kümmert sich selbst um die Speicherverwaltung. Der Speicherplatz von nicht mehr benötigten Objekten (d. h. von Objekten, auf die keine Variable mehr verweist), wird automatisch durch einen Hintergrundprozess mit dem Garbage Collector wieder freigegeben. Als Programmierer müssen Sie sich daher nicht um die Speicherverwaltung kümmern. Java auf den Punkt gebracht 1 Hello World! 1.4 Elementare Syntaxregeln ebooks.kofler 14 Achtung Ein Strichpunkt für sich ist syntaktisch erlaubt und gilt als eine Art leeres Kommando. Im folgenden Beispiel wird die for-Schleife zwar korrekt durchlaufen; mit jedem Schleifendurchgang wird aber nur das leere Kommando ; ausgeführt! Die printlnMethode wird aber nur einmal nach dem Ende der Schleife ausgeführt. // falsch ; println wird nur einmal ausgef ü hrt // ( Ausgabe 10) int i ; for ( i =0; i <10; i ++) ; System . out . println ( i ) ; // richtig ; println wird 10 x ausgef ü hrt // ( Ausgabe 0 bis 9) for ( i =0; i <10; i ++) System . out . println ( i ) ; Java-Schlüsselwörter Die folgenden Schlüsselwörter sind reserviert und dürfen nicht als Namen von Variablen, Klassen etc. verwendet werden: abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for if goto i m p l e m ents import i n s t a n ceof int interface long native new package private protected public return short static strictfp super switch s y n c h r o niz ed this throw throws transient try void volatile while Java auf den Punkt gebracht ebooks.kofler 1 Hello World! 1.4 Elementare Syntaxregeln 15 Kommentare im Java-Code Es gibt drei Möglichkeiten, um Kommentare in den Quellcode zu integrieren: Einzeilige Kommentare werden mit // eingeleitet und reichen bis zum Ende der Zeile. Mehrzeilige Kommentare beginnen mit /* und enden mit */. Javadoc-Kommentare werden mit /** eingeleitet und enden ebenfalls mit */. Diese Kommentare können vom Kommando javadoc ausgewertet und zu einem HTML-Dokument verarbeitet werden, das alle Klassen, Felder, Methoden, Parameter etc. eines Java-Projekts beschreibt. Innerhalb der Javadoc-Kommentare können einzelne Textpassagen durch Schlüsselwörter wie @author name oder @param parametername beschreibung gekennzeichnet werden. 2 Variablen, Arrays und Enums In Variablen speichern Sie alle elementaren Daten sowie Referenzen auf alle Objekte, die Sie in Ihrem Programm nutzen. Arrays bieten Ihnen darüber hinaus die Möglichkeit, unkompliziert mehrere gleichartige Daten bzw. Objekte zu verwalten. Zu den weiteren Themen dieses Kapitels zählen die elementaren Datentypen von Java (int, double etc.), deren Boxing-Varianten, die Typumwandlung durch Casting sowie der Umgang mit Konstanten und Enums (also Konstantenaufzählungen). 2.1 Variablen Variablen müssen vor ihrer Verwendung mit Typangabe deklariert werden. Zusammen mit der Deklaration kann auch die Initialisierung erfolgen. Jede Variable muss initialisiert werden, bevor sie das erste Mal ausgelesen wird. int a ; long b =3; Variablen können nur innerhalb des Codeblocks verwendet werden, in dem sie definiert sind, nicht aber außerhalb. Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.1 Variablen ebooks.kofler 17 { double x =3; { System . out . println ( x ); } } System . out . println (x ) ; // ok // Fehler , x ist hier unzug ä nglich Variablen, die auf Klassenebene definiert sind, werden auch Felder (Fields) genannt. Innerhalb der Methoden einer Klasse greifen Sie mit name auf derartige Variablen zu. Wenn es innerhalb der Methode einen gleichnamigen Parameter oder eine lokale Variable gibt, ermöglicht this.name den Zugriff auf das außerhalb deklarierte Feld. Anwender der Klasse können nur auf Felder zugreifen, die mit public deklariert sind. class M e i n e K l ass e { public int var1 ; private int var2 ; // K o n s t r ukt or public M e i n e K l asse( int var1 , int var2 ) { this. var1 = var1; this. var2 = var2; } // a l l g e m eine Methode public int berechne () { return var1 + var2 ; } } Java auf den Punkt gebracht 2 Variablen, Arrays und Enums ebooks.kofler 2.1 Variablen 18 Variablennamen Für die Bildung von Variablennamen gelten die folgenden Regeln: Der Name darf aus beliebig vielen Buchstaben, Ziffern sowie den Zeichen _ und $ zusammengesetzt werden. Der Einsatz des Zeichens $ ist nur für automatisch generierten Code empfohlen. Der Name darf nicht mit einer Ziffer beginnen. Der Name darf nicht mit Java-Schlüsselwörtern wie if oder break übereinstimmen. Ebenfalls unzulässig sind die booleschen Literale true und false sowie das NullLiteral null. Internationale Zeichen wie äöüß sind erlaubt, können aber zu Problemen führen, wenn der Zeichensatz der Java-Codedatei nicht den Erwartungen des Java-Compilers entspricht. Abhilfe: javac-Option -encoding. Besser ist es in der Regel, auf den Einsatz internationaler Zeichen zu verzichten. Es wird zwischen Groß- und Kleinschreibung unterschieden. Die obigen Regeln gelten auch für die Namen von Klassen, Aufzählungen (Enums) und anderen Java-Konstrukten. Es ist üblich (aber nicht zwingend vorgeschrieben), Variablenund Methodennamen mit Kleinbuchstaben zu beginnen lassen, Klassennamen dagegen mit Großbuchstaben. Für die Namen von Konstanten werden normalerweise ausschließlich Großbuchstaben verwendet. Elementare Datentypen Java kennt die in der folgenden Tabelle aufgezählten elemantaren Datentypen (primitive data types, siehe Tabelle 2.1). Beim Umgang mit den elementaren Java-Datentypen sind einige Besonderheiten zu beachten: Vorzeichen: Im Gegensatz zu vielen anderen Programmiersprachen gibt es in Java keine vorzeichenlosen Integerzahlen. Es existiert allerdings der Operator >>>, mit dem eine Integerzahl bitweise so nach rechts verschoben wird, als würde es sich um eine Zahl ohne Vorzeichen handeln. Java auf den Punkt gebracht 2 Variablen, Arrays und Enums ebooks.kofler 2.1 Variablen Datentyp Beschreibung byte ganze Zahl, 1 Byte, Zahlenbereich -128 bis +127 short ganze Zahl, 2 Byte, Zahlenbereich -32768 bis +32767 int ganze Zahl, 4 Byte, Zahlenbereich 231 (ca. 2 109 ) long ganze Zahl, 8 Byte, Zahlenbereich 263 (ca. 9 1018 ) boolean true oder false char ein Unicode-Zeichen double Fließkommazahl, 8 Byte, 16 Stellen (max. ca. 2 10308 ) float Fließkommazahl, 4 Byte, 8 Stellen (max. ca. 3 1038 ) 19 Tabelle 2.1: Elementare Java-Datenypen Überlaufkontrolle: Java führt bei Berechnungen keine Überlaufkontrolle durch. Wenn der zulässige Zahlenbereich bei Integer-Berechnungen überschritten wird, kommt es zu falschen Ergebnissen. Bei Fließkommazahlen lautet das Ergebnis Infinity bzw. -Infinity. In beiden Fällen kommt es zu keiner Fehlermeldung oder Exception! Division durch Null: Bei Integer-Berechnungen löst eine Division durch 0 eine ArithmeticException aus. Bei Fließkommazahlen tritt hingegen keine Exception auf, das Ergebnis lautet Infinity bzw. -Infinity. Rundungsfehler: Bei Berechnungen mit Fließkommazahlen kommt es zu Rundungsfehlern. Das ist kein Java-Problem, sondern hat mit der binären Darstellung von Zahlen zu tun und ist unvermeidbar. Erschwert wird die Situation allerdings dadurch, dass die Fließkommaarithmetik in Java ein wenig von der anderer Programmiersprachen abweicht, die zumeist konform zum IEEE-Standard 754/854 sind. Das betrifft insbesondere die oben schon erwähnte fehlende Überlaufkontrolle. Leseempfehlungen: einerseits die mittlerweile legendäre Kritik an Javas Fließkommaarithmetik des Berkeley-Professors William Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.1 Variablen ebooks.kofler 20 Kahan (veröffentlicht 1998), andererseits eine Zusammenfassung seither realisierter Verbesserungen (veröffentlicht 2008). Wenn Sie Rundungsfehler ausschließen möchten, z. B. beim Rechnen mit Geld, können Sie die BigDecimal-Klasse einsetzen. Deren Anwendung ist freilich sehr unhandlich, weil Sie dann Operatoren wie + und - nicht verwenden können und stattdessen Methoden wie add und subtract gebrauchen müssen. double x =10; x = x /0.0; // x enthä lt Infinity int i =100000; i=i*i; // i enthä lt 1 4 1 0 0 65 408 double y =1 E250; y=y*y; // y enthä lt Infinity double a = 0.7; double b = 0.9; double c = a + 0.1; double d = b - 0.1; System . out . println (c == d) ; System . out . println (c - d ); // liefert false // liefert - 1 . 1 1 0 2 2 3 0 2 46 251 565E - 16 Hinweis Besonderheiten beim Umgang mit Zeichen (char) und Zeichenketten (String) werden im Kapitel Zeichenketten beschrieben. Literale Standardmäßig werden ganze Zahlen im Java-Code als int-Zahlen interpretiert, Fließkommazahlen als double-Zahlen. Das folgende Listing fasst Syntaxvarianten für Zahlen-Literale zusammen. char- und String-Literale finden Sie abermals im Kapitel Zeichenketten. Java auf den Punkt gebracht ebooks.kofler 2 Variablen, Arrays und Enums 2.1 Variablen 21 int i1 = 123; int i2 = 0 xABCD ; // hexadezimal , e n t s p r icht 43981 int i3 = 0123; // oktal , e n t s p r icht 83 int i4 = 0 b110; // bin är , e n t s p r icht 6 ( ab Java 7) int i5 = 1 _ 237 _ 343 ; // dezimal mit T a u s e n d e r t ren nun g ( ab Java 7) long l1 = 1 2 3 4 5 6 7 8 90 123 45L ; // long boolean b1 = true ; boolean b2 = false ; double d1 = 1.0; double d2 = 1.7 E4 ; // E x p o n e ntia l- Schreibweise , = 17000 float f = 1.0 F; // float Rechnen mit double-Zahlen Elementare mathematische Funktionen wie Sinus, Cosinus und die Quadratwurzel sind als statische Methoden in der Math-Klasse enthalten. Dort ist auch die Konstante PI definiert. Das folgende Listing gibt einige Anwendungsbeispiele. double x =2.4 , y =0.7; double sinus = Math . sin ( x ) ; double cosinus = Math . cos (0.3 * Math. PI ) ; double quadrat = Math . pow (x , 2) ; // e n t s p ri cht x * x double qwurzel = Math . sqrt ( x ) ; double minimum = Math . min (x , y) ; // Minimum , liefert 0.7 double absolut = Math . abs ( - 0.3) ; // liefert 3 long n1 = Math . round (2.3) ; // liefert 2 long n2 = Math . round (2.5) ; // liefert 3 Hinweis Die Schreibweise x^2 im Sinne von x*x ist in Java nicht zulässig. Der Operator ^ dient vielmehr zur Verknüpfung logischer Ausdrücke oder binärer Zahlen durch exklusives Oder. Zum Potenzieren verwenden Sie die Methode Math.pow(x, n). Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.1 Variablen ebooks.kofler 22 Zufallszahlen Zur Erzeugung von Pseudozufallszahlen verwenden Sie ein Objekt der Klasse java.util.Random. Die folgenden Zeilen zeigen die Anwendung: java. util. Random r = new java . util . Random () ; double x = r . n e x t D o uble() ; // zwischen 0.0 ( inkl .) und 1.0 ( exkl .) int i = r . nextInt (100) ; // zwischen 0 und 99 long n = r . nextLong () ; // gesamter long- Z a h l e n b e reic h double g = r . n e x t G a u ss ian() ; // n o r m a l v e rtei lt ( M i t t e l wert 0 , // S t a n d a r d a b wei chu ng 1) Unterschiede zwischen elementaren Datentypen und Objekten (reference types) Für elementaren Datentypen stehen keine Felder oder Methoden zur Verfügung. Ihre Daten werden bei Zuweisungen kopiert. Der Inhalt der beiden Variablen a und b im folgenden Beispiel ist daher vollkommen unabhängig voneinander: int a =3; int b = a ; b =4; System . out . println (a + " " + b) ; // Ausgabe : 3 4 Im Gegensatz zu elementaren Datentypen müssen Objekte explizit erzeugt werden (in der Regel mit new). Bei Zuweisungen wird nur eine Referenz kopiert. Beide Variablen verweisen nun auf dieselben Daten. Änderungen wirken sich daher für beide Variablen aus. Im folgenden Beispiel verweisen p1 und p2 auf dasselbe Point-Objekt: import java . awt . Point ; ... Point p1 = new Point (3 , 3) ; Point p2 = p1 ; p2 .x = 4; System . out . println ( p1 . x + " " + p2 .x ) ; // Ausgabe : 4 4 Java auf den Punkt gebracht 2 Variablen, Arrays und Enums ebooks.kofler 2.1 Variablen 23 Wenn Sie eine unabhängige Kopie eines Objekts benötigen, müssen Sie dieses kopieren bzw. duplizieren (klonen). Einige Klassen der Java-Standardbibliothek sehen hierfür die clone-Methode vor und implementieren die Schnittstelle Cloneable. Beachten Sie aber, dass clone nur das unmittelbare Objekt dupliziert (shallow clone). Wenn das Objekt auf weitere Objekte verweist und auch diese Objekte dupliziert werden sollen, ist ein deep clone erforderlich, was in Java z. B. durch Serialisierungsmethoden ermöglicht wird. Generell haben weder die Methode clone noch die Schnittstelle Cloneable in JavaKreisen einen guten Ruf. Der Java-Chefentwickler Joshua Bloch rät in einem Interview davon ab, Cloneable und clone in eigenen Klassen zu implementieren und empfiehlt stattdessen die Entwicklung eines eigenen Copy-Konstruktors. Für den Umgang mit simplen Point-Objekten reicht clone aber vollkommen aus, um p1 unabhängig von p2 zu machen: Point p1 = new Point (3 , 3) ; Point p2 = ( Point ) p1 . clone () ; p2 .x = 4; System . out . println ( p1 . x + " " + p2 .x ) ; // Ausgabe : 3 4 Das unterschiedliche Verhalten hat auch Konsequenzen auf die Parameterübergabe: Bei elementaren Typen werden die Daten kopiert. Änderungen in der Methode haben deswegen keinen Einfluss auf die ursprünglichen Variablen. Bei Objekten werden hingegen wie bei Zuweisungen Referenzen kopiert. Das ist nicht nur effizient, sondern ermöglicht es zudem, die zugrunde liegenden Daten in der Methode zu verändern. int a =3; Point b = new Point (3 , 3) ; m (a , b ) ; System . out . println (a + " " + b .x ) ; // Ausgabe : 3 4 void m ( int i , Point p ) { i = 4; p. x = 4; } Java auf den Punkt gebracht 2 Variablen, Arrays und Enums ebooks.kofler 2.1 Variablen 24 Zeichenketten (Strings) sind Objekte. Dennoch verhalten sie sich in mehrerlei Hinsicht wie elementare Datentypen: Zum einen können Zeichenketten wegen ihrer tiefen Verankerung in der Java-Sprachdefinition ohne new einfach durch Zuweisungen erzeugt werden (String s = "abc"). Zum anderen sind Zeichenketten generell unveränderlich (immutable). Bei jedem Versuch, eine Zeichenkette zu verändern, wird daher in Wirklichkeit eine neue Zeichenkette erzeugt. Daher ist es auch unmöglich, eine als Parameter übergebene Zeichenkette in einer Methode zu verändern. String s1 = " abc " ; String s2 = s1 ; s2 = " efg " ; // s2 zeigt jetzt auf einen neuen String ! System . out . println ( s1 + " " + s2 ) ; // Ausgabe : abc efg Boxing-Varianten von elementaren Datentypen Zu allen elementaren Java-Datentypen gibt es seit Version 5 Boxed-Varianten. Die elementaren Datentypen als Objekte verpackt, verhalten sich also wie Objekte. Datentyp Boxing-Variante byte Byte short Short int Integer long Long boolean Boolean char Character double Double float Float Tabelle 2.2: Boxing-Datentypen Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.1 Variablen ebooks.kofler 25 Im Unterschied zu den elementaren Datentypen können ihre Boxing-Varianten auch den Wert null annehmen. Das ist besonders dann praktisch, wenn zwischen dem Wert 0 und dem Zustand nicht initialisiert unterschieden werden soll. Boxed-Datentypen bieten sich auch für Datenbankanwendungen an, weil Sie den SQL-Wert NULL abbilden können. Die Initialisierung von Boxed-Variablen erfordert keinen Konstruktor, erfolgt also ohne new. Die Umwandlung zwischen elementaren Datentypen und ihren Boxing-Varianten (also das boxing und das unboxing) erfolgt automatisch. Integer i =3; int j = i + 1; i = j + 1; Boxed-Datentypen können also weitgehend wie gewöhnliche Datentypen verwendet werden. Beachten Sie jedoch die Sonderfälle, die sich dadurch ergeben können, dass der Zustand null erlaubt ist. Integer i= null; int j = i + 1; // l ö st N u l l P o i n t e r Ex cep ti on aus Die Klassen der Boxing-Datentypen sind in der Java-Standardbibliothek java.lang definiert und stehen ohne import in allen Java-Programmen zur Verfügung. Sie enthalten eine Menge nützlicher Umwandlungsmethoden, die auch dann eingesetzt werden können, wenn Sie in Ihren Variablen gewöhnliche elementare Datentypen verwenden. double d = Double . p a r s e D o uble(" 12.34 " ) ; String s = Long . t o H e x S t ri ng (123) ; // 7b Typumwandlung (Casting) Wenn bei Zuweisungen oder in Ausdrücken unterschiedliche Datentypen vorkommen, führt Java nach Möglichkeit eine automatische Typumwandlung durch (implizites Casting). Java auf den Punkt gebracht ebooks.kofler 2 Variablen, Arrays und Enums 2.1 Variablen 26 int i1 =3; double d1 =1.7; double d2 = d1 * i1 ; // double - M u l t i p l i kat ion - - > 5.1 long l1 = i1 ; // casting int - - > long String s1 = " abc " ; String s2 = s1 + i1 ; // String - Verkn ü pfung - - > " abc3" Wenn die Gefahr besteht, dass es bei der Typumwandlung zu einem Genauigkeitsverlust (z. B. double ! float) oder zu einem Überlauf kommt (z. B. long ! int), produziert javac einen Fehler. Das Programm kann also nicht kompiliert werden. long l int i double float = = d f 10; l; // javac - Fehler : possible loss of precision = 3.14; = d ; // javac - Fehler : possible loss of precision Abhilfe schafft in solchen Fällen eine explizite Typumwandlung. Dazu stellen Sie dem betreffenden Ausdruck in runden Klammern den gewünschten Datentyp voran. Sollte sich zur Laufzeit herausstellen, dass die Umwandlung unmöglich ist oder dass dabei ein Überlauf auftritt, kommt es zu einer Exception. long l int i double float = = d f 10; ( int ) l ; = 3.14; = ( float ) d; Modifizierer für die Variablendeklaration (public, private, static etc.) Der Deklaration einer Variablen können verschiedene Schlüsselwörter vorangestellt werden, die die Eigenschaften bzw. die Erreichbarkeit der Variablen modifizieren. Die meisten Modifizier sind nur für Felder (fields) relevant, also für Variablen, die auf Klassenebene deklariert werden, nicht als lokale Variable innerhalb einer Methode. Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.2 Arrays ebooks.kofler Schlüsselwort Bedeutung public öffentliche Variable private private Variable protected interne Variable static statische Variable final Konstante (siehe unten) transient wird bei der Serialisierung nicht gespeichert volatile Variable für Multi-Threaded-Programmierung 27 Tabelle 2.3: Schlüsselwörter, um das Verhalten von Variablen zu modifizieren Die Schlüsselwörter public, private, protected und static werden im Kapitel Klassen genauer beschrieben. transient bewirkt, dass der Inhalt dieser Klassenvariablen bei einer Serialisierung des Objekts nicht gespeichert wird. volatile bewirkt, dass der Compiler darauf verzichtet, aus Effizienzgründen auf zwischengespeicherte Werte dieser Variable zuzugreifen. Stattdessen wird die Variable bei jedem Zugriff neu ausgelesen. In Multi-Threaded-Programmen stellt volatile sicher, dass das Lesen oder Schreiben der Variable ein atomarer Prozess ist, der nicht durch einen anderen Thread gestört werden kann. 2.2 Arrays Arrays speichern mehrere Zahlen, Zeichenketten oder andere Objekte. Die Größe des Arrays muss beim Erzeugen festgelegt werden und kann nachträglich nicht mehr verändert werden. Die übliche Syntax zur Deklaration von Arrays lautet datentyp[] var. Unüblich, aber ebenfalls erlaubt ist datentyp var[]. Wenn von vornherein klar ist, welche Daten ein Array aufnehmen soll, können Sie die Elemente in der Form {e1, e2, e3} einer Array-Variable direkt zuweisen. Der Java- Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.2 Arrays ebooks.kofler 28 Compiler kümmert sich dann selbstständig darum, das Array in der richtigen Größe zu erzeugen. int [] x ; x = new int [5]; double [] y = new double [5]; int [] z = {17 , 19 , 25 , 12}; // // // // x als int - Array d e k l a r ier en Array erzeugen y d e k l a r ieren und erzeugen z d e k l a r ieren + i n i t i a l i sie ren Der Zugriff auf die Array-Elemente erfolgt in der Form array[n], wobei n von 0 bis zur Elementanzahl -1 reicht. Die Anzahl der Array-Elemente geht aus dem Array-Feld (field) length hervor. Beachten Sie, dass length keine Methode ist! Sie dürfen daher nicht zwei runde Klammern hintanstellen, wie dies z. B. bei der gleichnamigen String-Methode erforderlich ist! for ( int i =0; i < x . length ; i ++) System . out . println ( x [ i ]) ; Arrays können auch in for-each-Schleifen durchlaufen werden. Die Array-Elemente können dann allerdings nur gelesen, nicht aber verändert werden: for ( int itm : x) System . out . println ( itm ); Mehrdimensionale Arrays Die Java-Syntax unterstützt auch mehrdimensionale Arrays. Dabei werden einfach mehrere eckige Klammernpaare aneinandergereiht, wie die folgenden Beispiele zeigen: int [][] x; x = new int [8][4]; x [2][3] = 27; double [][] y = new double [ 1 0 0 ] [ 1 00]; Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.2 Arrays ebooks.kofler int [][] z = { {1 , 2 , 3} , {4 , 5 , 6} }; System . out . println (z [1][2]) ; 29 // liefert 6 // d r e i d i m e n s ion ale s Array ( ca. 2 MByte P l a t z b e darf) short [][][] s = new short [ 1 0 0 ] [ 1 0 0 ][ 100 ]; In Wirklichkeit verwaltet Java allerdings nicht echte mehrdimensionale Arrays, sondern Arrays von Arrays (vor Arrays . . . ). Im obigen Beispiel verweist x[2] auf ein einfaches Array aus vier Elementen. Auch das length-Feld bezieht sich nur auf die Elemente der ersten Dimension. x.length liefert deswegen 8, nicht etwa 32. Dieses Konzept ermöglicht die unkomplizierte Realisierung von nicht-rechteckigen Arrays. Im folgenden Beispiel zeigt x[0] auf ein Array mit einem Element, x[1] auf ein Array mit zwei Elementen etc. int [][] x = new int [10][]; for ( int i =0; i <10; i ++) { x[ i ] = new int [ i +1]; } Abbildung 2.1: Ein nicht-rechteckiges Arrays Java auf den Punkt gebracht 2 Variablen, Arrays und Enums ebooks.kofler 2.2 Arrays 30 Das folgende Beispiel zeigt zwei Schleifenvarianten, um alle Elemente des obigen Arrays zu durchlaufen. Beachten Sie, dass bei der zweiten Variante die Schleifenvariable subarr den Typ int[] aufweist. Jedes Element, das diese Schleifenvariable durchläuft, ist also selbst ein Array. // S c h l e i f e n v ari ant e 1: Array i n i t i a l i si eren for ( int i =0; i < x . length ; i ++) { for ( int j =0; j <x [ i ]. length ; j ++) { x[ i ][ j] = i + j ; } } // S c h l e i f e n v ari ant e 2: Summe a u s r e c hnen int sum =0; for ( int [] subarr : x ) { for ( int itm : subarr ) { sum += itm ; }} System . out . println ( sum ) ; Array-Interna und -Methoden Während lokale Variablen vor ihrer Verwendung initialisiert werden müssen, gilt diese Regel nicht für die Elemente eines Arrays sowie für Klassenvariablen (fields). Diese werden beim Erzeugen des Arrays bzw. des Objekts automatisch mit 0 bzw. null initialisiert. Java-intern sind Arrays Objekte. Es existiert allerdings nicht einfach eine Array-Klasse, deren Instanzen dann Array-Objekte sind. Vielmehr sind Arrays eine Sonderform von Klassen, die tief in der Java-Sprachdefinition verankert sind. Ihr Klassenname ergibt sich aus einer oder mehreren eckigen Klammern, je nachdem, ob es sich um ein ein- oder um ein mehrdimensionales Array handelt. Den Klammern folgt bei elementaren Datentypen ein Buchstabe, z. B. I für int oder D für double, oder der vollständige Klassenname plus einem Semikolon bei Objekt-Arrays: Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.2 Arrays ebooks.kofler Datentyp Defaultwert Integerzahlen 0 Fließkommazahlen 0.0 boolean false char \u0000 (also das Unicode-Zeichen 0) Objekte inklusive String null 31 Tabelle 2.4: Defaultwerte für Array-Elemente und Klassenvariablen import java . awt . Point ; int [] x = new int [5]; System . out . println (x . getClass () . getName () ) ; double [] y = new double [5]; System . out . println (y . getClass () . getName () ) ; Point [][] pts = new Point [7][2]; System . out . println ( pts . getClass () . getName () ) ; // Ausgabe : [I [D [[ Ljava . awt . Point ; Die Array-Klassen sind nur von Object abgeleitet und bieten daher keine spezifischen Methoden. Vielmehr stellt die Klasse java.util.Arrays (beachten Sie die Endung -s!) statische Methoden zur Verfügung, mit denen Sie Arrays initialisieren, durchsuchen, sortieren etc. können. Beachten Sie aber, dass sämtliche Arrays-Methoden für eindimensionale Arrays konzipiert sind. Java auf den Punkt gebracht 2 Variablen, Arrays und Enums 2.2 Arrays ebooks.kofler 32 Methode Funktion binarySearch durchsucht ein sortiertes Array. sort sortiert ein Array. copyOfRange bildet ein neues Teil-Array. equals vergleicht den Inhalt zweier Arrays. fill initialisiert die Array-Elemente mit einem Wert. toString liefert eine lesbare Zeichenkette des Arrays (zum Debuggen und Testen). Tabelle 2.5: Ausgewählte Methoden der Klasse java.util.Arrays int [] x = {7 , 12 , 3 , 9}; Arrays . sort ( x ); System . out . println ( Arrays . toString (x ) ) ; // Ausgabe : [3 , 7 , 9 , 12] Die Methoden sort und binarySearch setzen voraus, dass es sich bei den ArrayElementen um elementare Datentypen oder um Objekte von Klassen handelt, die die Schnittstelle Comparable implementieren. Ist dies nicht der Fall, müssen Sie ein zusätzliches Comparator-Objekt angeben, das zwei Objekte vergleicht. Beispiele für die Implementierung der Comparable-Schnittstelle bzw. für eine Comparator-Klasse finden Sie in den Kapiteln Vererbung und Schnittstellen und Collections. Wenn Sie ein Array kopieren (duplizieren) möchten, verwenden Sie dazu am besten die Methode clone: int [] x = {1 , 4 , 189 , 3}; int [] y = x . clone () ; Beachten Sie aber, dass clone nur ein sogenanntes shallow copy durchführt und dabei nur die erste Dimension des Feldes berücksichtigt. Bei Objekt-Arrays werden nur die Objektreferenzen geklont, nicht die Objekte selbst. Eine clone-Kopie eines mehrdimensionalen Java auf den Punkt gebracht ebooks.kofler 2 Variablen, Arrays und Enums 2.3 Konstanten und Enums 33 Arrays liefert ein neues Array, dessen Elemente in der ersten Dimension auf dieselben Subarrays zeigen wie im ursprünglichen Array. Wie das folgende Beispiel beweist, können Arrays wie Objekte als Parameter an Methoden übergeben bzw. als Ergebnis von Methoden zurückgegeben werden: import java . util . Arrays ; int [] x = {7 , 12 , 3 , 9}; m1 (x ) ; System . out . println ( Arrays . toString (x ) ) ; // Ausgabe : [14 , 24 , 6 , 18] x = m2 (5) ; System . out . println ( Arrays . toString (x ) ) ; // Ausgabe : [0 , 1 , 2 , 3 , 4] public static void m1 ( int [] ar ) { for ( int i =0; i < ar . length ; i ++) ar [ i ] * =2; } public static int [] m2 ( int n ) { int [] ar = new int [ n ]; for ( int i =0; i < n ; i ++) ar [ i ] = i ; return ar ; } 2.3 Konstanten und Enums Konstanten Java sieht keine direkte Definition von Konstanten vor. Stattdessen kann der Deklaration von Variablen das Schlüsselwort final vorangestellt werden. Es bewirkt, dass die Variable nach der erstmaligen Initialisierung nicht mehr verändert werden kann. Versuchen Sie es dennoch, liefert javac beim Kompilieren eine Fehlermeldung. Java auf den Punkt gebracht 2 Variablen, Arrays und Enums ebooks.kofler 2.3 Konstanten und Enums 34 final double PI = 3 . 1 4 1 5 92 7; Wenn final für eine Objektvariable verwendet wird, kann die Objektreferenz nicht mehr verändert werden. Aber aufgepasst: Die Daten des Objekts können aber sehr wohl modifiziert werden! Insofern verhält sich ein finales Objekt nicht wie eine Konstante. import java . awt . Point ; ... final Point p = new Point (3 , 3) ; p = new Point (4 ,4) ; // nicht zul ä ssig p . x = 4; // erlaubt ! p . y = 4; // erlaubt ! Hinweis final kann auch bei der Deklaration von Klassen verwendet werden, hat dort aber eine andere Bedeutung: Finale Klassen können durch Vererbung nicht erweitert werden. Konstantenaufzählungen (Enums) Seit Version 5 können Sie in Java mit dem Schlüsselwort enum eine Aufzählung von Konstanten definieren. Das folgende Listing demonstriert die Anwendung einer solchen Aufzählung: // D e f i n i tion public enum Color { RED , GREEN , BLUE , YELLOW , BLACK , WHITE } // Anwendung Color c = Color . GREEN ; if( c == Color . BLACK ) System . out . println ( " schwarz ") ; Java auf den Punkt gebracht ebooks.kofler 2 Variablen, Arrays und Enums 2.3 Konstanten und Enums 35 Enums sind ein Sonderfall einer Klasse. Java-intern wird eine Enumeration durch die Vererbung von der Klasse java.lang.Enum gebildet. Der Compiler macht daher aus enum Color eine Klasse, deren Definition mit class Color extends Enum beginnt. Das ist auch der Grund, weswegen Enumerationen nicht wie Klassen durch extends erweitert werden können. (Java kennt keine Mehrfachvererbung in der Art class C extends A, B. Da jede Enumeration bereits extends Enum impliziert, würde enum F extends E zu class F extends Enum, E führen, also zu einer nicht zulässigen Mehrfachvererbung.) Enums gelten wie Klassen als Referenztypen. Allerdings sind einfache Enum-Objekte wie im obigen Beispiel so wie Strings unveränderlich (immutable). Sie verhalten sich daher in vielerlei Hinsicht wie primitive Datentypen. Die Java-Syntax bietet bei der Definition von Enums viel mehr Freiheiten als andere Programmiersprachen. Beispielsweise können Sie eine Farbaufzählung auch mit einem eigenen Konstruktor und privaten und öffentlichen Methoden ausstatten: public enum Color { RED (1 ,0 ,0), GREEN (0 ,1 ,0), BLUE (0 ,0 ,1), WHITE (1 ,1 ,1), BLACK (0 ,0 ,0), GREY (0.7 ,0.7 ,0.7); private double r , g , b ; public Color ( double r , double g , double b ) { this. r = r; this. g = g; this. b = b; } public String hexcode () { return hex ( this. r ) + hex ( this . g) + hex ( this. b ) ; } Java auf den Punkt gebracht ebooks.kofler 2 Variablen, Arrays und Enums 2.3 Konstanten und Enums private String hex ( double x ) { if (x <=0) return " 00 " ; if (x >=1) return " FF " ; return String . format (" %02 X" , ( int ) ( x * 256) ); } } ... Color c = Color . GREY; System . out . println (c . hexcode () ) ; // liefert B3B3B3 Hinweis Syntaktisch ist es möglich, Enums zu definieren, die sich kaum von gewöhnlichen Klassen unterscheiden. Das ist in selten Fällen zweckmäßig, etwa zur Implementierung des Singleton-Entwurfsmuster, wie dies in einem Stack-Overflow-Beitrag beschrieben ist. 36 12 Exceptions Exceptions (Ausnahmen) geben Auskunft über in Java-Programmen aufgetretene Fehler. Mit try-catch besteht die Möglichkeit, auf Fehler zu reagieren und das Programm fortzusetzen oder zumindest geordnet zu beenden. Eine Besonderheit des Exception-Konzepts von Java besteht darin, dass bei der Deklaration von Methoden angegeben werden kann, welche Arten von Exceptions bei deren Ausführung auftreten können. Der Aufruf von derart deklarierten Methoden muss durch try-catch abgesichert werden; alternativ besteht die Möglichkeit, die übergeordnete Methode ebenfalls so zu deklarieren, dass darin eine Exception auftreten kann. 12.1 Exception-Klassen Formal betrachtet werden die Informationen über einen Fehler in einem Objekt transportiert, dessen Klasse von Throwable abgeleitet ist. In der Java-Standardbibliothek sind Dutzende verschiedene Error- und Exception-Klassen definiert. Die folgende Abbildung gibt einen ersten Überblick: Java unterscheidet zwischen Fehlern, die im Code abgesichert werden müssen (in der obigen Abbildung blau gekennzeichnet), und solchen, bei denen die Absicherung optional ist (gelb). Welche Klasse in welche Gruppe fällt, hängt von der Erbfolge ab: Fehler, deren Klasse von Error bzw. von RuntimeException abgeleitet ist, müssen nicht abgesichert werden, alle anderen schon. Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.1 Exception-Klassen 141 Abbildung 12.1: Die wichtigsten Error- und Exception-Klassen Die Throwable-Klasse An der Spitze der Klassenhierarchie steht Throwable. Anders als der Name vermuten lässt, handelt es sich dabei um eine gewöhnliche Klasse, keine Schnittstelle. Objekte der Throwable-Klasse enthalten eine Beschreibung des Fehlers sowie Informationen darüber, wo im Code der Fehler aufgetreten ist. Dabei ist der sogenannte Stack Trace eine Liste aller Methoden, die aufgerufen wurden, bis es zum Fehler gekommen ist. Die folgende Tabelle fasst die wichtigsten Methoden zum Auslesen der Daten aus der ThrowableKlasse zusammen: Java auf den Punkt gebracht 12 Exceptions 12.1 Exception-Klassen ebooks.kofler 142 Methode Bedeutung getCause gibt die Ursache des Fehlers an (ein weiteres Throwable-Objekt). getLocalizedMessage liefert die Fehlermeldung in der Sprache des Systems. getMessage liefert die englische Fehlermeldung. getStackTrace verrät, welche Methoden aufgerufen wurden, als es zum Fehler kam. printStackTrace gibt die Stack-Trace-Daten in der Konsole aus. toString liefert eine kurze Fehlerbeschreibung. Tabelle 12.1: Wichtige Throwable-Methoden Die Error-Klassen Die von der Error-Klasse abgeleiteten Klassen beschreiben schwerwiegende Probleme, z. B. wenn der Java-Interpreter nicht genug Speicherplatz zur Ausführung des Programms findet. In den meisten Java-Programmen ist es nicht zweckmäßig und mitunter auch gar nicht möglich, auf solche Fehler in einer sinnvollen Art und Weise zu reagieren. Deswegen ist die Absicherung gegen solche Fehler optional. Die RuntimeException-Klassen Eine RuntimeException deutet in der Regel auf Programmierfehler (Schlampigkeitsfehler) hin. Die Entwickler der Sprache Java sind davon ausgegangen, dass solche Fehler bereits während der Programmentwicklung im Code behoben werden. Daher ist auch für diese Fehlerklasse eine Absicherung nicht vorgeschrieben. Ein typischer Vertreter dieser Exception-Gruppe ist die ArrayIndexOutOfBoundException: Diese tritt auf, wenn Sie beim Zugriff auf die Elemente eines Arrays einen zu großen Index verwenden, also z. B. wenn Sie die Schleife für den Index so for(i=0; i<=ar.length; i++) statt korrekt so for(i=0; i<ar.length; i++) formulieren. Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.2 try-catch 143 Hinweis Die Bezeichnung RuntimeException ist denkbar unglücklich gewählt. Jede Exception tritt zur Laufzeit auf! Gewöhnliche Exceptions Alle Exceptions, die nicht von Error oder RuntimeException abgeleitet sind, gelten als gewöhnliche Exceptions. Methoden, die solche Exceptions auslösen können, müssen mit throws entsprechend markiert werden. Der Aufruf solcher Methoden muss entweder durch try-catch abgesichert werden, oder die übergeordnete Methode muss ebenfalls mit throws XxxException markiert werden. Eine Gruppe von Fehlern, die in der Praxis besonders häufig vorkommt, ist IOException. Derartige Fehler treten auf, wenn Sie mit Dateien hantieren. Diese Art von Fehlern ist über die Basisklasse IOException zu einer eigenen Gruppe zusammengefasst. Die IOException weist gegenüber der normalen Exception keine weiteren Methoden auf; ihre Existenzberechtigung besteht darin, dass Sie damit sämtliche Ein- und Ausgabefehler in einer einzigen catch-Anweisung verarbeiten können. 12.2 try-catch Das folgende Listing zeigt beispielhaft die try-catch-Syntax zur Fehlerabsicherung. Mit try leiten Sie einen Block ein, der Java-Anweisungen enthält, die eventuell einen Fehler auslösen können. Eine Reihe von catch-Blöcken enthält Code, in dem Sie auf Fehler reagieren. Wenn es mehrere catch-Blöcke gibt, zwingt der Java-Compiler zwingt Sie dazu, zuerst die speziellen und erst danach allgemeine Exception-Klassen anzugeben; wenn Sie diese Reihenfolge missachten, tritt ein Compile-Fehler auf. Der finally-Block enthält schließlich Code, der immer ausgeführt wird, egal, ob vorher ein Fehler aufgetreten ist oder nicht. In diesem Codeblock werden üblicherweise Aufräumarbeiten durchgeführt; wenn Sie beispielsweise mit try das Lesen einer Text- Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.2 try-catch 144 datei abgesichert haben, ist finally der richtige Ort, um die Datei mit close wieder zu schließen. Sowohl catch als auch finally sind für sich optional. Die gesamte try-Konstruktion muss aber zumindest eine dieser beiden Ergänzungen aufweisen. Nur try ohne catch und ohne finally ist nicht erlaubt. try { ... // fehleranf ä lliger Java- Code ( z .\ ,B .\ f ü r I /O ) ... } catch ( F i l e N o t F o u n d Exc ep ti on e ) { ... // Datei konnte nicht gefunden werden } catch ( I O E x c e p tion e ) { ... // anderer IO- Fehler } catch ( Exception e) { ... // anderer Fehler } finally { ... // diesen Code immer ausfü hren } Ab Java 7 können Sie in einem catch-Block mehrere Exceptions gemeinsam verarbeiten: // mehrere E x c e p t ions in einem catch - Block try { ... } catch ( F i l e N o t F o u n d Exc ep ti on| U n s u p p o r t e d E n c od in gE xc ep t io n e ) { ... } Ebenfalls seit Java 7 besteht die Möglichkeit, in einem Parameter von try mehrere durch Kommata getrennte Ressourcen zu öffnen. Die dort definierten Variablen stehen in der gesamten try-catch-Konstruktion zur Verfügung. Java garantiert, dass die Ressourcen am Ende der try-catch-Konstruktion automatisch geschlossen werden – egal, ob bei der Code-Ausführung ein Fehler auftritt oder nicht. Die einzige Voraussetzung besteht darin, Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.2 try-catch 145 dass die Klassen der in try(...) geöffneten Ressourcen die Schnittstelle AutoCloseable implementieren. try ( IOKlasse1 x = new IOKlasse1 ( " d a t e i n a me1" ) , IOKlasse2 y = new IOKlasse2 ( " d a t e i n a me2" ) ) { ... // x und y benutzen } catch ( I O E x c e p tion e ) { ... // F e h l e r b e h an dlu ng } Diese try-Variante darf ohne catch und finally formuliert werden, weil es ja einen impliziten finally-Code zum Schließen der geöffneten Ressourcen gibt. Exception-Weitergabe Gewöhnliche Exceptions müssen Sie entweder durch try-catch absichern, oder die übergeordnete Methode mit throws XxxException kennzeichnen. Für die Gruppe der RuntimeExceptions gilt dies aber nicht. Was passiert also, wenn eine RuntimeException in einem nicht durch try-catch abgesicherten Codeabschnitt auftritt? In solchen Fällen bricht Java die Ausführung der aktuellen Methode ab und springt an die Stelle im Code zurück, an der die Methode aufgerufen wurde. Die Exception wird nun dort ausgelöst. Gibt es auch hier keine Fehlerabsicherung, wiederholt sich dieses Spiel durch die gesamte Liste der Methodenaufrufe (also durch den gesamten Call Stack). Sollte es bis zu main keine Fehlerabsicherung geben, wird das Programm letztlich mit einem Laufzeitfehler abgebrochen. Im folgenden Beispiel ruft main die Methode m1 auf, m1 dann m2. Dort tritt wegen einer schlampig programmierten Schleife eine ArrayIndexOutOfBoundsException auf. Da weder m2 noch m1 abgesichert sind, wird die Exception schrittweise bis zu main zurückgereicht. Dort wird der Fehler abgefangen und eine Fehlermeldung ausgegeben. Java auf den Punkt gebracht 12 Exceptions 12.2 try-catch ebooks.kofler 146 public class Test { public static void main( String [] args ) { try { m1 () ; } catch ( Exception e ) { e . p r i n t S t a c kTr ace() ; } } public static void m1 () { m2 () ; } public static void m2 () { int [] ar = {1 , 2 , 3}; int sum = 0; for ( int i =1; i <=3; i ++) sum += ar [ i ]; } // falsch ! } // F e h l e r m e ldun g: java. lang. A r r a y I n d e x O u t O fB ou nd sE x ce pt io n: 3 at testme . m2 ( testme . java :18) at testme . m1 ( testme . java :11) at testme . main ( testme . java :4) Beispiele Das folgende Beispiel zeigt, wie ein PrintStream-Objekt verwendet wird, um eine Textdatei zu schreiben. Beachten Sie, dass im finally-Block das Schließen der Datei neuerlich abgesichert ist. Die close-Methode kann ebenfalls eine IOException auslösen, z. B. wenn das Dateisystem voll ist und die Datei nicht vollständig auf dem Datenträger synchronisiert werden kann. Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.2 try-catch import java . io . * ; ... P r i n t S tre am ps = null; try { // fehleranf ä lliger Code ps = new P r i n t S t re am( new F i l e O u t p u tS tre am(" out . txt " ) , false , " UTF - 8 ") ; ps . printf ( " Zwei Zahlen % d % d\ n " , 1 , 2) ; } catch ( I O E x c e p tion e ) { // F e h l e r m el dung System . out . println ( " Fehler : " + e. g e t M e s sage() ) ; } finally { // Datei schlie ß en ( immer ) try { if ( ps != null) ps . close () ; } catch ( I O E x c e pt ion ex ) { System . out . println ( " Fehler bei close ... " ); } } Ab Java 7 lässt sich dieselbe Aufgabe wesentlich kompakter formulieren: import java . io . * ; ... try ( P r i n t S t ream ps = new P r i n t S t ream( new F i l e O u t p u tSt rea m( " out . txt " ) , false , " UTF -8 " ) ) { // fehleranf ä lliger Code ps . printf ( " Zwei Zahlen % d % d\ n " , 1 , 2) ; } catch ( I O E x c e p tion e ) { // F e h l e r m el dung System . out . println ( " Fehler : " + e. g e t M e s sage() ) ; } // das P r i n t S tream- Objekt ist hier auf jeden Fall g e s c h l ossen 147 Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.3 Fehleranfällige Methoden deklarieren (throws) 148 12.3 Fehleranfällige Methoden deklarieren (throws) Methoden, die eine gewöhnliche Exception auslösen können (also keinen Error und keine RuntimeException), müssen mit throws XxxException deklariert werden. Es dürfen auch mehrere, durch Kommata getrennte Exceptions angegeben werden. void m1 () throws F i l e N o t FoundEx ception , T i m e o u t E x ce pti on { ... // Code , der die genannten E x c e p t ions auslö sen kann } In der Java-Klassenbibliothek sind unzählige Methoden auf diese Weise deklariert – besonders häufig in den java.io-Klassen. Einige Beispiele: P r i n t S tre am K o n s t r u ktor: throws F i l e N o t FoundE xception , U n s u p p o r t e d E n co di ng Ex ce pt io n F i l e O u t p u tSt rea m K o n s t r u ktor: throws F i l e N o t F o u nd Ex ce pt ion write (...) throws I O E x c e p tion close () throws I O E x c e p tio n F i l e I n p u tS trea m: read (...) throws I O E x c e p tion Die Deklaration einer eigenen Methode mit throws Xxx hat zwei Konsequenzen: Zum einen dürfen Sie nun auf die Absicherung der betreffenden Exceptions innerhalb der Methode verzichten. Zum anderen zwingen Sie den Nutzer der Methode, sich selbst um die Fehlerabsicherung zu kümmern. Wenn Sie also beispielsweise eine Methode zum Schreiben einer Datei programmieren und dabei auf diverse java.io-Klassen und deren Methoden zurückgreifen, stehen Sie vor zwei Wahlmöglichkeiten: Sie können sich selbst um die Fehlerabsicherung kümmern (also try-catch), oder Sie können Ihre Methode mit throws IOException deklarieren Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.4 Selbst Exceptions werfen (throw) 149 und die Fehlerabsicherung an den Nutzer Ihrer Methode delegieren. In Entwicklungsumgebungen wie Eclipse können Sie sich per Mausklick für eine der beiden Varianten entscheiden. Welcher Weg ist nun der bessere? Wenn es eine Möglichkeit gibt, die Methode dennoch mit einem vernünftigen Ergebnis abzuschließen, ist try-catch natürlich der richtige Weg. Andernfalls ist es ratsamer, den Fehler weiterzuleiten und den Nutzer der Methode entscheiden zu lassen, wie er mit dem Problem umgehen möchte. In der Praxis werden Sie oft sogar beide Wege zugleich beschreiten: Einerseits verwenden Sie try-catch bzw. try-finally, um offene Ressourcen zu schließen und sonstige Aufräumarbeiten durchzuführen; andererseits geben Sie aber den Fehler auch weiter an die übergeordnete Methode, um diese so zu informieren, dass ein Problem aufgetreten ist. Tipp Vermeiden Sie eine Fehlerabsicherung im Stil try-catch(Exception e) mit einem catch-Block, der sich auf die Ausgabe des Fehlertexts in der Konsole beschränkt. Formal haben Sie damit Ihre Methode zwar gegen alle denkbaren Fehler abgesichert, tatsächlich kaschieren Sie die auftretenden Probleme aber nur. Wenn Sie in Ihrem Programm keine korrekte Möglichkeit sehen, auf einen bestimmten Fehler angemessen zu reagieren, ist es ehrlicher, die betroffene Methode mit throws XxxException zu deklarieren! 12.4 Selbst Exceptions werfen (throw) Mit throw können Sie selbst eine Exception auslösen (werfen): if(n <0) throw new I l l e g a l A r g u me nt Ex ce pt io n( " Parameter n " + " darf nicht negativ sein ! ") ; Bei nicht abgesichertem Code wird damit die aktuelle Methode verlassen und die Exception an die übergeordnete Methode weitergeleitet. Wenn Sie throw innerhalb einer Java auf den Punkt gebracht ebooks.kofler 12 Exceptions 12.4 Selbst Exceptions werfen (throw) 150 try-catch-Konstruktion auslösen, wird der Code im ersten passenden catch-Block fortgesetzt. Nach Möglichkeit sollten Sie versuchen, eine der vielen vordefinierten Exceptions aus der Java-Standardbibliothek zu verwenden. Bei Bedarf können Sie aber auch vollkommen unkompliziert eine eigene Exception-Klasse definieren: class M y E x c e p tio n extends Exception { ... }