Hur man beskriver Java-kod med anteckningar

Du har förmodligen stött på situationer där du behöver associera metadata (data som beskriver annan data) till klasser, metoder och / eller andra applikationselement. Till exempel kan ditt programmeringsteam behöva identifiera oavslutade klasser i en stor applikation. För varje oavslutad klass skulle metadata sannolikt innehålla namnet på utvecklaren som är ansvarig för att avsluta klassen och klassens förväntade slutdatum.

Innan Java 5 var kommentarer den enda flexibla mekanismen som Java hade att erbjuda för att associera metadata med applikationselement. Kommentarer är dock ett dåligt val. Eftersom kompilatorn ignorerar dem är kommentarer inte tillgängliga vid körning. Och även om de fanns tillgängliga skulle texten behöva analyseras för att få viktiga dataobjekt. Utan att standardisera hur dataobjekten är specificerade kan dessa data vara omöjliga att analysera.

ladda ner Skaffa koden Ladda ner källkoden för exempel i denna Java 101-handledning. Skapad av Jeff Friesen för.

Icke-standardiserade annoteringsmekanismer

Java tillhandahåller icke-standardiserade mekanismer för att associera metadata med applikationselement. Med det transientreserverade ordet kan du till exempel anteckna (associera data med) fält som ska uteslutas under serialisering.

Java 5 ändrade allt genom att införa anteckningar , en standardmekanism för att associera metadata med olika applikationselement. Denna mekanism består av fyra komponenter:

  • En @interfacemekanism för att deklarera annoteringstyper.
  • Meta-annotationstyper, som du kan använda för att identifiera de applikationselement som en annoteringstyp gäller för; för att identifiera livslängden för en anteckning (en instans av en anteckningstyp); och mer.
  • Stöd för bearbetning av anteckningar via ett tillägg till Java Reflection API (som kommer att diskuteras i en framtida artikel), som du kan använda för att upptäcka ett programs runtime-anteckningar och ett generaliserat verktyg för att behandla anteckningar.
  • Standardannoteringstyper.

Jag ska förklara hur man använder dessa komponenter när vi arbetar oss igenom den här artikeln.

Deklarerar anteckningstyper med @interface

Du kan deklarera en anteckningstyp genom att ange @symbolen omedelbart följt av det interfacereserverade ordet och en identifierare. Till exempel förklarar Listing 1 en enkel kommentarstyp som du kan använda för att kommentera trådsäker kod.

Listning 1:ThreadSafe.java

public @interface ThreadSafe {}

Efter att ha förklarat den här anteckningstypen, prefixerar du de metoder som du anser är trådsäkra med förekomster av den här typen genom att @omedelbart lägga förföljande följt av typnamnet till metodrubrikerna . Listning 2 erbjuder ett enkelt exempel där main()metoden antecknas @ThreadSafe.

Listing 2:AnnDemo.java (version 1)

public class AnnDemo {@ThreadSafe public static void main (String [] args) {}}

ThreadSafeinstanser tillhandahåller inga andra metadata än namntypens namn. Du kan dock tillhandahålla metadata genom att lägga till element till den här typen, där ett element är ett metodhuvud som placeras i kommentarstypens kropp.

Förutom att de inte har kodorgan, är element föremål för följande begränsningar:

  • Metodhuvudet kan inte deklarera parametrar.
  • Metodrubriken kan inte ge en kastklausul.
  • Metoden header returtyp måste vara en primitiv typ (t ex int), java.lang.String, java.lang.Class, en enum, en typanteckning, eller ett system av en av dessa typer. Ingen annan typ kan anges för returtypen.

Som ett annat exempel presenterar Listing 3 en ToDoannoteringstyp med tre element som identifierar ett visst kodningsjobb, som anger datumet då jobbet ska avslutas och namnet på den kodare som är ansvarig för att slutföra jobbet.

Listing 3:ToDo.java (version 1)

public @interface ToDo {int id (); Sträng finishDate (); Strängkodare () standard "n / a"; }

