I am currently working on an English translation. If you like to help to proofread please contact me: ullenboom ät g m a i l dot c o m.

Java Videotraining Werbung

1. Reflection, Annotationen und JavaBeans

Reflection bietet uns die Möglichkeit, in ein laufendes Java-Programm hineinzuschauen. Wir können eine Klasse fragen, welche Eigenschaften sie hat, und später auf beliebigen Objekten Methoden aufrufen und Objekt- bzw. Klassenvariablen auslesen und modifizieren. Viele Frameworks greifen auf die Reflection-API zurück, etwa JPA für das objektrelationale Mapping oder JAXB für die Abbildung von Java-Objekten auf XML-Bäume. Wir werden einige Beispiele selbst programmieren, die ohne Reflection nicht möglich wären.

Annotationen sind eine Art selbst programmierte Modifizierer. Mit ihnen können wir den Quellcode Metadaten anreichern, die später über Reflection oder ein anderes Werkzeug ausgelesen werden können. Oftmals sind wir nur Nutzer fremder Annotationen, in diesem Kapitel soll allerdings auch geübt werden, wie man selbst eigene Annotationstypen schreibt.

Voraussetzungen

  • Class-Typ kennen

  • Typbeziehungen zur Laufzeit auslesen können

  • Eigenschaften von Objekten zur Laufzeit ansprechen können

  • Annotationen auslesen können

  • neue Annotationstypen deklarieren können

Verwendete Datentypen in diesem Kapitel:

1.1. Reflection-API

Mit der Reflection-API lassen sich beliebige Objekte untersuchen und die folgenden Aufgaben nutzen das, um UML-Diagramme von beliebigen Datentypen zu erzeugen. Die Aufgaben konzentrieren sich auf realistische Einsatzfelder; man kann mit der Reflection-API auch viel Unsinn anstellen, etwa von immutablen Strings die Zeichen ändern, aber das ist dumm, und so etwas wollen wir nicht machen.

1.1.1. UML-Klassendiagramm mit Vererbungsbeziehungen erstellen ⭐

UML-Diagramme sind in der Dokumentation von Systemen sehr praktisch. Einige UML-Diagramme lassen sich auch durch Werkzeuge automatisch generieren. Wir wollen so ein Werkzeug selber schreiben. Ausgangspunkt ist dabei eine beliebige Klasse, die per Reflection untersucht wird. Wir können alle Eigenschaften dieser Klasse auslesen und ein UML-Diagramm generieren.

