Java Language
anteckningar
Sök…
Introduktion
I Java är en kommentar en form av syntaktiska metadata som kan läggas till Java-källkoden. Den tillhandahåller data om ett program som inte ingår i själva programmet. Anteckningar har ingen direkt effekt på hur koden de kommenterar fungerar. Klasser, metoder, variabler, parametrar och paket får antecknas.
Syntax
- @AnnotationName // 'Marker annotation' (inga parametrar)
- @AnnotationName (someValue) // ställer in parameter med namnet 'värde'
- @AnnotationName (param1 = value1) // namngivna parameter
- @AnnotationName (param1 = value1, param2 = value2) // flera namngivna parametrar
- @AnnotationName (param1 = {1, 2, 3}) // namngiven array-parameter
- @AnnotationName ({value1}) // matris med enstaka element som parameter med namnet 'värde'
Anmärkningar
Parametertyper
Endast konstanta uttryck av följande typer är tillåtna för parametrar såväl som matriser av dessa typer:
-
String
-
Class
- primitiva typer
- Enumtyper
- Annotationstyper
Inbyggda kommentarer
Standardversionen av Java kommer med några fördefinierade kommentarer. Du behöver inte definiera dem själv och du kan använda dem omedelbart. De tillåter kompilatorn att möjliggöra en grundläggande kontroll av metoder, klasser och kod.
@Åsidosätta
Den här anteckningen gäller en metod och säger att den här metoden måste åsidosätta en superklass-metod eller implementera en abstrakt superklass-metoddefinition. Om denna kommentar används med någon annan typ av metod, kommer kompilatorn att kasta ett fel.
Betong superklass
public class Vehicle {
public void drive() {
System.out.println("I am driving");
}
}
class Car extends Vehicle {
// Fine
@Override
public void drive() {
System.out.prinln("Brrrm, brrm");
}
}
Abstrakt klass
abstract class Animal {
public abstract void makeNoise();
}
class Dog extends Animal {
// Fine
@Override
public void makeNoise() {
System.out.prinln("Woof");
}
}
Fungerar inte
class Logger1 {
public void log(String logString) {
System.out.prinln(logString);
}
}
class Logger2 {
// This will throw compile-time error. Logger2 is not a subclass of Logger1.
// log method is not overriding anything
@Override
public void log(String logString) {
System.out.println("Log 2" + logString);
}
}
Det huvudsakliga syftet är att fånga in felaktig typ, där du tror att du åsidosätter en metod, men faktiskt definierar en ny.
class Vehicle {
public void drive() {
System.out.println("I am driving");
}
}
class Car extends Vehicle {
// Compiler error. "dirve" is not the correct method name to override.
@Override
public void dirve() {
System.out.prinln("Brrrm, brrm");
}
}
Observera att betydelsen av @Override
har förändrats över tid:
- I Java 5 betydde det att den antecknade metoden var tvungen att åsidosätta en icke-abstrakt metod som deklarerats i superklasskedjan.
- Från Java 6 och framåt är det också nöjd om den antecknade metoden implementerar en abstrakt metod som deklareras i klasserna superklass / gränssnittshierarki.
(Detta kan ibland orsaka problem när du backar portkoden till Java 5.)
@Deprecated
Detta markerar metoden som avskrivet. Det kan finnas flera skäl till detta:
API är bristfälligt och är opraktiskt att fixa,
användning av API kommer sannolikt att leda till fel,
API har ersatts av ett annat API,
API är föråldrad,
API är experimentellt och är föremål för oförenliga förändringar,
eller någon kombination av ovanstående.
Det specifika skälet till avskrivning finns vanligtvis i dokumentationen för API: n.
Annotationen får kompilatorn att avge ett fel om du använder den. IDE: er kan också lyfta fram den här metoden på något sätt som föråldrad
class ComplexAlgorithm {
@Deprecated
public void oldSlowUnthreadSafeMethod() {
// stuff here
}
public void quickThreadSafeMethod() {
// client code should use this instead
}
}
@SuppressWarnings
I nästan alla fall, när kompilatorn avger en varning, är den lämpligaste åtgärden att fixa orsaken. I vissa fall (till exempel Generics-kod med exempelvis säker-säker pre-generics-kod) är detta kanske inte möjligt och det är bättre att undertrycka de varningar du förväntar dig och inte kan fixa, så att du tydligare kan se oväntade varningar.
Denna kommentar kan tillämpas på en hel klass, metod eller rad. Det tar kategorin av varning som en parameter.
@SuppressWarnings("deprecation")
public class RiddledWithWarnings {
// several methods calling deprecated code here
}
@SuppressWarning("finally")
public boolean checkData() {
// method calling return from within finally block
}
Det är bättre att begränsa kommentarens omfattning så mycket som möjligt för att förhindra att oväntade varningar också undertrycks. Till exempel begränsa annoteringens räckvidd till en enda rad:
ComplexAlgorithm algorithm = new ComplexAlgorithm();
@SuppressWarnings("deprecation") algoritm.slowUnthreadSafeMethod();
// we marked this method deprecated in an example above
@SuppressWarnings("unsafe") List<Integer> list = getUntypeSafeList();
// old library returns, non-generic List containing only integers
Varningarna som stöds av denna kommentar kan variera från kompilator till kompilator. Endast de unchecked
och deprecation
nämns specifikt i JLS. Oigenkända varningstyper ignoreras.
@SafeVarargs
På grund av radering av typ omvandlas void method(T... t)
till void method(Object[] t)
betyder att kompilatorn inte alltid kan verifiera att användningen av vargar är typsäker. Till exempel:
private static <T> void generatesVarargsWarning(T... lists) {
Det finns fall där användningen är säker, i vilket fall du kan kommentera metoden med SafeVarargs
anteckningen för att undertrycka varningen. Detta döljer uppenbarligen varningen om din användning också är osäker.
@FunctionalInterface
Detta är en valfri annotation som används för att markera en FunctionalInterface. Det kommer att få kompilatorn att klaga om den inte överensstämmer med FunctionalInterface-specifikationen (har en enda abstrakt metod)
@FunctionalInterface
public interface ITrade {
public boolean check(Trade t);
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Runtime-kommentarer kontrolleras via reflektion
Java's Reflection API låter programmeraren utföra olika kontroller och operationer på klassfält, metoder och kommentarer under körning. För att en kommentar ska kunna synas vid körning måste RetentionPolicy
ändras till RUNTIME
, vilket visas i exemplet nedan:
@interface MyDefaultAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyRuntimeVisibleAnnotation {
}
public class AnnotationAtRuntimeTest {
@MyDefaultAnnotation
static class RuntimeCheck1 {
}
@MyRuntimeVisibleAnnotation
static class RuntimeCheck2 {
}
public static void main(String[] args) {
Annotation[] annotationsByType = RuntimeCheck1.class.getAnnotations();
Annotation[] annotationsByType2 = RuntimeCheck2.class.getAnnotations();
System.out.println("default retention: " + Arrays.toString(annotationsByType));
System.out.println("runtime retention: " + Arrays.toString(annotationsByType2));
}
}
Definiera annotationstyper
Annotationstyper definieras med @interface
. Parametrar definieras liknande metoder för ett vanligt gränssnitt.
@interface MyAnnotation {
String param1();
boolean param2();
int[] param3(); // array parameter
}
Ursprungliga värden
@interface MyAnnotation {
String param1() default "someValue";
boolean param2() default true;
int[] param3() default {};
}
Meta-Anteckningar
Meta-kommentarer är anteckningar som kan tillämpas på annotationstyper. Speciell fördefinierad metaanotation definierar hur annotationstyper kan användas.
@Mål
@Target
begränsar de typer annotationen kan tillämpas på.
@Target(ElementType.METHOD)
@interface MyAnnotation {
// this annotation can only be applied to methods
}
Flera värden kan läggas till med hjälp av arraynotation, t.ex. @Target({ElementType.FIELD, ElementType.TYPE})
Tillgängliga värden
ElementType | mål | exempel användning på målelement |
---|---|---|
ANNOTATION_TYPE | annotationstyper | |
KONSTRUKTÖR | konstruktörer | |
FÄLT | fält, enumkonstanter | |
LOCAL_VARIABLE | variabla deklarationer inom metoder | |
PAKET | paket (i package-info.java ) | |
METOD | metoder | |
PARAMETER | metod / konstruktörsparametrar | |
TYP | klasser, gränssnitt, enums | |
ElementType | mål | exempel användning på målelement |
---|---|---|
TYPE_PARAMETER | Skriv in parameterdeklarationer | |
TYPE_USE | Användning av en typ | |
@Bibehållande
Meta-anteckningen @Retention
definierar synligheten för anteckningen under applikationens sammanställningsprocess eller körning. Som standard ingår kommentarer i .class
filer, men är inte synliga vid körning. För att göra en kommentar tillgänglig vid körning måste RetentionPolicy.RUNTIME
ställas in för den annotationen.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
// this annotation can be accessed with reflections at runtime
}
Tillgängliga värden
RetentionPolicy | Effekt |
---|---|
KLASS | Anteckningen är tillgänglig i .class filen, men inte vid körning |
KÖRNING | Anteckningen är tillgänglig vid körning och kan nås via reflektion |
KÄLLA | Anteckningen är tillgänglig vid kompileringstidpunkten, men läggs inte till i .class . Annoteringen kan t.ex. användas av en annotationsprocessor. |
@Documented
Den @Documented
används för att markera anteckningar vars användning bör dokumenteras av API-dokumentationsgeneratorer som javadoc . Det har inga värden. Med @Documented
kommer alla klasser som använder annotationen att lista den på deras genererade dokumentationssida. Utan @Documented
är det inte möjligt att se vilka klasser som använder annotationen i dokumentationen.
@Inherited
@Inherited
meta-annotation är relevant för anteckningar som tillämpas på klasser. Det har inga värden. Att markera en kommentar som @Inherited
ändrar sättet på vilket kommentarfrågan fungerar.
- För en icke-ärvt kommentar undersöker frågan bara klassen som granskas.
- För en ärvd kommentar kommer frågan också att kontrollera superklasskedjan (rekursivt) tills en instans av annotationen hittas.
Observera att endast superklasserna fråges: eventuella anteckningar kopplade till gränssnitt i klasserhierarkin ignoreras.
@Repeatable
@Repeatable
meta-annotation lades till i Java 8. Det indikerar att flera instanser av annotationen kan bifogas till annotationens mål. Denna metanotation har inga värden.
Få annotationsvärden vid körning
Du kan hämta de aktuella egenskaperna för annotationen genom att använda Reflektion för att hämta metoden eller fältet eller klassen som har en kommentar tillämpad på och sedan hämta önskade egenskaper.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String key() default "foo";
String value() default "bar";
}
class AnnotationExample {
// Put the Annotation on the method, but leave the defaults
@MyAnnotation
public void testDefaults() throws Exception {
// Using reflection, get the public method "testDefaults", which is this method with no args
Method method = AnnotationExample.class.getMethod("testDefaults", null);
// Fetch the Annotation that is of type MyAnnotation from the Method
MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
// Print out the settings of the Annotation
print(annotation);
}
//Put the Annotation on the method, but override the settings
@MyAnnotation(key="baz", value="buzz")
public void testValues() throws Exception {
// Using reflection, get the public method "testValues", which is this method with no args
Method method = AnnotationExample.class.getMethod("testValues", null);
// Fetch the Annotation that is of type MyAnnotation from the Method
MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
// Print out the settings of the Annotation
print(annotation);
}
public void print(MyAnnotation annotation) {
// Fetch the MyAnnotation 'key' & 'value' properties, and print them out
System.out.println(annotation.key() + " = " + annotation.value());
}
public static void main(String[] args) {
AnnotationExample example = new AnnotationExample();
try {
example.testDefaults();
example.testValues();
} catch( Exception e ) {
// Shouldn't throw any Exceptions
System.err.println("Exception [" + e.getClass().getName() + "] - " + e.getMessage());
e.printStackTrace(System.err);
}
}
}
Utgången kommer att vara
foo = bar
baz = buzz
Upprepande kommentarer
Tills Java 8 kunde två instanser av samma kommentar inte tillämpas på ett enda element. Standardlösningen var att använda en containeranteckning som innehöll en matris med någon annan annotation:
// Author.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String value();
}
// Authors.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Authors {
Author[] value();
}
// Test.java
@Authors({
@Author("Mary"),
@Author("Sam")
})
public class Test {
public static void main(String[] args) {
Author[] authors = Test.class.getAnnotation(Authors.class).value();
for (Author author : authors) {
System.out.println(author.value());
// Output:
// Mary
// Sam
}
}
}
Java 8 tillhandahåller ett renare, mer transparent sätt att använda behållaranteckningar med @Repeatable
anteckningen. Först lägger vi till detta i Author
:
@Repeatable(Authors.class)
Detta säger Java att behandla flera @Author
kommentarer som om de var omgiven av @Authors
behållaren. Vi kan också använda Class.getAnnotationsByType()
att få åtkomst till @Author
arrayen av sin egen klass istället för genom sin behållare:
@Author("Mary")
@Author("Sam")
public class Test {
public static void main(String[] args) {
Author[] authors = Test.class.getAnnotationsByType(Author.class);
for (Author author : authors) {
System.out.println(author.value());
// Output:
// Mary
// Sam
}
}
}
Ärvda kommentarer
Som standard klassanteckningar gäller inte för typer som utvidgar dem. Detta kan ändras genom att lägga till @Inherited
kommentaren i kommentardefinitionen
Exempel
Tänk på följande två kommentarer:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotationType {
}
och
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UninheritedAnnotationType {
}
Om tre klasser kommenteras så här:
@UninheritedAnnotationType
class A {
}
@InheritedAnnotationType
class B extends A {
}
class C extends B {
}
kör den här koden
System.out.println(new A().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println(new B().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println(new C().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println("_________________________________");
System.out.println(new A().getClass().getAnnotation(UninheritedAnnotationType.class));
System.out.println(new B().getClass().getAnnotation(UninheritedAnnotationType.class));
System.out.println(new C().getClass().getAnnotation(UninheritedAnnotationType.class));
kommer att skriva ut ett resultat som liknar detta (beroende på anteckningarnas paket):
null
@InheritedAnnotationType()
@InheritedAnnotationType()
_________________________________
@UninheritedAnnotationType()
null
null
Observera att kommentarer endast kan ärvas från klasser, inte gränssnitt.
Kompilera tidsbearbetning med kommentarprocessor
Detta exempel visar hur man gör kompilering av tidskontroll av ett kommenterat element.
Anteckningen
@Setter
anteckningen är en markör som kan tillämpas på metoder. Anteckningen kommer att kasseras under sammanställningen och kommer inte att finnas tillgänglig efteråt.
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Setter {
}
Annotationsprocessorn
SetterProcessor
används av kompilatorn för att bearbeta kommentarerna. Den kontrollerar, om de metoder som är markerade med @Setter
anteckningen är public
, icke- static
metoder med ett namn som börjar med set
och som har en stor bokstav som fjärde bokstaven. Om ett av dessa villkor inte uppfylls, skrivs ett fel till Messager
. Kompilatorn skriver detta till stderr, men andra verktyg kan använda denna information annorlunda. NetBeans IDE tillåter exempelvis användaren att ange kommentarprocessorer som används för att visa felmeddelanden i redigeraren.
package annotation.processor;
import annotation.Setter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"annotation.Setter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {
private Messager messager;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// get elements annotated with the @Setter annotation
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Setter.class);
for (Element element : annotatedElements) {
if (element.getKind() == ElementKind.METHOD) {
// only handle methods as targets
checkMethod((ExecutableElement) element);
}
}
// don't claim annotations to allow other processors to process them
return false;
}
private void checkMethod(ExecutableElement method) {
// check for valid name
String name = method.getSimpleName().toString();
if (!name.startsWith("set")) {
printError(method, "setter name must start with \"set\"");
} else if (name.length() == 3) {
printError(method, "the method name must contain more than just \"set\"");
} else if (Character.isLowerCase(name.charAt(3))) {
if (method.getParameters().size() != 1) {
printError(method, "character following \"set\" must be upper case");
}
}
// check, if setter is public
if (!method.getModifiers().contains(Modifier.PUBLIC)) {
printError(method, "setter must be public");
}
// check, if method is static
if (method.getModifiers().contains(Modifier.STATIC)) {
printError(method, "setter must not be static");
}
}
private void printError(Element element, String message) {
messager.printMessage(Diagnostic.Kind.ERROR, message, element);
}
@Override
public void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// get messager for printing errors
messager = processingEnvironment.getMessager();
}
}
Förpackning
För att kunna appliceras av kompilatorn måste annotationsprocessorn göras tillgänglig för SPI (se ServiceLoader ).
För att göra detta måste en textfil META-INF/services/javax.annotation.processing.Processor
läggas till i burkfilen som innehåller annotationsprocessorn och anteckningen utöver de andra filerna. Filen måste innehålla det fullt kvalificerade namnet på annotationsprocessorn, dvs den ska se ut så här
annotation.processor.SetterProcessor
Vi antar att burkfilen heter AnnotationProcessor.jar
nedan.
Exempel kommenterad klass
Följande klass är exempelklass i standardpaketet med anteckningar som tillämpas på korrekta element enligt lagringspolicyn. Men endast annotationsprocessorn betraktar endast den andra metoden som ett giltigt anteckningsmål.
import annotation.Setter;
public class AnnotationProcessorTest {
@Setter
private void setValue(String value) {}
@Setter
public void setString(String value) {}
@Setter
public static void main(String[] args) {}
}
Använda annotationsprocessorn med javac
Om kommentarprocessorn upptäcks med hjälp av SPI, används den automatiskt för att behandla kommenterade element. Exempelvis sammanställa klassen AnnotationProcessorTest
med
javac -cp AnnotationProcessor.jar AnnotationProcessorTest.java
ger följande utgång
AnnotationProcessorTest.java:6: error: setter must be public
private void setValue(String value) {}
^
AnnotationProcessorTest.java:12: error: setter name must start with "set"
public static void main(String[] args) {}
^
2 errors
istället för att sammanställa normalt. Ingen .class
skapas.
Detta kan förhindras genom att ange -proc:none
alternativ för javac
. Du kan också avstå från den vanliga sammanställningen genom att ange -proc:only
istället.
IDE-integration
Netbeans
Annotationsprocessorer kan användas i NetBeans-redigeraren. För att göra detta måste annotationsprocessorn anges i projektinställningarna:
gå till
Project Properties
>Build
>Compiling
lägg till kryssmarkeringar för
Enable Annotation Processing
ochEnable Annotation Processing in Editor
Klicka på
Add
bredvid listan med kommentarernai popup-fönstret som visas anger du det fullt kvalificerade klassnamnet för annotationsprocessorn och klickar på
Ok
.
Resultat
Idén bakom kommentarer
Java Language Specification beskriver kommentarer på följande sätt:
En kommentar är en markör som associerar information med ett programkonstrukt, men har ingen effekt vid körtid.
Kommentarer kan förekomma före typer eller deklarationer. Det är möjligt för dem att visas på en plats där de kan gälla både för en typ eller en deklaration.
Vad exakt en kommentar gäller för regleras av " @Target
" @Target
. Se "Definiera annotationstyper" för mer information.
Anteckningar används för en mängd syften. Ramar som Spring och Spring-MVC använder anteckningar för att definiera var beroenden ska injiceras eller var förfrågningar ska dirigeras.
Andra ramverk använder anteckningar för kodgenerering. Lombok och JPA är främsta exempel som använder anteckningar för att generera Java (och SQL) -kod.
Detta ämne syftar till att ge en omfattande översikt av:
Hur definierar du dina egna kommentarer?
Vilka kommentarer ger Java-språket?
Hur används anteckningar i praktiken?
Anteckningar för "detta" och mottagarparametrar
När Java-anteckningar först introducerades fanns det ingen möjlighet att kommentera målet för en instansmetod eller den dolda konstruktörsparametern för en konstruktör för inre klasser. Detta åtgärdades i Java 8 med tillägg av mottagarparameterdeklarationer ; se JLS 8.4.1 .
Mottagarparametern är en valfri syntaktisk enhet för en instansmetod eller en inre klassens konstruktör. För en instansmetod representerar mottagarparametern objektet för vilket metoden åberopas. För en inre klassens konstruktör representerar mottagarparametern den omedelbart omslutande instansen för det nybyggda objektet. I vilket fall som helst existerar mottagarparametern enbart för att tillåta typen av det representerade objektet att anges i källkod, så att typen kan antecknas. Mottagarparametern är inte en formell parameter; mer exakt, det är inte en deklaration av någon form av variabel (§4.12.3), den är aldrig bunden till något värde som överförs som ett argument i ett metodkallningsuttryck eller ett kvalificerat uttryck för klassinstans, och det har ingen som helst effekt på körtid.
Följande exempel illustrerar syntaxen för båda typerna av mottagarparameter:
public class Outer {
public class Inner {
public Inner (Outer this) {
// ...
}
public void doIt(Inner this) {
// ...
}
}
}
Det enda syftet med mottagarparametrarna är att låta dig lägga till kommentarer. Du kan till exempel ha en anpassad kommentar @IsOpen
vars syfte är att hävda att ett Closeable
objekt inte har stängts när en metod kallas. Till exempel:
public class MyResource extends Closeable {
public void update(@IsOpen MyResource this, int value) {
// ...
}
public void close() {
// ...
}
}
På en nivå kan @IsOpen
anteckningen på this
helt enkelt fungera som dokumentation. Men vi kan potentiellt göra mer. Till exempel:
- En kommentarprocessor kan infoga en körtidskontroll om att
this
inte är i stängt tillstånd närupdate
anropas. - En kodkontroll kan utföra en statisk kodanalys för att hitta fall där
this
kan stängas närupdate
anropas.
Lägg till flera kommentarvärden
En annotationsparameter kan acceptera flera värden om den definieras som en matris. Till exempel definieras standardanteckningen @SuppressWarnings
:
public @interface SuppressWarnings {
String[] value();
}
Det value
parametern är en array av strängar. Du kan ställa in flera värden genom att använda en notation liknande Array initializers:
@SuppressWarnings({"unused"})
@SuppressWarnings({"unused", "javadoc"})
Om du bara behöver ställa in ett enda värde kan parenteserna utelämnas:
@SuppressWarnings("unused")