Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 17 Typen, Reflection und Annotationen
Pfeil 17.1 Metadaten
Pfeil 17.1.1 Metadaten durch Javadoc-Tags
Pfeil 17.2 Metadaten der Typen mit dem Class-Objekt
Pfeil 17.2.1 An ein Class-Objekt kommen
Pfeil 17.2.2 Eine Class ist ein Type
Pfeil 17.3 Klassenlader
Pfeil 17.3.1 Das Verzeichnis jre/lib/endorsed *
Pfeil 17.3.2 Die Klasse java.lang.ClassLoader
Pfeil 17.3.3 Hot Deployment mit dem URL-Classloader *
Pfeil 17.4 Metadaten der Typen mit dem Class-Objekt
Pfeil 17.4.1 Der Name des Typs
Pfeil 17.4.2 Was das Class-Objekt beschreibt *
Pfeil 17.4.3 instanceof mit Class-Objekten *
Pfeil 17.4.4 Oberklassen finden *
Pfeil 17.4.5 Implementierte Interfaces einer Klasse oder eines Interfaces *
Pfeil 17.4.6 Modifizierer und die Klasse Modifier *
Pfeil 17.4.7 Die Arbeit auf dem Feld *
Pfeil 17.5 Attribute, Methoden und Konstruktoren
Pfeil 17.5.1 Reflections – Gespür für die Attribute einer Klasse
Pfeil 17.5.2 Schnittstelle Member für Eigenschaften
Pfeil 17.5.3 Field-Klasse
Pfeil 17.5.4 Methoden einer Klasse erfragen
Pfeil 17.5.5 Properties einer Bean erfragen
Pfeil 17.5.6 Konstruktoren einer Klasse
Pfeil 17.5.7 Annotationen
Pfeil 17.6 Objekte erzeugen und manipulieren
Pfeil 17.6.1 Objekte erzeugen
Pfeil 17.6.2 Die Belegung der Variablen erfragen
Pfeil 17.6.3 Eine generische eigene toString()-Methode *
Pfeil 17.6.4 Variablen setzen
Pfeil 17.6.5 Bean-Zustände kopieren *
Pfeil 17.6.6 Private Attribute ändern
Pfeil 17.6.7 Methoden aufrufen
Pfeil 17.6.8 Statische Methoden aufrufen
Pfeil 17.6.9 Dynamische Methodenaufrufe bei festen Methoden beschleunigen *
Pfeil 17.6.10 java.lang.reflect.Parameter
Pfeil 17.7 Eigene Annotationstypen *
Pfeil 17.7.1 Annotationen zum Laden von Ressourcen
Pfeil 17.7.2 Neue Annotationen deklarieren
Pfeil 17.7.3 Annotationen mit genau einem Attribut
Pfeil 17.7.4 Element-Wert-Paare (Attribute) hinzufügen
Pfeil 17.7.5 Annotationsattribute vom Typ einer Aufzählung
Pfeil 17.7.6 Felder von Annotationsattributen
Pfeil 17.7.7 Vorbelegte Attribute
Pfeil 17.7.8 Annotieren von Annotationstypen
Pfeil 17.7.9 Deklarationen für unsere Ressourcen-Annotationen
Pfeil 17.7.10 Annotierte Elemente auslesen
Pfeil 17.7.11 Auf die Annotationsattribute zugreifen
Pfeil 17.7.12 Komplettbeispiel zum Initialisieren von Ressourcen
Pfeil 17.7.13 Mögliche Nachteile von Annotationen
Pfeil 17.8 Zum Weiterlesen
 
Zum Seitenanfang

17.7Eigene Annotationstypen * Zur vorigen ÜberschriftZur nächsten Überschrift

Die in der Java-Standardbibliothek vorgegebenen Annotationen haben entweder eine besondere Semantik für den Compiler, wie @Override oder @SuppressWarnings, oder dienen zum Beispiel der Definition von Web-Services (@WebService, @WebMethod, …) oder von Komponenten mit XML-Abbildung (@XmlRootElement, @XmlElement, …). Insbesondere die Java Enterprise Edition (Java EE) macht von Annotationen fleißig Gebrauch, und es lassen sich auch neue Annotationstypen deklarieren.

 
Zum Seitenanfang

17.7.1Annotationen zum Laden von Ressourcen Zur vorigen ÜberschriftZur nächsten Überschrift

Im Folgenden wollen wir drei Annotationstypen deklarieren, die den Inhalt von Objektvariablen beschreiben. Zunächst werden die Annotationstypen selbst beschrieben, und abschließend folgt eine Klasse, die die Annotationen ausliest und die Ressourcen initialisiert.

Es soll möglich sein, mit @CurrentDateResource eine Objektvariable mit dem aktuellen Datum zu belegen:

@CurrentDateResource
public Date now;

Ist eine Variable mit @ListOfFilesResource annotiert, so sollen alle Dateien und Unterverzeichnisse aus einem gegebenen Verzeichnis aufgelistet und damit ein Feld initialisiert werden:

@ListOfFilesResource( "c:/" )
String[] files;

Die Annotation @UrlResource ist die komplexeste Annotation. Sie beschreibt im einfachsten Fall eine URL mit Daten von einem HTTP-Server (mit dem URL-Protokoll file:// auch vom lokalen Dateisystem), sodass eine Variable mit dem Inhalt initialisiert werden kann:

@UrlResource( "http://tutego.de/javabuch/aufgaben/bond.txt" )
String testFile;

Der Annotation lassen sich noch einige Attribute (Element-Wert-Paare) übergeben, sodass etwa Leerraum entfernt wird oder der String in Groß-/Kleinbuchstaben konvertiert wird:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
trim = true,
upperLowerCase = UpperLowerCase.UPPERCASE )
public String testFile;

