Start / Seminare / Java SE Fortgeschrittene Sprachfeatures
Modul
Annotationen
6 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
Annotationen
0:00 Zum Abschluss des Seminars ein Thema, mit dem Sie täglich arbeiten, ohne vermutlich je darüber nachgedacht zu haben. Annotationen sind allgegenwärtig — in Spring, in JPA, in JUnit, in jedem Test, den Sie schreiben. Und es gibt einen Satz über sie, den man verinnerlicht haben sollte: Eine Annotation tut von sich aus überhaupt nichts. Sie ist eine Angabe, mehr nicht.
0:23 Erst ein Werkzeug, das sie liest, macht etwas daraus. Aus diesem Satz folgt alles Weitere — auch die Frage, warum eine selbst gebaute Annotation manchmal einfach ignoriert wird.
Annotationen
0:34 Sechs Kapitel liegen vor uns, und der Aufbau folgt der Reihenfolge, in der Sie sich dem Thema in der Praxis nähern. Zuerst der Blick auf die Werkzeuge, die Sie kennen — was machen die eigentlich mit den Annotationen? Dann die Standard-Annotationen, die der Compiler selbst auswertet. Danach bauen wir eine eigene, samt Attributen und Meta-Annotationen.
0:55 Und zum Schluss die beiden Wege der Verarbeitung: zur Laufzeit über Reflection und beim Übersetzen über einen Prozessor. Am Ende steht noch ein Rückblick auf das gesamte Seminar. Vier Punkte. Der erste ist der wichtigste und zugleich der, den man am leichtesten übersieht: Eine Annotation ohne verarbeitendes Werkzeug bleibt wirkungslos.
1:15 Der zweite bringt sofortigen Nutzen — es gibt eine Handvoll Standard-Annotationen, die zusätzliche Compilerprüfungen einschalten. Der dritte ist Handwerk: eine eigene Annotation samt Attributen bauen. Und der vierte ist die Entscheidung, die alles zusammenhält: Wann wird ausgewertet, und wie lange muss die Annotation deshalb überleben?
Annotationen in der Praxis
1:36 Beginnen wir mit einer Bestandsaufnahme. Sie verwenden Annotationen längst, und zwar täglich — in jedem Test, in jeder Entität, in jeder Komponente. Aber was passiert dabei eigentlich? Wer liest diese Angaben, und wann? Diese Frage lohnt sich, weil die Antwort erklärt, warum manche Annotationen im Bytecode landen und andere spurlos verschwinden.
1:56 Und weil sie zugleich die wichtigste Entwurfsentscheidung vorbereitet, die Sie beim Bau einer eigenen Annotation treffen müssen. Eine Annotation hängt Metadaten an eine Klasse, eine Methode, ein Feld oder eine Variable. Ein Bild dazu: Denken Sie an einen Aufkleber auf einem Umzugskarton. Der Aufkleber trägt den Karton nicht und öffnet ihn auch nicht — er sagt nur etwas über den Inhalt. Was daraus wird, hängt davon ab, ob jemand ihn liest.
2:24 Genauso sind Annotationen: Sie bewirken für sich genommen nichts. Erst ein Werkzeug oder Rahmenwerk, das sie liest, macht etwas daraus — entweder zur Laufzeit über Reflection oder beim Übersetzen über einen Annotationsprozessor. Diese Übersicht zeigt die Werkzeuge, die Sie vermutlich alle im Einsatz haben, und den Zeitpunkt, zu dem sie arbeiten.
2:45 Die ersten vier lesen zur Laufzeit über Reflection — Spring baut daraus seine Komponenten, JPA die Abbildung auf die Datenbank, JUnit findet damit die Testmethoden. Lombok geht den anderen Weg und erzeugt beim Übersetzen Code. Was ich Ihnen mitgeben möchte, steht in der Fußzeile: Die rechte Spalte ist immer die Leistung des Werkzeugs.
3:05 Die Annotation selbst liefert nur die Angabe. Was folgt daraus praktisch? Erstens: Eine eigene Annotation ohne Verarbeitung ist ein Kommentar mit Klammeraffe — hübsch, aber wirkungslos. Zweitens, und das ist die zentrale Entwurfsentscheidung: Der Zeitpunkt der Verarbeitung bestimmt, wie lange die Annotation aufbewahrt werden muss.
3:25 Wer zur Laufzeit lesen will, braucht Reflection und die passende Einstellung — und wenn diese Einstellung fehlt, findet er schlicht nichts. Genau das ist der häufigste Fehler bei selbst gebauten Annotationen, und wir kommen im vierten Kapitel darauf zurück. Vier Punkte. Der erste ist die Erwartung, eine eigene Annotation würde von allein wirken.
3:45 Der zweite ist genau der Fall, den ich eben angekündigt habe — fehlende Aufbewahrungsdauer und leere Ergebnisse ohne jede Fehlermeldung. Der dritte ist eine Entwurfsfrage: Annotationen stehen im Quelltext und sind damit fest verbaut; für Konfiguration, die sich im Betrieb ändern soll, taugen sie nicht. Und der vierte ist eine Frage des Maßes — Fachlogik, die in Annotationen ausgedrückt wird, ist schwerer zu lesen und zu testen als Fachlogik im Code.
Standard-Annotationen
4:12 Bevor wir eigene Annotationen bauen, ein Blick auf die, die es schon gibt. Die Standardbibliothek bringt eine Handvoll mit, und die haben etwas Besonderes gemeinsam: Bei ihnen ist der Compiler selbst das verarbeitende Werkzeug. Kein Rahmenwerk nötig, keine Bibliothek, keine Konfiguration — sie wirken sofort und überall. Das macht sie zum günstigsten Werkzeug in diesem ganzen Seminar: eine Zeile schreiben und dafür zusätzliche Prüfungen bekommen, die es sonst nicht gäbe.
4:41 Diese Annotationen tun im Wesentlichen dreierlei: Sie verhindern Fehler, sie kennzeichnen Veraltetes, oder sie steuern Meldungen. Der interessante Teil ist die erste Gruppe, denn dort schalten Sie zusätzliche Prüfungen ein, die es sonst nicht gäbe. Das ist ein ausgesprochen günstiges Geschäft: eine Zeile schreiben und dafür eine Fehlerklasse loswerden.
5:01 Wenn Sie in Ihrem Team eine einzige Regel zu Annotationen durchsetzen wollen, dann diese — die prüfenden Annotationen konsequent setzen. Hier die fünf im Überblick. Die Fußzeile trifft die entscheidende Unterscheidung: Override, SafeVarargs und FunctionalInterface prüfen etwas — sie können also Fehler finden. Deprecated und SuppressWarnings steuern nur Meldungen.
5:22 Zwei kennen Sie schon aus dem letzten Modul: SafeVarargs mit all seinen Bedingungen, und SuppressWarnings, das wir dort als Behauptung eingeordnet haben. FunctionalInterface ist die unbekannteste der fünf und lohnt einen Blick — sie meldet, wenn eine Schnittstelle mehr als eine abstrakte Methode bekommt und damit für Lambdas unbrauchbar wird.
5:42 Ein winziges Beispiel mit großer Wirkung. Hier ist hashCode mit kleinem c geschrieben — ein Tippfehler, wie er jedem passiert. Ohne die Annotation entstünde daraus einfach eine neue Methode, die niemand aufruft. Das Programm übersetzt, es läuft, und irgendwann verhält sich ein HashSet merkwürdig, weil die eigentliche hashCode-Methode nie überschrieben wurde.
6:04 Mit der Annotation bricht der Compiler sofort ab. Genau das meine ich mit günstigem Geschäft: eine Zeile, und dieser Fehler kann nicht mehr passieren. Zu SuppressWarnings noch drei Hinweise, die aus der Praxis kommen. Erstens: Die Annotation beseitigt die Meldung, nicht die Ursache — das haben wir im letzten Modul ausführlich gesehen.
6:24 Zweitens: Sie gehört so eng wie möglich um die betroffene Stelle, idealerweise um eine einzelne Variable, nicht um eine ganze Klasse. Und drittens: Sie braucht einen Kommentar, der begründet, warum die Umwandlung sicher ist. Ohne Begründung ist sie eine Behauptung, die niemand nachprüfen kann — und die in einem halben Jahr niemand mehr hinterfragt.
6:45 Diese Aufgabe hat einen sehr konkreten Nutzen für Ihren Alltag. Setzen Sie Override konsequent — auch bei Schnittstellenmethoden, wo es viele weglassen, weil sie es für optional halten. Und dann machen Sie den Versuch aus dem Hinweis: Bauen Sie einen Tippfehler ein und schauen Sie, ob der Compiler ihn ohne und mit Annotation findet.
7:04 Diese kleine Demonstration überzeugt in einem Team oft mehr als jede Stilrichtlinie, weil sie den Nutzen unmittelbar sichtbar macht. Der erste Punkt ist die zu grob gesetzte Unterdrückung — über eine ganze Klasse gelegt, verdeckt sie auch Warnungen, die nichts mit dem ursprünglichen Problem zu tun haben. Der zweite ist eine Höflichkeitsfrage gegenüber den Nutzern Ihrer Bibliothek: Deprecated ohne Hinweis auf den Nachfolger hilft niemandem.
7:28 Der dritte ist das Weglassen von Override bei Schnittstellenmethoden. Und der vierte verbindet uns mit dem letzten Modul — SafeVarargs setzen, ohne die dort besprochenen Bedingungen zu prüfen.
Eigene Annotationen erstellen
7:39 Jetzt bauen wir eine eigene Annotation. Unser Beispiel ist ein kleines Kommandozeilenwerkzeug für Lagerpuls, das verschiedene Befehle kennen soll — Bestand anzeigen, buchen, Hilfe ausgeben. Der Anspruch dabei: Ein neuer Befehl soll nur eine neue Klasse kosten und keinen Eintrag in irgendeiner zentralen Liste. Genau solche Listen sind es nämlich, die man beim Erweitern vergisst. Wie das geht und welche Regeln beim Entwurf einer Annotation gelten, sehen wir uns jetzt an.
8:08 Eine eigene Annotation deklarieren Sie mit at-interface. Die Schreibweise ähnelt einer Schnittstelle, und das ist kein Zufall, aber eine Annotation ist etwas anderes. Ihre Attribute sehen aus wie Methoden ohne Rumpf — der Rückgabetyp ist dabei der Typ des Attributs. Mit dem Schlüsselwort default setzen Sie einen Vorgabewert.
8:27 Und daraus ergibt sich eine praktische Unterscheidung: Attribute ohne Vorgabewert sind Pflichtangaben, alle anderen sind optional. So steuern Sie, wie viel der Anwender Ihrer Annotation hinschreiben muss. Hier unsere Annotation mit drei Attributen: dem Namen des Befehls, einer Beschreibung und einer Zahl für die Sortierung.
8:47 Die Beschreibung und die Sortierung haben Vorgabewerte, der Name nicht — er ist also Pflicht. Und jetzt achten Sie auf den Namen des ersten Attributs: value. Der ist nicht zufällig gewählt. Für ein Attribut mit genau diesem Namen gibt es eine Abkürzung bei der Anwendung, die den Aufruf deutlich lesbarer macht. Wie die aussieht, sehen wir auf der nächsten Folie.
9:09 Oben die Kurzform: nur der Wert in Klammern, kein value-gleich davor. Das funktioniert, weil das Attribut value heißt und weil sonst nichts angegeben ist. Unten die vollständige Form mit allen drei Attributen — und dort steht value dann wieder ausgeschrieben. Diese Regel ist einfach zu merken: Die Abkürzung gilt, so lange Sie ausschließlich value setzen.
9:30 Deshalb lohnt es sich, das wichtigste Attribut Ihrer Annotation auch tatsächlich value zu nennen. Es ist der Unterschied zwischen einer lesbaren und einer geschwätzigen Anwendung. Bei den Attributtypen gibt es Einschränkungen, und sie sind strenger, als man erwartet. Erlaubt sind primitive Typen, Zeichenketten, Klassenobjekte, Enums, andere Annotationen und Arrays davon.
9:53 Nicht erlaubt sind beliebige Objekttypen, generische Typen und — das überrascht viele — auch die Hüllklassen wie Integer. Der Grund für all das steht in der Fußzeile: Alle Werte müssen beim Übersetzen feststehen. Deshalb ist auch null als Vorgabewert ausgeschlossen, und deshalb lässt sich kein Wert aus einer Konfigurationsdatei verwenden.
10:13 Diese Frage wird regelmäßig gestellt, und die Antwort lautet: geht nicht. Entwerfen Sie eine Annotation für Metadaten, die bisher woanders stecken — typischerweise in Namenskonventionen oder in einer zentralen Liste. Der Hinweis verlangt einen bewussten Fehlversuch: Probieren Sie eine Liste von Zeichenketten als Attributtyp. Die Fehlermeldung zeigt Ihnen die Grenze unmittelbar.
10:35 Solche kleinen Experimente lohnen sich, weil die Regel dann nicht mehr abstrakt ist, sondern mit einer konkreten Erfahrung verbunden. Achten Sie außerdem darauf, genau ein Pflichtattribut zu haben — mehr macht die Anwendung schwerfällig. Der erste Punkt ist die Hüllklasse statt des primitiven Typs — Integer geht nicht, int schon.
10:55 Der zweite und dritte gehören zusammen: null als Vorgabewert und Werte aus Konfigurationsdateien; beides scheitert an derselben Regel, nämlich dass alles beim Übersetzen feststehen muss. Wenn Sie einen leeren Wert brauchen, nehmen Sie eine leere Zeichenkette oder ein leeres Array. Und der vierte ist die verpasste Gelegenheit: Wer sein Hauptattribut anders nennt als value, verzichtet auf die Abkürzung — für nichts.
Meta-Annotationen
11:19 Jetzt kommt das Kapitel, in dem sich entscheidet, ob Ihre Annotation überhaupt funktioniert. Meta-Annotationen sind Annotationen an Annotationen — das klingt nach einem akademischen Spezialthema und ist in Wahrheit der Punkt, an dem selbst gebaute Annotationen am häufigsten scheitern. Und zwar auf die unangenehmste Art: Es gibt keinen Fehler, keine Warnung, sondern schlicht ein leeres Ergebnis.
11:42 Wenn Sie sich aus diesem Modul eine Folie merken, dann bitte die über die Aufbewahrungsdauer. Meta-Annotationen stammen aus dem Paket java.lang.annotation und legen die Rahmenbedingungen fest: wo eine Annotation stehen darf, wie lange sie aufbewahrt wird, ob sie in die Dokumentation gehört, ob Untertypen sie erben und ob sie mehrfach gesetzt werden darf.
12:03 Von diesen fünf sind zwei wirklich wichtig — Target und Retention. Die anderen drei sind nützlich, aber optional. Und wenn Sie sich nur eine merken wollen: Retention. Denn eine falsche Aufbewahrungsdauer macht Ihre Annotation unsichtbar, ohne dass irgendetwas warnt. Hier die fünf im Überblick. Target legt fest, an welchen Stellen die Annotation stehen darf — an Klassen, an Methoden, an Feldern.
12:27 Die Fußzeile nennt den Grund, warum man sie setzen sollte: Ohne Target darf die Annotation fast überall stehen, und das ist selten gewollt. Documented und Inherited sind Feinheiten. Repeatable ist eine Nachrüstung aus Java acht, zu der wir gleich kommen. Und Retention ist die entscheidende — die nehmen wir uns nach dem nächsten Codebeispiel im Detail vor.
12:49 Und hier unsere Annotation in vollständiger Form. Target auf Typen, weil sie an Klassen stehen soll. Retention auf RUNTIME, und das ist hier Pflicht — ohne diese Zeile würde unser Register die Annotation später schlicht nicht finden. Documented ist eine Höflichkeit gegenüber den Lesern der Dokumentation. Was ich Ihnen an dieser Folie zeigen möchte: Die drei Zeilen darüber sind kein Beiwerk.
13:12 Sie entscheiden darüber, ob die Annotation funktioniert. Ohne sie ist der Rest wirkungslos. Drei Stufen gibt es. SOURCE bedeutet: Der Compiler wirft die Annotation nach der Verarbeitung weg — richtig für Annotationsprozessoren. CLASS bedeutet: Sie landet in der Klassendatei, ist aber zur Laufzeit nicht über Reflection erreichbar. RUNTIME bedeutet: Sie ist zur Laufzeit lesbar.
13:36 Und jetzt der Punkt, der in der Praxis Zeit kostet: CLASS ist die Vorgabe, wenn Sie nichts angeben. Wer also Retention weglässt und dann zur Laufzeit sucht, findet nichts — ohne Fehler, ohne Warnung, einfach ein leeres Ergebnis. Ein Randthema mit ungewöhnlicher Mechanik. Ursprünglich durfte eine Annotation nur einmal je Stelle stehen.
13:57 Java acht hat das nachgerüstet, aber abwärtskompatibel — und deshalb ist der Weg etwas umständlich. Sie legen eine zweite Annotation an, die als Sammler dient und ein Array der ersten aufnimmt. Dann kennzeichnen Sie die ursprüngliche mit Repeatable und nennen dort den Sammler. Und ein praktischer Hinweis für später: Beim Auslesen bekommen Sie dann den Sammler zurück, nicht die einzelnen Annotationen.
14:21 Hier leiten Sie aus dem geplanten Verarbeitungszeitpunkt die richtigen Meta-Annotationen ab — das ist die eigentliche Fähigkeit. Der Hinweis nennt einen guten Test: Setzen Sie die Annotation versuchsweise an eine Methode. Target muss das verhindern. Wenn es das nicht tut, haben Sie Target vergessen oder zu weit gefasst. Und prüfen Sie im selben Zug die Retention gegen Ihren Plan — bei Auswertung zur Laufzeit muss dort RUNTIME stehen, sonst ist die Arbeit im nächsten Kapitel vergebens.
14:50 Der erste Punkt ist der wichtigste dieses ganzen Moduls: Retention weglassen. Die Vorgabe ist CLASS, und damit ist die Annotation über Reflection unsichtbar. Wenn eine selbst gebaute Annotation nicht funktioniert, ist das mit Abstand die häufigste Ursache. Der zweite ist das fehlende Target. Der dritte ist eine Einschränkung, die man kennen sollte: Inherited wirkt nur bei Klassenvererbung, nicht bei Schnittstellen.
15:14 Und der vierte ist der Versuch, Repeatable ohne die Sammler-Annotation zu verwenden.
Verarbeitung zur Laufzeit
15:20 Jetzt kommt die Ernte. Wir haben eine Annotation, sie ist mit Target und Retention richtig konfiguriert — also können wir sie endlich auswerten. Der Weg über Reflection ist der häufigere der beiden und für die allermeisten Anwendungsfälle der passende. Sehen wir uns an, wie man annotierte Klassen findet, ihre Attributwerte ausliest und daraus ein Register baut, das sich von selbst erweitert.
15:43 Und nebenbei begegnet uns eine alte Bekannte aus dem letzten Modul. Zur Laufzeit finden Sie Annotationen über Reflection. Auf einem Klassenobjekt liefert getAnnotation ein Objekt, das die Annotation umsetzt — und dessen Methoden geben Ihnen die Attributwerte zurück. Das ist ein hübsches Detail: Weil Sie die Attribute als Methoden deklariert haben, lesen Sie sie auch wie Methoden. Die Voraussetzung kennen Sie inzwischen — Retention auf RUNTIME.
16:10 Und wenn die Annotation an der Klasse gar nicht steht, liefert getAnnotation null. Diese Prüfung wird gern vergessen. Hier der Kern der Auswertung, konzeptionell erzählt: Klasse laden, Annotation holen, prüfen ob vorhanden, Attribute auslesen. Was mir an diesem Codeausschnitt gefällt, ist die Unauffälligkeit — es sieht aus wie gewöhnlicher Code, obwohl es Metadaten aus dem Bytecode liest.
16:33 Achten Sie auf den Aufruf von value: Er sieht aus wie ein normaler Methodenaufruf, weil das Attribut genau so deklariert wurde. Die Brücke zwischen Deklaration und Auswertung ist damit sehr direkt. Der vollständige Ablauf hat fünf Schritte. Die Klassen des Pakets ermitteln, je Klasse die Annotation prüfen, die erwartete Schnittstelle prüfen, nach dem Sortierattribut ordnen und schließlich je Treffer eine Instanz erzeugen.
16:58 Schritt drei ist wichtiger, als er aussieht — wenn eine Klasse die Annotation trägt, aber die Schnittstelle nicht umsetzt, sollten Sie das sofort melden statt später eine ClassCastException zu ernten. Und die Fußzeile nennt ein hübsches Detail: Eine LinkedHashMap bewahrt die sortierte Reihenfolge für die Ausgabe der Hilfe.
17:16 An dieser Stelle schlägt das vorige Modul zurück. Die Umwandlung des Klassenobjekts auf einen Typ mit Wildcard-Schranke ist nicht prüfbar — Type Erasure hat der Laufzeit die nötige Information genommen. Der Compiler warnt also zu Recht. Und hier ist SuppressWarnings ausnahmsweise zulässig, weil eine Zeile vorher tatsächlich geprüft wurde, dass die Klasse die Schnittstelle umsetzt.
17:38 Genau so sollte es sein: erst die Prüfung, dann die Unterdrückung — und nicht umgekehrt. Ohne diese vorherige Prüfung wäre die Unterdrückung eine leere Behauptung. Diese Aufgabe ist die umfangreichste des Moduls und zugleich die befriedigendste. Am Ende listet Lagerpuls seine Befehle mit Beschreibung und in der richtigen Reihenfolge auf, und zwar ohne dass irgendwo eine zentrale Liste gepflegt wird.
18:01 Der Hinweis nennt den Prüfstein: Ein neuer Befehl darf danach nur noch eine neue Klasse samt Annotation kosten. Wenn Sie an einer zweiten Stelle etwas eintragen müssen, ist das Register noch nicht fertig — und genau diese zweite Stelle wollten wir ja loswerden. Der erste Punkt ist der Klassiker mit der Retention, der uns in diesem Modul schon zweimal begegnet ist — er ist es wert, dreimal genannt zu werden.
18:25 Der zweite ist die fehlende Prüfung auf null. Der dritte ist die ausgelassene Schnittstellenprüfung, die den Fehler nur verschiebt. Und der vierte ist ein Leistungsproblem: Das Register bei jedem Aufruf neu aufzubauen bedeutet, bei jedem Kommando das gesamte Paket zu durchsuchen. Einmal aufbauen und behalten ist hier eindeutig richtig.
Verarbeitung beim Übersetzen
18:44 Das letzte Kapitel dieses Seminars behandeln wir als Ausblick. Annotationsprozessoren sind ein eigenes, umfangreiches Thema, und die wenigsten von Ihnen werden je einen schreiben — das ist auch völlig in Ordnung. Aber es lohnt sich zu wissen, dass es sie gibt, was sie leisten und wo man anfangen würde, wenn der Fall doch einmal eintritt.
19:03 Denn dann wüssten Sie sonst nicht einmal, wonach Sie suchen sollen. Ich halte es kurz und zeige das Grundgerüst. Ein Annotationsprozessor erweitert den Compiler. Sie erben von AbstractProcessor und setzen die Methode process um. Zwei Annotationen sagen dem Compiler, welche Annotationen dieser Prozessor bearbeitet und welche Java-Version er unterstützt.
19:24 Der Compiler ruft Ihren Prozessor dann während der Übersetzung auf und übergibt ihm die gefundenen Annotationen samt den Quelltextelementen, an denen sie stehen. Das ist derselbe Mechanismus, über den Lombok seinen Code erzeugt — und das ist gleichzeitig der häufigste sinnvolle Anwendungsfall. Das Grundgerüst, mehr nicht. Oben die beiden Annotationen mit dem vollqualifizierten Namen der zu verarbeitenden Annotation und der unterstützten Java-Version.
19:51 Darunter die process-Methode, die vom Compiler aufgerufen wird. Ein Hinweis aus der Praxis steht in der Fußzeile: Meldungen geben Sie über den Messager aus, nicht über System.out. Dann erscheinen sie wie normale Compilermeldungen, mit Dateiname und Zeilennummer — und lassen sich in der IDE anklicken. Das ist der Unterschied zwischen einem Werkzeug und einem Skript.
20:13 Der Ablauf ist etwas umständlich, und das ist gut zu wissen, bevor man anfängt. Die Annotation bekommt Retention SOURCE, denn sie wird ja beim Übersetzen verbraucht. Der Prozessor selbst muss zuerst und getrennt übersetzt werden — er kann nicht im selben Durchlauf entstehen, in dem er gebraucht wird. Dann legen Sie ihn in den Klassenpfad und weisen den Compiler mit einer Option auf ihn hin.
20:35 In modernen Bauwerkzeugen ist das alles konfiguriert, aber wenn Sie es einmal von Hand gemacht haben, verstehen Sie, was dort passiert. Wann lohnt sich dieser Aufwand? Wenn aus der Annotation Quellcode entstehen soll — das ist der Hauptfall. Wenn die Annotation zur Laufzeit gar nicht mehr gebraucht wird und den Bytecode nicht belasten soll.
20:55 Oder wenn Fehler schon beim Übersetzen gemeldet werden sollen statt erst im Betrieb; ein Prozessor kann nämlich eigene Fehlermeldungen erzeugen. In allen anderen Fällen ist der Weg über Reflection einfacher, schneller umgesetzt und leichter zu testen. Meine Empfehlung: Fangen Sie immer bei Reflection an. Der erste Punkt ist die falsche Retention — RUNTIME, obwohl der Prozessor beim Übersetzen arbeitet; die Annotation belastet dann unnötig den Bytecode.
21:22 Der zweite ist System.out statt des Messagers, mit dem Ergebnis, dass Meldungen im Bauprotokoll untergehen. Der dritte ist das Henne-Ei-Problem beim Übersetzen. Und der vierte ist eine Frage der Verhältnismäßigkeit: einen Prozessor zu schreiben, wo eine Auswertung zur Laufzeit in dreißig Zeilen fertig wäre. Vier Sätze aus diesem Modul. Eine Annotation ist eine Angabe — die Wirkung kommt immer vom verarbeitenden Werkzeug.
21:48 Target und Retention entscheiden, wo sie stehen darf und wie lange sie lebt; wenn etwas nicht funktioniert, schauen Sie zuerst auf die Retention. Zur Laufzeit findet Reflection nur, was mit RUNTIME aufbewahrt wurde. Und Annotationsprozessoren lohnen sich dann, wenn beim Übersetzen tatsächlich etwas entstehen soll — für alles andere ist Reflection der einfachere Weg.
22:10 Damit sind wir am Ende. Werfen wir einen Blick zurück auf drei Tage. Records und versiegelte Typen haben wir als zwei Hälften derselben Sache kennengelernt — das UND und das ODER jedes Datenmodells. Enums haben sich als vollwertige Klassen erwiesen, die Daten und Verhalten tragen können. Bei Generics haben wir vom Grundbegriff bis zur Typlöschung alles aufgebaut, und PECS ist Ihre Merkregel für Wildcards. Und Annotationen beschreiben, ohne selbst zu wirken.
22:37 Was diese Themen verbindet, ist ein einziger Gedanke: möglichst viel vom Betrieb in den Compiler verlagern.
Danke!
22:44 Vielen Dank für Ihre Aufmerksamkeit und für drei intensive Tage. Alle Materialien und der vollständige Beispielcode zu Lagerpuls liegen in der Freigabe — das Projekt wächst über alle neun Module und eignet sich gut zum Nachvollziehen. Wenn Sie im Alltag über etwas stolpern, das wir hier besprochen haben, oder wenn eine Fehlermeldung Sie ratlos zurücklässt: Melden Sie sich gern.
23:06 Und ein letzter Gedanke zum Mitnehmen — die meisten dieser Sprachmittel sparen keine Tipparbeit. Sie sparen Fehlersuche. Das ist der eigentliche Gewinn.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →