© Holger Röder Winter 2008/2009 Programmentwicklung se Java: Kapitel 4 Fortgeschrittene Konzepte Programmentwicklung WS 2008/2009 Holger Röder [email protected] © Holger Röder Autoboxing Erweiterte for-Schleife Aufzählungen Ausnahmebehandlung Generische Klassen Reflection Annotationen Garbage Collection Programmentwicklung Winter 2008/2009 Überblick über Kapitel 4 se 2 © Holger Röder Winter 2008/2009 Programmentwicklung se Autoboxing Zu jedem primitiven Datentyp existiert in Java eine Wrapper-Klasse (int: Integer, char: Character, byte: Byte etc.), die den jeweiligen Datentyp in einem Objekt „aufnehmen“. Autoboxing: primitive Datentypen und Wrapper-Objekte werden bei Bedarf implizit ineinander umgewandelt: Integer i = 3; // Boxing, int -> Integer int j = i; // Unboxing, Integer -> int Damit sind – in Verbindung mit variablen Parameterlisten – Methoden möglich, die beliebige Parameter beliebigen Typs akzeptieren: public void print(Object... o) { for (int i = 0; i < o.length; i++) System.out.println("Parameter: "+ o[i]); } ... print("Hallo",3, 17.2, new Integer(4), 'x', false); 3 © Holger Röder Winter 2008/2009 Programmentwicklung se Erweiterte for-Schleife Seit Java 5 existiert eine erweiterte for-Schleife mit folgender Syntax: for (formalerParameter: ausdruck) anweisung; Dabei gilt: formalerParameter ist eine Parameterdeklaration, z. B. String s ausdruck implementiert die Schnittstelle java.lang.Iterable (wie z. B. die Listenklassen aus der Standardbibliothek) oder ist ein []-Array String[] texte = new String[3]; texte[0] = "Hallo "; texte[1] = "Welt"; texte[2] = "!"; /* "Neue" Variante (mit Array): */ for(String s: texte) { System.out.print(s); } 4 © Holger Röder Winter 2008/2009 Programmentwicklung se Aufzählungen Mit dem Schlüsselwort enum können Aufzählungen definiert werden: public enum Farbe { ROT, GELB, GRUEN } Aufzählungen sind Klassen, jedes Element einer Aufzählung ist ein Objekt. Farbe f1 = Farbe.GELB; Standard-Methoden erlauben das Arbeiten mit Aufzählungsklassen und Aufzählungsobjekten. int i = f1.ordinal(); // liefert 1 (ROT=0, BLAU=2) String s = f1.toString(); // liefert "GELB" boolean b = f1.equals(Farbe.GELB); // liefert true Farbe f2 = Farbe.valueOf("ROT"); // ok Farbe f3 = Farbe.valueOf("BLAU"); // Exception! 5 © Holger Röder Winter 2008/2009 Programmentwicklung se Aufzählungen in switch-Anweisungen Aufzählungen können in switch-Anweisungen unterschieden werden: Farbe f1 = Farbe.GELB; ... switch (f1) { case GELB: System.out.println("Farbe ist GELB."); break; case GRUEN: System.out.println("Farbe ist GRÜN"); break; } 6 © Holger Röder Winter 2008/2009 Programmentwicklung se Ausnahmebehandlung: Exceptions In Java werden Ausnahmen (Exceptions) zur Fehlerbehandlung verwendet. Viele Methoden der Java-Standardbibliothek „werfen“ im Fehlerfall eine Exception. Grundsätzlich gilt: Tritt eine Exception auf, muss sie entweder lokal behandelt oder weitergegeben werden. Sonderfall: Ausnahmen vom Typ RuntimeException (z. B. NullPointerException) können, müssen aber nicht behandelt oder weitergegeben werden. Für die lokale Ausnahmenbehandlung können Exceptions in einem (try-)catch-Block „gefangen“ werden: try { double d = Double.parseDouble("Hallo"); System.out.println("Alles ok!"); // nie erreicht! } catch (NumberFormatException e) { System.out.println("Ungültiges Zahlenformat"); } 7 © Holger Röder Winter 2008/2009 Programmentwicklung se Behandlung von Exceptions Verschiedene Exceptions können über nacheinander folgende catchBlöcke gefangen werden. Der Typ der geworfenen Exception wird mit dem Typ der zu fangenden Exception verglichen. Bei Übereinstimmung wird zum jeweiligen catch-Block gesprungen. Der (optionale) finally-Block wird immer aufgerufen (egal, ob eine Exception bearbeitet wurde oder nicht). try { //... Hier können Exceptions geworfen werden ... } catch(IOException e) { System.out.println("IO-Fehler: " + e.getMessage()); } catch(Exception e) { System.out.println("Fehler: " + e.getMessage()); } finally { System.out.println("Programmende"); } Anschließend fährt das Programm mit der ersten Anweisung nach dem try-catch-(finally-)-Abschnitt fort. 8 © Holger Röder Winter 2008/2009 Programmentwicklung se Weitergabe von Exceptions Wenn die Ausnahmenbehandlung nicht lokal erfolgen soll, können Exceptions an die aufrufende Methode weitergegeben werden. Dazu muss der Methodenkopf mit einer throws-Klausel und der Liste aller möglicherweise weitergegebenen Exceptions erweitert werden: import java.io.*; ... public String liesEin() throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String eingabe = in.readLine(); Wird eine Exception return eingabe; geworfen, wird diese an die aufrufende Methode } weitergegeben. Weitergegebene Exceptions müssen in der aufrufenden Methode behandelt oder ebenfalls weitergegeben werden. 9 © Holger Röder Winter 2008/2009 Programmentwicklung se Kombination von Behandlung und Weitergabe Lokale Behandlung von Ausnahmen und Weitergabe können kombiniert werden, wenn innerhalb eines catch-Blocks die gefangene Exception mit throw weitergegeben wird. public double liesZahl() throws IOException, NumberFormatException { ... try { String eingabe = in.readLine(); } catch (IOException e) { System.out.println("Fehler bei der Eingabe!"); throw e; Zunächst lokale } Fehlerbehandlung… ... } … dann Weitergabe der Exception 10 © Holger Röder Winter 2008/2009 Programmentwicklung se Eigene Exceptions Auftretende Ausnahmen werden durch Objekte vom Typ Exception (oder einer Unterklasse) repräsentiert. Eigene Ausnahmen können als Unterklassen der Klasse Exception erstellt werden. MeineException.java public class MeineException extends Exception { public int fehlerCode; ... } Exceptions können mit der throw-Anweisung geworfen werden: MeineException e = new MeineException(); e.fehlerCode = 127; throw e; 11 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Klassen Generische Klassen (Generics) können verwendet werden, um eine Implementierung, die prinzipiell alle Klassen unterstützt, typsicher auf die Verwendung mit bestimmten Klassen zu beschränken. Beispiel: Datenspeicher für 1 Element IntegerSpeicher.java public class IntegerSpeicher { private Integer element; public void put(Integer e) { element = e; } public Integer get() { return element; } } StringSpeicher.java public class StringSpeicher { private String element; public void put(String e) { element = e; } public String get() { return element; } } Doppelter Code! 12 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Klassen (2) Implementierung als generische Speicher.java Klasse: public class Speicher<T> { private T element; T dient als Platzhalter public void put(T e) { und wird bei der Deklaration und element = e; Initialisierung durch einen } konkreten Typ ersetzt. public T get() { return element; } } Aufruf: Speicher<Integer> i = new Speicher<Integer>(); i.put(new Integer(42)); Speicher<String> s = new Speicher<String>(); s.put("Ein String"); // s.put(new Integer(42)); // unzulässig, nur Strings! 13 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Klassen – Polymorphie Erinnerung: Polymorphie bei „normalen“ Klassen class A { ... } class B extends A { ... } // abgeleitete Klasse ... B b_obj = new B(); A a_obj = b_obj; // zulässig, Polymorphie Eine analoge Zuweisung mit generischen Klassen, die mit Ober- und Unterklassen typisiert sind, ist jedoch nicht zulässig: Speicher<B> b_speicher = new Speicher<B>; Speicher<A> a_speicher = b_speicher; // nicht zulässig! Wäre die letzte Anweisung zulässig, könnte beispielsweise folgende Typinkompatibilität auftreten: a_speicher.put(new A()); B obj1 = b_speicher.get(); /* B-Objekt erwartet, aber A-Objekt im Speicher! */ 14 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Klassen – Einschränkungen Die Information über die Typisierung generischer Klassen ist zur Laufzeit nicht bekannt (Type Erasure). Bei der Verwendung gibt es deshalb Einschränkungen. Der instanceof-Operator ist bei generischen Klassen nur zur Prüfung der untypisierten generischen Klasse erlaubt. if (b_speicher instanceof Speicher) { ... } // erlaubt! if (b_speicher instanceof Speicher<B>) { ... } // nicht erlaubt! Ebenso gilt: Parametrisierte Exception-Typen sind nicht erlaubt. Arrays parametrisierter Typen (Speicher<B>[] …) sind nicht erlaubt. 15 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Klassen – Wildcards Die Wildcard ? kann bei der Typisierung generischer Klassen anstelle eines konkreten Typs angegeben werden: Speicher<B> b_speicher = new Speicher<B>; Speicher<?> o_speicher = b_speicher; Bei Verwendung der Wildcard ? sind Methoden, die einen generisch typisierten Rückgabewert haben, nur noch mit Object typisiert: Object obj2 = o_speicher.get(); // ok, liefert Object B obj3 = o_speicher.get(); // nicht ok! Der Aufruf von Methoden mit generisch typisierten Parametern ist nicht mehr erlaubt: o_speicher.put(new Object()); // nicht zulässig! 16 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Klassen – Bounded Wildcards Gebundene Wildcards (bounded wildcards) erlauben die Typisierung mit der angegebenen Klasse oder einer Unterklasse: Speicher<B> b_speicher = new Speicher<B>; Speicher<? extends A> a_speicher = b_speicher; Der Aufruf von Methoden mit generisch typisierten Parametern (z. B. Schreiboperationen) ist ebenfalls nicht erlaubt: A obj4 = a_speicher.get(); // Lesen ok a_speicher.put(new A()); // Schreiben nicht ok! Mit dem Schlüsselwort super können gebundene Wildcards mit unteren Grenzen definiert werden: Speicher<? super B> speicher = ... /* kann mit B oder Oberklasse von B typisiert werden */ 17 © Holger Röder Winter 2008/2009 Programmentwicklung se Generische Methoden Auch einzelne Methoden können Typen generisch vorschreiben: Auswahl.java public class Auswahl { public static <T> T zufall(T arg1, T arg2) { // zufällig einen der beiden Parameter zurückgeben return Math.random() > 0.5 ? arg1 : arg2; } } ... String s = Auswahl.zufall("Hallo", "Welt"); // ok Integer i = Auswahl.zufall(2, 3); // auch ok Integer i = Auswahl.zufall("Hallo", "Welt"); // Fehler! 18 © Holger Röder Das Reflection-API ermöglicht den Umgang mit Klassen und Objekten zur Laufzeit. Dazu gehören etwa: das Laden und Instanziieren von Klassen, der Aufruf von Methoden und der Zugriff auf Attribute. Reflection kommt häufig bei der Entwicklung generischer, hochkonfigurierbarer oder durch Plug-Ins erweiterbarer Anwendungen zum Einsatz (z. B. OR-Datenbank-Mapper etc.) Programmentwicklung Winter 2008/2009 Reflection se 19 © Holger Röder Winter 2008/2009 Programmentwicklung se Reflection – Class-Objekte Die Klasse Class repräsentiert eine Java-Klasse zur Laufzeit und ermöglicht den Zugriff auf Eigenschaften der Klasse. Class-Objekte können auf verschiedene Weise erhalten werden: Die Methode getClass() der Klasse Object liefert ein passendes Class-Objekt für das jeweilige Objekt. String s = "Hallo"; Class c = s.getClass(); System.out.println(c.getName()); // Ausgabe: java.lang.String Jede Klasse besitzt ein statisches Class-Attribut namens class: Class c = String.class; Die statische Methode Class.forName() liefert das ClassObjekt für die angegebene Klasse zurück: Class c = Class.forName("java.lang.String"); Analog zu Class existieren die Klasse Method für Methodenzugriff und Field für Attributzugriff. 20 © Holger Röder Winter 2008/2009 Programmentwicklung se Reflection – Beispiel public static void printMethods(String className) throws ClassNotFoundException { Sucht Klasse anhand Class c = Class.forName(className); des Klassennamens for (Method m : c.getMethods()) { Liefert alle System.out.print(m.getReturnType(). Methoden getSimpleName() + " "); der Klasse System.out.print(m.getName() + "("); String delim = ""; for (Class param : m.getParameterTypes()) { System.out.print(delim); System.out.print(param.getSimpleName()); delim = ", "; } System.out.println(")"); } } 21 © Holger Röder Winter 2008/2009 Programmentwicklung se Reflection – Beispiel (Ausgabe) printMethods("java.lang.String"); liefert int hashCode() int compareTo(String) int compareTo(Object) int indexOf(int, int) int indexOf(int) int indexOf(String) int indexOf(String, int) boolean equals(Object) String toString() char charAt(int) int codePointAt(int) int codePointBefore(int) int codePointCount(int, int) int compareToIgnoreCase(String) String concat(String) boolean contains(CharSequence) boolean contentEquals(StringBuffer) boolean contentEquals(CharSequence) ... 22 © Holger Röder Winter 2008/2009 Programmentwicklung se Annotationen Annotationen sind Metadaten, die im Quellcode dokumentiert werden. Sie können zum „Kommentieren“ des Codes verwendet werden, sind aber – anders als normale Kommentare – Teil des Syntaxbaums. Es existiert eine Reihe vordefinierter Annotationen, z. B. @Deprecated – kennzeichnet Klasse/Methode als „veraltet“ @Override – kennzeichnet überlagerte Methoden Beispiel: public class Person { public double berechneGehalt() { ... } } public class Angestellter extends Person { @Override public double berechnGehalt() { ... } } Compiler meldet Fehler: die definierte Methode überlagert keine Methode der Oberklasse! 23 © Holger Röder Winter 2008/2009 Programmentwicklung se Eigene Annotationen Die Definition eigener Annotationstypen erfolgt ähnlich wie die Definition von Schnittstellen. Damit Annotationen zur Laufzeit verfügbar sind, muss die Annotationsklasse selbst entsprechend annotiert werden: @Retention(RetentionPolicy.RUNTIME) import java.lang.annotation.*; // für Reflection (Zugriff zur Laufzeit): @Retention(RetentionPolicy.RUNTIME) public @interface Autor { String vorname(); // Elemente des Annotationstyps String nachname(); String firma() default "Firma XYZ"; // Standardwert } 24 © Holger Röder Nach der Definition können eigene Annotationen verwendet werden. Die Elemente der Annotation werden über Schlüssel-Wert-Paare zugewiesen. public class Person { @Autor (vorname = "Carl", nachname = "Coder") public double berechneGehalt() { ... } } Programmentwicklung Winter 2008/2009 Eigene Annotationen (2) se 25 © Holger Röder Winter 2008/2009 Programmentwicklung se Annotationen und Reflection Annotationen können per Reflection abgefragt werden: Person p = new Person(); for (Method m : p.getClass().getMethods()) { if (m.isAnnotationPresent(Autor.class)) { Autor autor = m.getAnnotation(Autor.class); System.out.println("Methode '" + m.getName() + "' entwickelt von " + autor.vorname() + " " + autor.nachname()); } } Ausgabe: Methode 'berechneGehalt' entwickelt von Carl Coder 26 © Holger Röder Winter 2008/2009 Programmentwicklung se Garbage Collection in Java Java besitzt ein automatisches Speichermanagement. Speicher muss nicht explizit allokiert oder freigegeben werden. Objekte, die nicht mehr referenziert werden, werden automatisch (irgendwann) vom Garbage Collector gelöscht. Der Garbage Collector läuft mit niedriger Priorität im Hintergrund. Person carl = new Person(); // 1 Referenz auf das Objekt Person p1 = carl // jetzt 2 Referenzen auf das Objekt p1 = null; // nur noch 1 Referenz auf das Objekt carl = null; // Objekt ohne Referenz Vor dem Löschen eines Objekts wird jeweils die Destruktor-Methode finalize() des Objekts aufgerufen. Der Garbage Collector kann explizit gestartet werden: System.gc(); 27