Zu guter Letzt lassen sich bei @UrlResource auch beliebige Konvertierer-Klassen angeben, die den Text der Ressource transformieren:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
converter = { RemoveNoWordCharactersConverter.class,
SortConverter.class } )
public String testFile;
 
Zum Seitenanfang

17.7.2Neue Annotationen deklarieren Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Annotationstyp (engl. annotation type) wird so deklariert wie eine Schnittstelle, nur steht vor dem Schlüsselwort interface ein @-Zeichen.

Beginnen wir mit dem einfachsten Annotationstyp, CurrentDateResource:

public @interface CurrentDateResource { }

Die Ähnlichkeit von neuen Annotationstypen und Schnittstellen ist so groß, dass in der Java Language Specification die Annotationen auch im Kapitel über Schnittstellen behandelt werden. (Später erfahren wir den Grund dafür: Der Compiler übersetzt die Annotationstypen in Schnittstellen.)

Wo sich der Annotationstyp festmachen lässt, kann eingeschränkt werden. Im Standardfall kann er überall angeheftet werden, das heißt an beliebigen Typdeklarationen, Annotationen, Aufzählungen, Objekt-/Klassenvariablen, lokalen Variablen, Parametern, Methoden, Konstruktoren oder auch an Paketen (wobei die Syntax da etwas anders ist).

Damit ist Folgendes erlaubt:

@CurrentDateResource public Date now;
 
Zum Seitenanfang

17.7.3Annotationen mit genau einem Attribut Zur vorigen ÜberschriftZur nächsten Überschrift

Der Annotationstyp @CurrentDateResource kann mit keinem zusätzlichen Attribut versehen werden, da er in der bisherigen Schreibweise eine Markierungsannotation ist. Erlaubt sind zwar ein Paar runde Klammern hinter dem Namen und auch Kommentare, aber eben kein zusätzliches Attribut, wie es @ListOfFilesResource etwa wünscht:

@CurrentDateResource public Date now;
@CurrentDateResource() public Date now;
@CurrentDateRessource( "gestern" ) public Date now; // Compilerfehler

Damit zusätzliche Informationen für den Pfadnamen bei @ListOfFilesResource("c:/") möglich sind, werden im Annotationstyp Deklarationen für Attribute eingesetzt, deren Schreibweise an Operationen einer Java-Schnittstelle erinnert. (Aber die Operationen dürfen keinen Parameter besitzen, die Rückgabe darf nicht void sein und kein throws besitzen. Und Operationen, die so heißen wie die Methoden aus Object, sind nicht zugelassen.)

Damit ein zusätzliches Attribut den Pfadnamen annehmen kann, sieht die Deklaration des Annotationstyps ListOfFilesResource so aus:

public @interface ListOfFilesResource {
String value();
}

Damit haben wir den zweiten Annotationstyp aus unserem Beispiel vorbereitet, und gültig wäre:

@ListOfFilesResource( "c:/" )
String[] files;

Fehlt das erwartete Element, also der Pfad-String, gibt es einen Compilerfehler.

Attributtypen

Das, was so wie ein Rückgabetyp einer Methode aussieht, bestimmt den Typ des Attributs und ist im begrenzten Rahmen wählbar. Der Typ muss nicht immer nur String sein. Insgesamt erlaubt Java:

  • alle primitiven Datentypen (byte, short, int, long, float, double, boolean), aber keine Wrapper

  • String

  • Class. Insbesondere mit der generischen Angabe ermöglicht er eine präzise Klassenangabe.

  • Aufzählungstypen

  • andere Annotationen (was zu geschachtelten Annotationen führt)

  • Felder von oben genannten Typen. Felder von Feldern (mehrdimensionale Felder) sind aber nicht gestattet.

[»]Hinweis

Die Attribute sind typisiert, und fehlerhafte Typen lehnt der Compiler ab. null ist als Argument nie erlaubt. Mögliche Typkonvertierungen führt der Compiler automatisch durch:

@ListOfFilesResource( "" ) String[] files; // OK
@ListOfFilesResource() String[] files; // Compilerfehler
@ListOfFilesResource( null ) String[] files; // Compilerfehler
@ListOfFilesResource( 1 ) String[] files; // Compilerfehler
@ListOfFilesResource( 'C' ) String[] files; // Compilerfehler
@ListOfFilesResource( "C:" + '/' ) String[] files; // OK
 
Zum Seitenanfang

17.7.4Element-Wert-Paare (Attribute) hinzufügen Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn der Annotationstyp ein Attribut mit dem Namen value deklariert, so muss keine Angabe über einen Schlüsselnamen gemacht werden. Möglich wäre das aber schon, und geschrieben würde das so:

@ListOfFilesResource( value = "c:/" )
String[] files;

Eine Annotation lässt sich mit einer beliebigen Anzahl an Attributen deklarieren, und das Attribut muss auch nur dann value heißen, wenn der Schlüssel nicht ausdrücklich genannt werden soll – also @ListOfFilesResource("c:/") statt @ListOfFilesResource(value = "c:/"). Ist mehr als ein Attribut nötig, muss ohnehin immer der Attributname zusammen mit der Belegung genannt werden.

Wenn @ListOfFilesResource mit einem Attribut trim ausgestattet wird, sodass die gelesenen Texte automatisch vorne und hinten den Weißraum abgeschnitten bekommen, so könnte die Deklaration des Annotationstyps so aussehen:

public @interface UrlResource {
String value();
boolean trim();
}

Und in der Anwendung:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
trim = true )
String testFile;
 
Zum Seitenanfang

17.7.5Annotationsattribute vom Typ einer Aufzählung Zur vorigen ÜberschriftZur nächsten Überschrift

Bisher haben wir als Attributtyp String und boolean eingesetzt. Attribute dürfen auch Aufzählungen sein. Wir wollen das für @UrlResource nutzen, damit wir beim Einlesen wählen können, ob der Text in Groß- oder Kleinbuchstaben konvertiert wird:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
upperLowerCase = UpperLowerCase.UPPERCASE )
String testFile;

Für die Konvertierungsart deklarieren wir zunächst eine Aufzählung und deklarieren das Attribut upperLowerCase dann genau mit dem Aufzählungstyp:

public @interface UrlResource {
public enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }

String value();
UpperLowerCase upperLowerCase();
}

Die Aufzählung UpperLowerCase als inneren Typ zu deklarieren, ist interessant, da sie ja nicht allgemein ist, sondern ausschließlich mit der Annotation @UrlResource Sinn ergibt.

 
Zum Seitenanfang

17.7.6Felder von Annotationsattributen Zur vorigen ÜberschriftZur nächsten Überschrift

Von den unterschiedlichen Elementtypen dürfen eindimensionale Felder gebildet werden. Da es keine anderen Sammlungen gibt, stellt das Feld die einzige Möglichkeit dar, beliebig viele Elemente anzugeben.

Der @UrlResource sollen beliebig viele Konvertierungsfilter zugewiesen werden. Konvertierungsfilter sind Klassen, die die Schnittstelle ResourceConverter implementieren und den eingelesenen String transformieren. Dann heißt es in der Deklaration des Annotationstyps:

public @interface UrlResource {
String value();
Class<? extends ResourceConverter>[] converter();
}

Der interessante Teil ist natürlich Class<? extends ResourceConverter>[]. Der setzt sich wie folgt zusammen:

  • Da Java es nicht erlaubt, dass beliebige Attributtypen verwendet werden, bleiben bei der Angabe der Konverter nur Class-Objekte und nicht etwa ResourceConverter[].

  • Die Typangabe Class[] wäre nicht ausreichend, da Class mit einem generischen Typ präzisiert werden muss. Jetzt ist aber Class<ResourceConverter> auch noch nicht präzise, denn wir wollen ja nicht nur exakt den Typ RessourceConverter treffen, sondern Untertypen, also Klassen, die RessourceConverter erweitern. Damit sind wir bei Class<? extends ResourceConverter>.

  • Da es eine Liste von Class-Angaben werden kann, muss das Paar eckiger Klammen an die Deklaration.

Weisen wir zum Beispiel zwei Konverter – die Klassen wurden noch nicht vorgestellt, aber das folgt – der @UrlResource zu:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
converter = { RemoveNoWordCharactersConverter.class,
SortConverter.class } )
public String testFile;

Bei nur einem angegebenen Konverter können die geschweiften Klammern sogar entfallen:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
converter = RemoveNoWordCharactersConverter.class )

[»]Hinweis

Da ein Attribut wieder eine Annotation sein kann, ergeben sich interessante Möglichkeiten. Nehmen wir an, der Annotationstyp Name speichert Vor- und Nachnamen:

public @interface Name {
String firstname();
String lastname();
}

Ein Annotationstyp Author soll Name als Elementtyp für value nutzen:

public @interface Author {
Name[] value();
}

Vor Name steht nicht das @-Zeichen. Nur in der Anwendung:

@Author( @Name( firstname = "Christian", lastname = "Ullenboom" ) )

Hätten wir das Element nicht value, sondern etwa name genannt, müsste die Angabe so heißen:

name = @Name( firstname = "Christian", lastname = "Ullenboom" )

Und hätten wir mehrere Autoren angegeben, würden wir Folgendes schreiben:

@Author(
{
@Name( firstname = "Christian", lastname = "Ullenboom" ),
@Name( firstname = "Hansi", lastname = "Hinterweltler" )
} )
 
Zum Seitenanfang

17.7.7Vorbelegte Attribute Zur vorigen ÜberschriftZur nächsten Überschrift

Im bisherigen Fall mussten alle Attributbelegungen angegeben werden, und wir konnten kein Element-Wert-Paar auslassen. Die Annotationstypen ermöglichen allerdings für Attribute Standardwerte, sodass ein Wert angeben werden kann, aber nicht muss. Statt

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt", trim = false )

soll es möglich sein, trim = false wegzulasssen, weil es Standard sein soll:

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt" )

Beziehungsweise dann wieder kürzer:

@UrlResource( "http://tutego.de/javabuch/aufgaben/bond.txt" )

In der Syntax für Vorbelegungen hält dafür das Schlüsselwort default her, was auch zu einer neuen Schreibweise führt, die von den Schnittstellen abweicht.

Bei unserem @UrlResource ist nur die Angabe der Textquelle vonnöten; alles andere soll mit Default-Werten belegt sein:

public @interface UrlResource {

enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }

String value();
boolean trim() default false;
UpperLowerCase upperLowerCase() default UpperLowerCase.UNCHANGED;
Class<? extends ResourceConverter>[] converter() default { };
}

Nachträgliche Änderung und die Sinnhaftigkeit von Standardwerten

Annotationstypen können ebenso wenig einfach geändert werden wie Schnittstellendeklarationen. Wird eine Methode nie über den Basistyp einer Schnittstelle aufgerufen, sondern die Schnittstelle lediglich implementiert, so kann diese ungenutzte Operation im Prinzip gelöscht werden. Bei Annotationen ist das genauso: Wenn ein Annotationselement mit einem Standardwert belegt ist und es nie genutzt wird, kann es gelöscht werden. Aber hier gilt analog zu den Schnittstellen: Gibt es eine dynamische Bindung über eine Schnittstelle und werden die Operationen entfernt, so gibt es genauso einen Compilerfehler, wie wenn es einen Zugriff auf ein Annotationselement gibt und es dann gelöscht wird. Auch das Ändern von Elementtypen führt im Allgemeinen zu Compilerfehlern, denn wenn aus einem int plötzlich ein String wird, fehlen Anführungszeichen.

Standardwerte sind für Annotationen ein sehr wichtiges Instrument, um neue Annotationselemente schmerzfrei einzuführen. Werden neue Elemente in bestehende Annotationstypen eingefügt, dann müssten alle existierenden konkreten Annotationen das neue Element setzen, was eine sehr große Änderung ist, vergleichbar einer neuen Operation in einer Schnittstelle. Anders als bei Schnittstellen lösen Default-Werte das Problem, da auf diese Weise für das neue Element immer gleich ein Wert vorhanden ist, der, sofern erwünscht, neu belegt werden kann. Ohne Probleme ist es möglich, einen Default-Wert hinzuzunehmen, während das Entfernen von Standardwerten wiederum kritisch ist.

 
Zum Seitenanfang

17.7.8Annotieren von Annotationstypen Zur vorigen ÜberschriftZur nächsten Überschrift

Drei Annotationstypen aus dem Paket java.lang haben wir schon kennengelernt. Die restlichen vier Annotationen aus dem Paket java.lang.annotation dienen dazu, Annotationstypen zu annotieren. In diesem Fall wird von Meta-Annotationen gesprochen.

Annotation

Beschreibung

@Target

Was lässt sich annotieren? Klasse, Methode …?

@Retention

Wo ist die Annotation sichtbar? Nur für den Compiler oder auch für die Laufzeitumgebung?

@Documented

Zeigt den Wunsch an, die Annotation in der Dokumentation zu erwähnen.

@Inherited

Macht deutlich, dass ein annotiertes Element auch in der Unterklasse annotiert ist.

@Repeatable

wenn eine Annotation mehrmals angewendet werden darf

Tabelle 17.5Meta-Annotationen

@Target

Die Meta-Annotation @java.lang.annotation.Target beschreibt, wo eine Annotation angeheftet werden kann. Ist kein ausdrückliches @Target gewählt, gilt es für alle Elemente; die Annotation kann also etwa an Klassen stehen, aber auch an lokalen Variablen. In der Regel gibt es bei @Target ein Element, und das ist von der Aufzählung java.lang.annotation.ElementType; es deklariert die folgenden Ziele:

ElementType

Erlaubt Annotationen …

ANNOTATION_TYPE

nur an anderen Annotationstypen, was @Target(ANNOTATION_TYPE) somit zu einer Meta-Annotation macht

TYPE

an allen Typdeklarationen, also Klassen, Schnittstellen, Annotationstypen, Aufzählungen

CONSTRUCTOR

an Konstruktor-Deklarationen

METHOD

an Deklarationen von statischen und nichtstatischen Methoden

FIELD

an Deklarationen von statischen Variablen und Objekt-Variablen

PARAMETER

an Parametervariablen von Methoden

LOCAL_VARIABLE

an lokalen Variablen

PACKAGE

an package-Deklarationen

TYPE_PARAMETER

an der Deklaration einer Typvariablen für generische Typ-Parameter. Neu in Java 8. Wenn es etwa heißt class List<@AnAnnotation T>

TYPE_USE

an allen Stellen, wo Typen eingesetzt werden, adressiert also Typ- Annotationen. Ebenfalls neu in Java 8. So etwas wie @NonNull (keine Annotation aus der Java SE!) ist ein Beispiel.

Tabelle 17.6ElementType bestimmt Orte, an denen Annotationen erlaubt sind.

Soll eine Annotation etwa vor beliebigen Typen, Methoden, Paketen und Konstruktoren erlaubt sein, so setzen wir Folgendes an die Deklaration des Annotationstyps:

@Target( { TYPE, METHOD, CONSTRUCTOR, PACKAGE } )
public @interface ...

Unsere eigenen drei Annotationstypen sind nur für Attribute sinnvoll. So nutzen wir FIELD, was hier an CurrentDateResource gezeigt wird:

@Target( java.lang.annotation.ElementType.FIELD )
public @interface CurrentDateResource { }

[»]Hinweis

Soll statt ElementType.FIELD einfach nur FIELD verwendet werden, so muss FIELD entsprechend aus ElementType statisch eingebunden werden. Damit ist folgender Programmcode eine Alternative:

import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Target;

@Target( FIELD )
public @interface CurrentDateResource { }

Mit ElementType.TYPE ist die Annotation vor allen Typen – Klassen, Schnittstellen, Annotationen, Aufzählungstypen – erlaubt. Eine Einschränkung, etwa nur auf Klassen, ist nicht möglich. Interessant ist die Tatsache, dass eine Unterteilung für Methoden und Konstruktoren möglich ist und dass sogar lokale Variablen annotiert werden können.

[zB]Beispiel

Beim existierenden Annotationstyp @Override ist die Annotation @Target schön zu erkennen:

@Target( value = METHOD )
public @interface Override

Die Idee der Meta-Annotation: Es gibt nur überschriebene Methoden.