Da UML-Diagramme grafisch sind, stellt sich natürlich die Frage, wie wir in Java Grafiken zeichnen können. Dieses Problem wollen wir nicht lösen, sondern die Beschreibungssprache PlantUML (https://plantuml.com/) nutzen. PlantUML ist quasi das für UML-Diagramme, was HTML für Webseiten und SVG für Vektorgrafiken ist. Ein Beispiel:

interface Serializable << interface >>
Radio ..|> Serializable
ElectronicDevice --|> Radio

Die Pfeile --|> oder <|-- sind regulär dargestellt, ..|> oder <|.. gestichelt.

PlantUML generiert aus diesen Textdokumenten eine Darstellung der folgenden Art:

PlantUmlExample1 UML
Abbildung 1. Repräsentation der PlantUML Syntax als Grafik

PlantUML ist quelloffen und man kann ein Kommandozeilenprogramm installieren, das die textuelle Beschreibung in eine Grafik mit dem UML-Diagramm konvertiert. Es gibt auch Webseiten wie https://www.planttext.com/, die UML-Diagramme live darstellen können.

Aufgabe:

  • Generiere für eine beliebige Klasse, von der nur der voll qualifizierte Name gegeben ist, einen PlantUML-Diagramm-Text, und gib den Text auf der Konsole aus.

    • Das Diagramm soll den Typ und seine Basistypen (Oberklassen und implementierte Schnittstellen) zeigen.

    • Das Diagramm soll rekursiv auch die Typen der Oberklassen mit aufführen.

Beispiel:

  • Für Class.forName("java.awt.Point") könnte die Ausgabe so aussehen:

    Point2D <|-- Point
    Object <|-- Point2D
    Cloneable <|.. Point2D
    Serializable <|.. Point

1.1.2. UML-Klassendiagramm mit Eigenschaften erstellen ⭐

In PlantUML können nicht nur die Typbeziehungen — wie Vererbung, Implementierung von Schnittstellen und Assoziationen — beschrieben werden, sondern auch Objekt-/Klassenvariablen und Methoden:

class Radio {
isOn: boolean
isOn() : boolean
{static} format(number: int): String
}

Das Ergebnis wird etwa so aussehen:

PlantUmlExample2 UML
Abbildung 2. Repräsentation der PlantUML Syntax als Grafik

Aufgabe:

  • Schreibe eine Methode, die ein beliebiges Class-Objekt bekommt und als Ergebnis einen mehrzeiligen String in der PlantUML-Syntax liefert.

  • Es reicht, nur die Objekt-/Klassenvariablen, Konstruktoren und Methoden aufzunehmen, nicht die Typbeziehungen.

Beispiel:

  • Für den Typ java.awt.Dimension könnte die Ausgabe so aussehen:

    @startuml
    class Dimension {
       + width: int
       + height: int
       - serialVersionUID: long
       + Dimension(arg0: Dimension)
       + Dimension()
       + Dimension(arg0: int, arg1: int)
       + equals(arg0: Object): boolean
       + toString(): String
       + hashCode(): int
       + getSize(): Dimension
       - initIDs(): void
       + setSize(arg0: Dimension): void
       + setSize(arg0: double, arg1: double): void
       + setSize(arg0: int, arg1: int): void
       + getWidth(): double
       + getHeight(): double
    }
    @enduml

1.1.3. CSV-Dateien aus Listeneinträgen generieren ⭐⭐

In einer CSV-Datei werden die Einträge durch Komma oder Semikolon getrennt, das sieht etwa so aus:

1;2;3
4;5;6

Aufgabe:

  • Schreibe eine statische Methode writeAsCsv(List<?> objects, Appendable out), die alle Objekte der Liste abläuft, per Reflection alle Informationen extrahiert und dann die Ergebnisse im CSV-Format in den übergebenen Ausgabestrom schreibt.

  • Zum Extrahieren können wir entweder die öffentlichen Bean-Getter aufrufen (wenn wir über Properties gehen möchten) oder auf die (internen) Objektvariablen zugreifen — die Lösung kann eine der beiden Varianten nutzen.

Beispielnutzung:

Point p = new Point( 1, 2 );
Point q = new Point( 3, 4 );
List<?> list = Arrays.asList( p, q );
Writer out = new StringWriter();
writeAsCsv( list, out );
System.out.println( out );

Bonus: Nutzt man Zugriffe auf Objektvariablen, sollen die mit dem Modifizierer transient markierten Objektvariablen nicht geschrieben werden.

1.2. Annotationen

Mit Annotationen können wir Metadaten in den Java-Code einbringen, die später — in der Regel über Reflection — ausgelesen werden können. Annotationen sind sehr wichtig geworden, denn vieles drücken Entwickler heute deklarativ aus und überlassen dem Framework die eigentliche Ausführung.

1.2.1. CSV-Dokumente aus annotierten Objektvariablen erzeugen ⭐⭐

Gegeben ist eine Klasse mit Annotationen:

@Csv
class Pirate {
  @CsvColumn String name;
  @CsvColumn String profession;
  @CsvColumn int height;
  @CsvColumn( format = "### €" ) double income;
  @CsvColumn( format = "###.00" ) Object weight;
  String secrets;
}

Aufgabe:

  • Deklariere die Annotation @Csv, die man nur an Typendeklarationen setzen kann.

  • Deklariere die Annotation @CsvColumn, die man nur an Objektvariablen setzen kann.

  • Erlaube bei @CsvColumn ein String-Attribut format, für ein Pattern, das die Formatierung der Zahl über ein DecimalFormat-Muster steuert.

  • Lege eine Klasse CsvWriter mit einem Konstruktor an, der sich ein Class-Objekt als Typ-Token merkt und auch einen Writer, in den später die CSV-Zeilen geschrieben werden. Die Klasse CsvWriter kann AutoCloseable sein.

  • Lege CsvWriter als generischen Typ CsvWriter<T> an.

  • Schreibe zwei neue Methoden

    • void writeObject( T object ): Schreibt ein Objekt

    • void write( Iterable<? extends T> iterable ): Schreibt mehrere Objekte

  • Das Trennzeichen für die CSV-Spalten ist standardmäßig ';', soll aber über eine Methode delimiter(char) geändert werden können.

  • Überlege, welche Fehlerfälle auftreten können und melde sie als ungeprüfte Ausnahme.

Beispielnutzung:

Pirate p1 = new Pirate();
p1.name = "Hotzenplotz";
p1.profession = null;
p1.height = 192;
p1.income = 124234.3234;
p1.weight = 89.10;
p1.secrets = "kinky";

StringWriter writer = new StringWriter();
try ( CsvWriter<Pirate> csvWriter =
          new CsvWriter<>( Pirate.class, writer ).delimiter( ',' ) ) {
  csvWriter.writeObject( p1 );
  csvWriter.writeObject( p1 );
}
System.out.println( writer );

1.3. Proxy-Objekte

Proxy Objekte spielen im Jahre Universum eine große Rolle, und die nächste Aufgabe soll zeige, wie Proxies nützlich eingesetzt werden können.

1.3.1. Webressourcen erfragen

Gegeben ist folgende Schnittstelle mit annotierten Methoden:

interface Wikipedia {
  @Url( "https://www.wikipedia.org/" )
  String homepage();

  @Url( "https://en.wikipedia.org/wiki/Portal:Current_events" )
  String news();
}

Aufgabe:

  • Nutze java.lang.reflect.Proxy und den java.lang.reflect.InvocationHandler, um von einer gegebenen Schnittstelle (wie Wikipedia) zur Laufzeit ein Proxy-Objekt zu generieren. Wenn eine Methode der Schnittstelle aufgerufen wird, soll die URL der Annotation ausgewertet, der Inhalt der Webseite geladen und zurückgegeben werden.

  • Achte darauf, dass alle aufgerufen Methoden mit Url annotiert sein müssen.

  • Wird @Url eingesetzt, sind Default-Methoden nicht zulässig, auch müssen die Methoden den Rückgabetyp String besitzen.

  • Es ist nicht verboten, wenn es statische oder default-Methoden in der Schnittstelle gibt, nur können sie dann über den Proxy nicht aufgerufen werden.

Beispiel:

  • Der Proxy-Generator ist hinter der statischen UrlRessourceLoader-Methode get(…​) versteckt:

    System.out.println( UrlRessourceLoader.get( Wikipedia.class ).homepage() );
    System.out.println( UrlRessourceLoader.get( Wikipedia.class ).news() );

    Die Ausgabe könnte beginnen mit

    <!DOCTYPE html>
    <html lang="en" class="no-js">
    <head>
    <meta charset="utf-8">
    <title>Wikipedia</title>
    …