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, was sie für Eigenschaften hat, und später auf beliebigen Objekten Methoden aufrufen und Objekt-/Klassenvariablen auslesen und modifizieren. Viele Frameworks greifen auf die Reflection-API zurück, etwa JPA für das Objekt-Relationale-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 in 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 immutable 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 das für UML-Diagramme, was HTML für Webseiten ist und SVG für Vektorgrafiken. 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 gibt 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, 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, etwa so:

1;2;3
4;5;6

Aufgabe:

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

  • Zum Extrahieren kann man entweder die öffentlichen Bean-Getter aufrufen (wenn man über Properties gehen möchte) 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 );

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 Attributen 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 Attribute 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 dem 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 );