Annotationen für Pakete sind speziell, weil sich die Frage stellt, wo hier die Metadaten über ein Paket stehen sollen. Eine Klasse selbst wird ja einem Paket zugeordnet – sollte das heißen, in irgendeiner wahllosen Typdeklaration stehen dann an der package-Deklaration die Meta-Annotationen für das Paket? Nein, denn dann würde zum einen die Annotation bei vielen Typen vielleicht nie mehr wiedergefunden, und zum anderen gäbe es bestimmt Konflikte, wenn aus Versehen an zwei Typen widersprüchliche Annotationen an der package-Deklaration stünden. Java wählt eine andere Lösung. Es muss eine Datei mit dem Namen package-info.java im jeweiligen Paket stehen, und dort darf die package-Deklaration annotiert sein. Da der Dateiname schon kein Klassenname sein kann (Minuszeichen sind nicht erlaubt), wird die Datei auch keine Typdeklaration enthalten, aber der Compiler erzeugt natürlich eine .class-Datei für die Metadaten des Pakets. Kommentare sind selbstverständlich erlaubt, und die Datei wird auch für die API-Dokumentation eines Pakets verwendet.

Dazu ein Beispiel. Ein neuer Annotationstyp AutomaticUmlDiagram soll deklariert werden, und er soll nur an Paketen gültig sein:

Listing 17.24com/tutego/insel/annotation/AutomaticUmlDiagram.java

package com.tutego.insel.annotation;
import java.lang.annotation.*;
@Target( value = ElementType.PACKAGE )
public @interface AutomaticUmlDiagram {}

Das Paket com.tutego.insel.annotation soll nun mit AutomaticUmlDiagram annotiert werden:

Listing 17.25com/tutego/insel/annotation/package-info.java

@AutomaticUmlDiagram
package com.tutego.insel.annotation;

Die Datei package-info.java ist schlank, wird aber in der Regel größer sein, da sie das Javadoc des Pakets enthält.

@Retention

Die Annotation @Retention steuert, wer die Annotation sehen kann. Es gibt drei Typen, die in der Aufzählung java.lang.annotation.RetentionPolicy genannt sind:

  • SOURCE: Nützlich für Tools, die den Quellcode analysieren, aber die Annotationen werden vom Compiler verworfen, sodass sie nicht den Weg in den Bytecode finden.

  • CLASS: Die Annotationen speichert der Compiler in der Klassendatei, aber sie werden nicht in die Laufzeitumgebung gebracht.

  • RUNTIME: Die Annotationen werden in der Klassendatei gespeichert und sind zur Laufzeit in der JVM verfügbar.

Die Unterscheidung haben die Java-Designer vorgesehen, da nicht automatisch jede Annotation zur Laufzeit verfügbar ist (eine Begründung: andernfalls würde es den Ressourcenverbrauch erhöhen). Der Standard ist RetentionPolicy.CLASS.

[zB]Beispiel

Der Annotationstyp @Deprecated ist nur für den Compiler und nicht für die Laufzeit von Interesse:

@Retention( value = SOURCE )
public @interface Deprecated

Ist ein Element mit @Target annotiert, so soll diese Information auch zur Laufzeit vorliegen:

@Retention( value = RUNTIME )
@Target( value = ANNOTATION_TYPE )
public @interface Target

Das Beispiel zeigt, dass die Anwendung auch rekursiv sein kann (natürlich auch indirekt rekursiv, denn nicht nur @Retention annotiert @Target, auch @Target annotiert @Retention).

Für den Zugriff auf die Annotationen gibt es dann, je nach Retention-Typ, unterschiedliche Varianten. Im Fall von Source ist es ein Tool, das auf Textebene arbeitet, also etwa ein Compiler oder ein statisches Analysetool, das Quellcode analysiert. Sind die Annotationen im Bytecode abgelegt, so lassen sie sich über ein Werkzeug bzw. eine Bibliothek auslesen. Drei Wege sind möglich:

  • Zunächst bietet Java mit der Pluggable Annotation Processing API (spezifiziert im JSR-269) eine standardisierte API zum Zugriff auf die Elemente im Quellcode. Es geht dann darum, einen Annotation Processor (eine Implementierung von der Schnittstelle javax.annotation.processing.Processor) im Compiler einzuhaken, der dann zum Beispiel Artefakte erstellen oder Fehler melden kann.

  • Eine andere Variante funktioniert über rohe Tools, die direkt auf der Ebene vom Bytecode arbeiten. In Frage kommen etwa Bibliotheken wie ASM (unter http://asm.ow2.org/), die alles auslesen können, was in der Klassendatei steht, also auch die Annotationen. Sie sind aber proprietär und nicht einfach zu nutzen.

  • Die dritte Variante ist einfach, da hier Reflection eine Möglichkeit bietet. Das schauen wir uns gleich im Anschluss an, in Abschnitt 17.7.10, »Annotierte Elemente auslesen«. Natürlich funktioniert das nur bei @Retention(RUNTIME).

@Documented

Die Annotation @Documented zeigt an, dass die Annotation in der API-Dokumentation genannt werden soll. Alle Standard-Annotationen von Java werden so angezeigt, auch @Documented selbst. In der API-Dokumentation ist für die Annotationen ein neues Segment vorgesehen.

[zB]Beispiel

@Documented ist selbst @Documented:

@Documented
@Target( value = ANNOTATION_TYPE )
public @interface Documented

@Repeatable

Normalerweise nutzen Entwickler Annotationen wie einmalige Modifizierer, und es ergibt keinen Sinn, etwa @Override @Override String toString() zu schreiben, genauso wenig wie es einen Sinn ergibt, final static final double PI zu deklarieren. Doch da es durchaus Metadaten, insbesondere mit verschiedenen Werten, gibt, die mehrmals auftauchen können, bietet Java 8 eine Erweiterung, dass Annotationen wiederholt werden dürfen. Allerdings müssen die Annotationstypen dieser wiederholbaren Annotationen selbst mit einer besonderen Meta-Annotation @Repeatable ausgezeichnet werden. Damit ist es aber noch nicht getan, denn @Repeatable muss als Element einen Typ bekommen, der den Container angibt.

[zB]Beispiel

Der Annotationstyp für Autoren kann so aussehen:

public @interface Author { String name(); }

Soll nun die Annotation mehrfach verwendet werden, ist die Meta-Annotation nötig und mit ihr die Angabe eines Containers:

@Repeatable( Authors.class )
public @interface Author { String name(); }

Der Container ist selbst ein Annotationstyp mit einem Feld als Element. Der Typ des Feldes ist exakt der wiederholbare Annotationstyp:

public @interface Authors {
Autor[] value;
}

Ohne @Repeatable am Annotationstyp wird eine mehrmalige Verwendung einer Annotation zu einem Compilerfehler führen. In der Java SE 8 gibt es bisher keine Verwendung dieses Annotationstyps, also auch keine wiederholbaren Annotationen in der Standardbibliothek.

Erfragt werden wiederholbare Annotationen durch Methoden der Schnittstelle java.lang.reflect.AnnotatedElement (und das ist AccessibleObject, Class, Constructor, Executable, Field, Method, Package, Parameter):

interface java.lang.reflect.AnnotatedElement
  • default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)

  • default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)

 