Observera att varje element förklarar ingen parameter (er) eller kastar sats, har en laglig returtyp ( inteller String) och avslutas med semikolon. Det slutliga elementet avslöjar också att ett standardreturvärde kan anges; detta värde returneras när en kommentar inte tilldelar elementet ett värde.

Listning 4 använder för ToDoatt kommentera en oavslutad klassmetod.

Listing 4:AnnDemo.java (version 2)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"New York", "Melbourne", "Beijing", "Moscow", "Paris", "London"}; sortera (städer); } @ToDo (id = 1000, finishDate = "10/10/2019", coder = "John Doe") statisk ogiltig sort (Objekt [] objekt) {}}

Listning 4 tilldelar ett metadataobjekt till varje element; till exempel 1000tilldelas id. Till skillnad från coderde idoch finishDatemåste elementen specificeras; I annat fall rapporterar kompilatorn ett fel. När coderett värde inte tilldelas antar det standardvärdet "n/a".

Java tillhandahåller ett speciellt String value()element som kan användas för att returnera en kommaseparerad lista med metadataobjekt. Listning 5 visar detta element i en ombyggd version av ToDo.

Listing 5:ToDo.java (version 2)

public @interface ToDo {Strängvärde (); }

När value()är det enda elementet för en anteckningstyp, behöver du inte ange valueoch =tilldelningsoperatören när du tilldelar en sträng till detta element. Listning 6 visar båda metoderna.

Listing 6:AnnDemo.java (version 3)

public class AnnDemo {public static void main (String [] args) {String [] cities = {"New York", "Melbourne", "Beijing", "Moscow", "Paris", "London"}; sortera (städer); } @ToDo (value = "1000,10 / 10/2019, John Doe") statisk ogiltig sort (Objekt [] objekt) {} @ToDo ("1000,10 / 10/2019, John Doe") statisk boolesk sökning ( Object [] objects, Object key) {return false; }}

Använda meta-annoteringstyper - problemet med flexibilitet

Du kan kommentera typer (t.ex. klasser), metoder, lokala variabler och mer. Denna flexibilitet kan dock vara problematisk. Du kanske till exempel vill begränsa dig ToDotill metoder, men ingenting hindrar att den används för att kommentera andra applikationselement, vilket visas i Listing 7.

Listing 7:AnnDemo.java (version 4)

@ToDo ("1000,10 / 10/2019, John Doe") offentlig klass AnnDemo {public static void main (String [] args) {@ToDo (value = "1000,10 / 10/2019, John Doe") String [] städer = {"New York", "Melbourne", "Peking", "Moskva", "Paris", "London"}; sortera (städer); } @ToDo (value = "1000,10 / 10/2019, John Doe") statisk ogiltig sort (Objekt [] objekt) {} @ToDo ("1000,10 / 10/2019, John Doe") statisk boolesk sökning ( Object [] objects, Object key) {return false; }}

I Listing 7 används ToDoockså för att kommentera AnnDemoklassen och den citieslokala variabeln. Närvaron av dessa felaktiga anteckningar kan förvirra någon som granskar din kod eller till och med dina egna anteckningsbearbetningsverktyg. För de tider när du behöver begränsa en annoteringstyps flexibilitet, erbjuder Java Targetkommentarstypen i sitt java.lang.annotationpaket.

Targetär en meta-annotationstyp  - en annotationstyp vars annoteringar kommenterar annoteringstyper, i motsats till en icke-meta-annotationstyp vars anteckningar kommenterar applikationselement, såsom klasser och metoder. Den identifierar de typer av applikationselement som en annoteringstyp är tillämplig på. Dessa element är identifierade med Targets ElementValue[] value()elementet.

java.lang.annotation.ElementType is an enum whose constants describe application elements. For example, CONSTRUCTOR applies to constructors and PARAMETER applies to parameters. Listing 8 refactors Listing 5’s ToDo annotation type to restrict it to methods only.

Listing 8:ToDo.java (version 3)

import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); }

Given the refactored ToDo annotation type, an attempt to compile Listing 7 now results in the following error message:

AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo("1000,10/10/2019,John Doe") ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value="1000,10/10/2019,John Doe") ^ 2 errors

Additional meta-annotation types

Java 5 introduced three additional meta-annotation types, which are found in the java.lang.annotation package:

  • Retention indicates how long annotations with the annotated type are to be retained. This type’s associated java.lang.annotation.RetentionPolicy enum declares constants CLASS (compiler records annotations in class file; virtual machine doesn’t retain them to save memory — default policy), RUNTIME (compiler records annotations in class file; virtual machine retains them), and SOURCE (compiler discards annotations).
  • Documented indicates that instances of Documented-annotated annotations are to be documented by javadoc and similar tools.
  • Inherited indicates that an annotation type is automatically inherited.

Java 8 introduced the java.lang.annotation.Repeatable meta-annotation type. Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. In other words, you can apply multiple annotations from the same repeatable annotation type to an application element, as demonstrated here:

@ToDo(value = "1000,10/10/2019,John Doe") @ToDo(value = "1001,10/10/2019,Kate Doe") static void sort(Object[] objects) { }

This example assumes that ToDo has been annotated with the Repeatable annotation type.

Processing annotations

Annotations are meant to be processed; otherwise, there’s no point in having them. Java 5 extended the Reflection API to help you create your own annotation processing tools. For example, Class declares an Annotation[] getAnnotations() method that returns an array of java.lang.Annotation instances describing annotations present on the element described by the Class object.

Listing 9 presents a simple application that loads a class file, interrogates its methods for ToDo annotations, and outputs the components of each found annotation.

Listing 9:AnnProcDemo.java

import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("usage: java AnnProcDemo classfile"); return; } Method[] methods = Class.forName(args[0]).getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(ToDo.class)) { ToDo todo = methods[i].getAnnotation(ToDo.class); String[] components = todo.value().split(","); System.out.printf("ID = %s%n", components[0]); System.out.printf("Finish date = %s%n", components[1]); System.out.printf("Coder = %s%n%n", components[2]); } } } }

After verifying that exactly one command-line argument (identifying a class file) has been specified, main() loads the class file via Class.forName(), invokes getMethods() to return an array of java.lang.reflect.Method objects identifying all public methods in the class file, and processes these methods.

Method processing begins by invoking Method’s boolean isAnnotationPresent(Class annotationClass) method to determine if the annotation described by ToDo.class is present on the method. If so, Method’s T getAnnotation(Class annotationClass) method is called to obtain the annotation.

The ToDo annotations that are processed are those whose types declare a single String value() element (see Listing 5). Because this element’s string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.

Compile this source code (javac AnnProcDemo.java). Before you can run the application, you’ll need a suitable class file with @ToDo annotations on its public methods. For example, you could modify Listing 6’s AnnDemo source code to include public in its sort() and search() method headers. You’ll also need Listing 10’s ToDo annotation type, which requires the RUNTIME retention policy.

Listing 10:ToDo.java (version 4)

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ToDo { String value(); }

Compile the modified AnnDemo.java and Listing 10, and execute the following command to process AnnDemo’s ToDo annotations:

java AnnProcDemo AnnDemo

If all goes well, you should observe the following output:

ID = 1000 Finish date = 10/10/2019 Coder = John Doe ID = 1000 Finish date = 10/10/2019 Coder = John Doe

Processing annotations with apt and the Java compiler

Java 5 introducerade ett aptverktyg för att behandla anteckningar på ett generaliserat sätt. Java 6 migrerade aptfunktionalitet till javackompileringsverktyget och Java 7 upphörde apt, vilket därefter togs bort (börjar med Java 8).

Standardannoteringstyper

Tillsammans med Target, Retention, Documentedoch Inherited, Java 5 infördes java.lang.Deprecated, java.lang.Overrideoch java.lang.SuppressWarnings. Dessa tre anteckningstyper är utformade för att endast användas i kompilatorkontext, varför deras lagringspolicyer är inställda på SOURCE.

Föråldrad