Zum Seitenanfang

17.7.9Deklarationen für unsere Ressourcen-Annotationen Zur vorigen ÜberschriftZur nächsten Überschrift

Da unsere drei Annotationen zur Laufzeit ausgelesen werden sollen, muss die @Retention mit RetentionPolicy.RUNTIME gesetzt sein. Damit sind unsere Annotationstypen vollständig, und der Quellcode soll an dieser Stelle aufgeführt werden.

Der einfachste Annotationstyp war CurrentDateResource:

Listing 17.26com/tutego/insel/annotation/CurrentDateResource.java, CurrentDateResource

@Documented
@Target( ElementType.FIELD )
@Retention( RetentionPolicy.RUNTIME )
public @interface CurrentDateResource { }

Der Annotationstyp ListOfFilesResource erwartet eine Pfadangabe, ist aber nicht deutlich komplexer als CurrentDateResource:

Listing 17.27com/tutego/insel/annotation/ListOfFilesResource.java, ListOfFilesResource

@Documented
@Target( ElementType.FIELD )
@Retention( RetentionPolicy.RUNTIME )
public @interface ListOfFilesResource {
String value();
}

Und zu guter Letzt: Der Annotationstyp UrlResource hat am meisten zu bieten. Doch beginnen wir zunächst mit der Deklaration der Schnittstelle für die Konverter:

Listing 17.28com/tutego/insel/annotation/ResourceConverter.java, ResourceConverter

public interface ResourceConverter {
String convert( String input );
}

Zwei Implementierungen sollen für das Beispiel genügen:

Listing 17.29com/tutego/insel/annotation/SortConverter.java, SortConverter Teil 1

public class RemoveNoWordCharactersConverter implements ResourceConverter {
@Override public String convert( String input ) {
return input.replaceAll( "\\W", "" );
}
}

Listing 17.30com/tutego/insel/annotation/SortConverter.java, SortConverter Teil 2

public class SortConverter implements ResourceConverter {
@Override public String convert( String input ) {
char[] chars = input.toCharArray();
Arrays.sort( chars );
return new String( chars );
}
}

Damit kann dann der letzte Annotationstyp übersetzt werden:

Listing 17.31com/tutego/insel/annotation/UrlResource.java, UrlResource

@Documented
@Target( ElementType.FIELD )
@Retention( RetentionPolicy.RUNTIME )
public @interface UrlResource {

enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }

String value();
boolean trim() default false;
UpperLowerCase upperLowerCase() default UpperLowerCase.UNCHANGED;
Class<? extends ResourceConverter>[] converter() default { };
}
 
Zum Seitenanfang

17.7.10Annotierte Elemente auslesen Zur vorigen ÜberschriftZur nächsten Überschrift

Ob eine Klasse annotiert ist, erfragt ganz einfach die Methode isAnnotationPresent(Class<? extends Annotation> annotationClass) auf dem Class-Objekt:

Listing 17.32com/tutego/insel/annotation/CheckIsStringBufferInputStreamDeprecated.java, main()

println( String.class.isAnnotationPresent( Deprecated.class ) ); // false
println( StringBufferInputStream.class.isAnnotationPresent( Deprecated.class ) ); // true

Schnittstelle AnnotatedElement

Da unterschiedliche Dinge annotierbar sind, schreibt eine Schnittstelle AnnotatedElement für die Klassen Class, Constructor, Field, Method, Package und AccessibleObject folgende Operationen vor:

interface java.lang.reflect.AnnotatedElement
  • Annotation[] getAnnotations()
    Liefert die an dem Element festgemachten Annotationen. Gibt es keine Annotation, ist das Feld leer. Die Methode liefert auch Annotationen, die aus den Obertypen kommen.

  • Annotation[] getDeclaredAnnotations()
    Liefert die Annotationen, die exakt an diesem Element festgemacht sind, sprich, vererbte Annotationen zählen nicht dazu.

  • <T extends Annotation> T getAnnotation(Class<T> annotationType)
    Liefert die Annotation für einen bestimmten Typ. Ist keine Annotation vorhanden, ist die Rückgabe null. Der generische Typ ist bei der Rückgabe hilfreich. Denn das Argument ist ein Class-Objekt, das den Annotationstyp repräsentiert. Die Rückgabe ist genau die konkrete Annotation für das annotierte Element. Betrachtet auch Annotationen, die aus den Obertypen kommen.

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationType)
    Gibt es die angegebene Annotation? Betrachtet auch geerbte Annotationen. Im Grunde getAnnotation(annotationType) != null.

  • default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
    Wie getAnnotation(Class<T>), nur ohne geerbte Annotation.

  • default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)

  • default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
    Liefert ein Array von Annotationen vom gewünschten Typ; ein Array ist nötig bei wiederholten Annotationen. Neu in Java 8. Einmal inklusive/exklusive geerbter Annotationen.

Um die Annotationen etwa von Variablen oder Methoden zu erfragen, ist ein wenig Reflection-Wissen nötig. Ist obj ein Objekt, so findet folgende Schleife alle mit CurrentDateResource annotierten Objektvariablen und gibt eine Meldung aus:

for ( Field field : obj.getFields() )
if ( field.isAnnotationPresent( CurrentDateResource.class ) )
System.out.println( "CurrentDateResource gesetzt" );

AnnotatedType

Vier Methoden kommen in Java 8 in Executable hinzu:

abstract class java.lang.reflect.Executable<T>
extends AccessibleObject
implements Member, GenericDeclaration
  • abstract AnnotatedType getAnnotatedReturnType()

  • AnnotatedType getAnnotatedReceiverType()

  • AnnotatedType[] getAnnotatedParameterTypes()

  • AnnotatedType[] getAnnotatedExceptionTypes()

Die Schnittstelle AnnotatedType (erweitert AnnotatedElement) ist ebenfalls neu in Java 8 und repräsentiert einen annotierten Typ. Constructor überschreibt Executable die Methode getAnnotatedReturnType(), da Konstruktoren aber keine Rückgabe haben, ist das Ergebnis der Typ des erzeugten Objekts.

Zudem hat Field seit Java 8 eine Methode getAnnotatedType(), die ebenso AnnotatedType als Rückgabe hat.

Class besitzt seit Java 8 zwei neue Methoden:

  • AnnotatedType[] getAnnotatedInterfaces()

  • AnnotatedType getAnnotatedSuperclass()

 
Zum Seitenanfang

17.7.11Auf die Annotationsattribute zugreifen Zur vorigen ÜberschriftZur nächsten Überschrift

Um auf die einzelnen Attribute einer Annotation zuzugreifen, müssen wir etwas mehr über die Umsetzung einer Annotation vom Compiler und der JVM wissen. Übersetzt der Compiler einen Annotationstyp, generiert er daraus eine Schnittstelle.

[zB]Beispiel

Für den Annotationstyp ListOfFilesResource generiert der Compiler:

import java.lang.annotation.Annotation;

public interface ListOfFilesResource extends Annotation {
public abstract String value();
}

Rufen wir auf einem AnnotatedElement, etwa Field, eine Methode wie getAnnotations() oder getAnnotation(Class<T> annotationClass) auf, bekommen wir ein Objekt, das Zugriff auf unsere Element-Wert-Paare liefert. Denn zur Laufzeit werden über java.lang.reflect.Proxy Objekte gebaut, die unsere Schnittstelle – das ist ListOfFilesResource – implementiert und so die Methode value() anbietet.

[»]Hinweis

Die Annotation ist zur Laufzeit ein Proxy-Objekt, und daher kann der Annotationstyp keine eigene Klasse erweitern und auch keine anderen eigenen Schnittstellen implementieren. Ein Annotationstyp kann auch keine anderen Annotationstypen erweitern. Es könnte eine eigene Klasse zwar die Schnittstelle java.lang.annotation.Annotation implementieren, doch entsteht dadurch keine echte Annotation, was den Versuch sinnlos macht.

Testen wir die Möglichkeit, indem wir zwei annotierte Variablen in eine Klasse setzen und dann per Reflection über alle Variablen laufen und alle Annotationen erfragen lassen:

Listing 17.33com/tutego/insel/annotation/GetTheUrlResourceValues.java, GetTheUrlResourceValues

public class GetTheUrlResourceValues {

@UrlResource( value = "http://tutego.de/javabuch/aufgaben/bond.txt",
upperLowerCase = UpperLowerCase.UPPERCASE, trim = true,
converter = { RemoveNoWordCharactersConverter.class,
SortConverter.class } )
public String testFile;

@XmlValue @Deprecated
public String xmlValue;

public static void main( String[] args ) throws Exception {
for ( Field field : GetTheUrlResourceValues.class.getFields() )
for ( Annotation a : field.getAnnotations() )
System.out.println( a );
}
}

Die Ausgabe zeigt drei Annotationen:

@com.tutego.insel.annotation.UrlResource(converter=[class com.tutego.insel.annotation.¿
RemoveNoWordCharactersConverter, class com.tutego.insel.annotation.SortConverter], ¿
upperLowerCase=UPPERCASE, value=http://tutego.de/javabuch/aufgaben/bond.txt)
@javax.xml.bind.annotation.XmlValue()
@java.lang.Deprecated()

Die Default-Werte werden zur Laufzeit gesetzt.

 
Zum Seitenanfang

17.7.12Komplettbeispiel zum Initialisieren von Ressourcen Zur vorigen ÜberschriftZur nächsten Überschrift

Zusammenfassend können wir jetzt eine Klasse vorstellen, die tatsächlich die mit den Ressourcen-Annotationen versehenen Variablen mit sinnvollem Inhalt füllt. Zunächst betrachten wir ein Beispiel, das die Nutzung einer solchen Klasse aufzeigt.

Die Klasse Resources bildet den Rahmen für Objekte, die automatisch aufgebaut und korrekt initialisiert werden sollen:

Listing 17.34com/tutego/insel/annotation/AnnotatedResourceExample.java, Resources

class Resources {

@CurrentDateResource()
public Date now;

@ListOfFilesResource( value = "c:/" )
public String[] files;

@UrlResource( "http://tutego.de/javabuch/aufgaben/bond.txt" )
public String testFile;
}

Einer zweiten Klasse geben wir ein main(String[]) und setzen dort die Aufforderung, ein Objekt vom Typ Resources anzulegen und zu initialisieren:

Listing 17.35com/tutego/insel/annotation/AnnotatedResourceExample.java, AnnotatedResourceExample

public class AnnotatedResourceExample {
public static void main( String[] ars ) {
Resources resources =
ResourceReader.getInitializedInstance( Resources.class );
System.out.println( resources.now );
System.out.println( Arrays.toString( resources.files ) );
System.out.println( resources.testFile );
}
}

Kommen wir zum Herzen, der Klasse ResourceReader:

Listing 17.36com/tutego/insel/annotation/ResourceReader.java

package com.tutego.insel.annotation;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Date;
import java.util.Scanner;

public class ResourceReader {
public static <T> T getInitializeInstace( Class<T> ressources ) {
try {
T obj = ressources.newInstance();

for ( Field field : ressources.getFields() ) {
if ( field.isAnnotationPresent( CurrentDateResource.class ) )
field.set( obj, new Date() );

else if ( field.isAnnotationPresent( ListOfFilesResource.class ) )
field.set( obj, new File(field.getAnnotation(
ListOfFilesResource.class ).value().toString()).list() );

else if ( field.isAnnotationPresent( UrlResource.class ) ) {
String url = field.getAnnotation( UrlResource.class ).value();
try ( Scanner scanner = new Scanner( new URL( url ).openStream() ) ) {
String content = scanner.useDelimiter( "\\Z" ).next();

if ( field.getAnnotation( UrlResource.class ).trim() )
content = content.trim();

switch ( field.getAnnotation( UrlResource.class ).upperLowerCase() ) {
case UPPERCASE: content = content.toUpperCase(); break;
case LOWERCASE: content = content.toLowerCase(); break;
default: // Nichts zu tun
}

Class<? extends ResourceConverter>[] converterClasses =
field.getAnnotation( UrlResource.class ).converter();
for ( Class<? extends ResourceConverter> converterClass : converterClasses )
content = converterClass.newInstance().convert( content );

field.set( obj, content );
}
}
}

return obj;
}
catch ( Exception e ) { // Ignoriere alle Ausnahmen
return null;
}
}
}

An den folgenden Anweisungen ist das Prinzip gut ablesbar:

T newInstance = ressources.newInstance();
for ( Field field : ressources.getFields() )
if ( field.isAnnotationPresent( CurrentDateResource.class ) )
field.set( newInstance, new Date() );

Zunächst wird ein neues Exemplar, ein Behälter, aufgebaut. Dann läuft eine Schleife über alle Variablen. Gibt es zum Beispiel die Annotation CurrentDateResource an einer Variablen, so wird ein Date-Objekt aufgebaut und mit set(…) die Variable mit dem Datum initialisiert.

 
Zum Seitenanfang

17.7.13Mögliche Nachteile von Annotationen Zur vorigen ÜberschriftZur nächsten Überschrift

Annotationen sind eine gewaltige Neuerung und sicherlich die wichtigste seit vielen Java-Jahren. Auch wenn die Generics auf den ersten Blick bedeutsam erscheinen, sind die Annotationen ein ganz neuer Schritt in die deklarative Programmierung, wie sie Frameworks schon heute aufzeigen. Völlig problemlos sind Annotationen allerdings nicht, und so müssen wir etwas Wasser in den Wein gießen:

  • Die Annotationen sind stark mit dem Quellcode verbunden, können also auch nur dort geändert werden. Ist der Original-Quellcode nicht verfügbar, etwa weil der Auftraggeber ihn geschlossen hält, ist eine Änderung der Werte nahezu unmöglich.

  • Wenn Annotationen allerdings nach der Übersetzung nicht mehr geändert werden können, stellt das bei externen Konfigurationsdateien kein Problem dar. Externe Konfigurationsdateien können ebenso den Vorteil bieten, dass die relevanten Informationen auf einen Blick erfassbar sind und sich mitunter nicht redundant auf unterschiedliche Java-Klassen verteilen.

  • Klassen mit Annotationen sind invasiv und binden auch die Implementierungen an einen gewissen Typ, wie es Schnittstellen tun. Sind die Annotationstypen nicht im Klassenpfad, kommt es zu einem Compilerfehler.

  • Bisher gibt es keine Vererbung von Annotationen: Ein Annotationstyp kann keinen anderen Annotationstyp erweitern.

  • Die bei den Annotationen gesetzten Werte lassen sich zur Laufzeit erfragen, aber nicht modifizieren.

  • Warum werden Annotationen mit @interface deklariert, einer Schreibweise, die in Java sonst völlig unbekannt ist?

Ein Problem gibt es allerdings nur bei finalen statischen Variablen (Konstanten), das bei den Default-Werten der Annotationen nicht vorkommt: Weil die Default-Werte zur Laufzeit gesetzt werden, lassen sie sich in der Deklaration vom Annotationstyp leicht ändern, und eine Neuübersetzung des Projekts kann somit unterbleiben.

Zur Ehrenrettung sei erwähnt, dass moderne Frameworks wie JPA oder JSF 2 aus dem Java EE-Standard immer noch den Einsatz von XML vorsehen. So lässt sich auf Annotationen verzichten bzw. XML einsetzen, sodass Zuweisungen aus den Annotationen überschrieben werden können.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2018
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern