赞
踩
Java注解从Java1.5开始引入,注解就是代码中的特殊标记,告诉类要如何运作。注解的典型应用是:通过反射技术获得类中的注解,来决定如何运行类。
注解可以标记在类、属性、方法、变量等,并且一个地方可以同时标记多个注解。
先从一个简单的注解开始说起。
class SuperClass {
public void test() {}
}
class SubClass extends SuperClass{
@Override
public void test() {}
}
@Override
这个注解表示SubClass中的test()方法是覆写父类SuperClass中的test方法,阿里开发手册要求,凡是覆写的方法,强制加上@Override
注解。可以防止书写错误,类似getObject()与 get0bject()的问题,一个是字母的 O,一个是数字的 0,加@Override 可以准确判断是否覆盖成功。以及接口或是父类的方法签名修改后,能够及时报错。
Java自带的注解中,常用的还有两个:@Deprecated、@SuppressWarnings。
注解 | 作用 |
---|---|
@Deprecated | 写在方法上,表示此方法是过时方法,不建议使用 |
@SuppressWarnings | 表示抑制指定警告 |
当一个方法被@Deprecated注解修饰,调用此方法时编译器会有相应提示。
class DeprecatedDemo {
// 表示此方法为过时方法,不建议使用
@Deprecated
public void deprecate() {}
}
public class AnnotationDemo {
public static void main(String[] args) {
DeprecatedDemo deprecatedDemo = new DeprecatedDemo();
// 此处提示deprecate()为过时方法,并显示删除线
deprecatedDemo.deprecate();
}
}
@SuppressWarnings会抑制编译器警告
public static void main(String[] args) {
// 此处编译器警告:Variable 'str' is never used
String str = "";
}
public static void main(String[] args) {
// 加上@SuppressWarnings,警告消失
@SuppressWarnings("unused")
String str = "";
}
@SuppressWarnings可以同时抑制多类型警告,@SuppressWarnings({"unused", "unchecked"})
。
@SuppressWarnings也可以抑制所有类型的警告,@SuppressWarnings("all")
。
进入@Override注解源代码,可以看到如下代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
有另外两个注解@Target与@Retention标记了@Override,它俩被称为元注解。Java中提供了四个对注解类型记性注解的注解类,它们被叫做元注解。
元注解 | 说明 |
---|---|
@Target | 描述被修饰的注解的使用范围 |
@Retention | 描述被修饰的注解的生存期 |
@Documented | 在使用javadoc工具为类生成帮助文档时,被修饰的注解会被保留 |
@Inherited | 被修饰的注解可以被子类获取 |
注解可以修饰包、类、方法、参数等,@Target限定了被修饰的注解的作用范围。@Target的取值范围为枚举值ElementType。
public enum ElementType { // 类,接口(包括注解),枚举 TYPE, // 字段(成员变量),包括枚举实例 FIELD, // 方法 METHOD, // 方法参数 PARAMETER, // 构造方法 CONSTRUCTOR, // 局部变量 LOCAL_VARIABLE, // 注解 ANNOTATION_TYPE, // 包 PACKAGE, // 类型参数,jdk8新增 TYPE_PARAMETER, // 使用类型,jdk8新增 TYPE_USE }
例如,被@Target(ElementType.METHOD)修饰的注解只能用于方法上,用于其他地方会报错。
// @AnnotationMethod只能用于修饰方法
@Target(ElementType.METHOD)
@interface AnnotationMethod {}
// 报错,不可用于类型,'@AnnotationMethod' not applicable to type
@AnnotationMethod
public class AnnotationDemo {
@AnnotationMethod
public static void main(String[] args) {}
}
ElementType.TYPE_PARAMETER修饰的注解用于类型参数。
// 用于类型参数 @Target(ElementType.TYPE_PARAMETER) @interface AnnotationTypePra {} // 泛型类,声明泛型T,此处可以使用@AnnotationTypePra class Gen<@AnnotationTypePra T> { // 此处使用报错,'@AnnotationTypePra' not applicable to field // 此时T已被视为一个普通的类型,而不是类型参数 private @AnnotationTypePra T field; // 此处使用报错,'@AnnotationTypePra' not applicable to parameter // 此时T已被视为一个普通的类型,而不是类型参数 public void set(@AnnotationTypePra T var){ field = var; } // 泛型方法,<M>声明了泛型M,第一个@AnnotationTypePra可以使用 // 第二个@AnnotationTypePra报错,'@AnnotationTypePra' not applicable to parameter // 此时M已被视为一个普通的类型,而不是类型参数 public <@AnnotationTypePra M> void show(@AnnotationTypePra M var){ System.out.println(var); } }
ElementType.TYPE_PARAMETER修饰的注解可以使用的地方,ElementType.TYPE_USE修饰的注解都可以使用,并且可以用在任何使用类型的地方,void除外。
// 用于所有使用类型的地方 @Target(ElementType.TYPE_USE) @interface AnnotationTypeUse {} // 用在类上 @AnnotationTypeUse public class AnnotationDemo { // 用于字段类型 private @AnnotationTypeUse String str; // 第一个@AnnotationTypeUse报错,'void' type may not be annotated // 第二个@AnnotationTypeUse用于方法参数,可以使用 public static @AnnotationTypeUse void main(@AnnotationTypeUse String[] args) {} // 用于返回值类型 public @AnnotationTypeUse Integer getInt() { return 1; } } // 用于类型参数 class Gen<@AnnotationTypeUse T> { // 用于字段类型 private @AnnotationTypeUse T field; // 用于方法参数 public void set(@AnnotationTypeUse T var){ field = var; } // 第一个@AnnotationTypeUse用于类型参数 // 第二个@AnnotationTypeUse用于方法参数 public <@AnnotationTypeUse M> void show(@AnnotationTypeUse M var){ System.out.println(var); } }
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Target({})
@interface AnnotationEmpty {}
@interface AnnotationTest {
// 作为其他注解的成员类型
AnnotationEmpty annotationEmpty();
}
注解的生存期分为:只存在于源码中,保存到.class文件但是不被VM加载,运行期中可以获取。@Retention用来约束注解的生存期,取值范围为枚举值RetentionPolicy。
public enum RetentionPolicy {
// 该类型注解只存于源码中,将被编译器丢弃
SOURCE,
// 该类型注解会被编译器保存到.class文件,但是不会被VM加载到内存中
// 这是注解的默认生存期
CLASS,
// 该类型注解会被编译器保存到.class文件,并且会被VM加载到内存中,从而可以通过反射获取注解的相关信息
RUNTIME
}
下面用代码演示@Retention,首先定义三个不同生存期的注解。
// 该类型注解只存在于源码中
@Retention(RetentionPolicy.SOURCE)
@interface SourceAnnotation {}
// 该类型注解会被保存到.class文件,但是不会被VM加载到内存
@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation {}
// 该类型注解会被VM加载到内存中,运行期可通过反射获取
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {}
写三个被不同生存期注解修饰的方法
package Annotation;
public class AnnotationDemo {
@SourceAnnotation
public void SourceMethod() {}
@ClassAnnotation
public void ClassMethod() {}
@RuntimeAnnotation
public void RuntimeMethod() {}
}
cmd中执行javac *.java
,然后执行javap -verbose AnnotationDemo.class
查看AnnotationDemo的字节码文件,其中部分字节码如下所示:
{ public Annotation.AnnotationDemo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 public void SourceMethod(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 8: 0 public void ClassMethod(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 13: 0 RuntimeInvisibleAnnotations: 0: #11() public void RuntimeMethod(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 18: 0 RuntimeVisibleAnnotations: 0: #14() }
通过上述字节码可以看到:
在运行时查看注解信息
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
// 获取AnnotationDemo的Class对象
Class<?> clazz = Class.forName("Annotation.AnnotationDemo");
// 通过Class对象获取所有public方法,包括继承的
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// 通过Method对象获取修饰此方法的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(method.getName() + "方法上的注解为:" + annotation);
}
}
}
}
运行结果:
RuntimeMethod方法上的注解为:@Annotation.RuntimeAnnotation()
只有RuntimeMethod方法上的注解信息在运行期间被反射获取到。
@Documented注解作用:在使用javadoc为类生成帮助文档时,是否保留此注解信息。
// 此注解未被@Documented修饰,不会在帮助文档中保留此注解信息 @interface WithoutDocAnnotation {} // 此注解被@Documented修饰,会在帮助文档中保留此注解信息 @Documented @interface DocumentedAnnotation {} public class Test { @DocumentedAnnotation public void documentedTest() { } @WithoutDocAnnotation public void withoutDocumentedTest() { } }
在cmd窗口执行javac Test.java
,然后再执行javadoc -d doc Test.java
,会在新建的doc文件夹中生成帮助文档,使用浏览器打开其中的index.html
文件,可以看到以下内容。
被@Documented修饰的注解信息保留到了帮助文档中,未被@Documented修饰的注解信息则未保留下来。
被@Inherited修饰的注解会具有继承性,也就是在父类上使用的注解,可以被子类通过Class对象的getAnnotations()方法获取。
// @Inherited修饰注解,则此类型注解可以被子类获取 @Inherited // 注意添加RetentionPolicy.RUNTIME,否则默认只存在于.class文件,运行期间无法通过反射获取 @Retention(RetentionPolicy.RUNTIME) @interface InheritedAnnotation {} @Retention(RetentionPolicy.RUNTIME) @interface WithoutInheritedAnnotation {} @InheritedAnnotation class SuperClassA {} class SubClassA extends SuperClassA{} @WithoutInheritedAnnotation class SuperClassB {} class SubClassB extends SuperClassB{} public class Test { public static void main(String[] args) throws ClassNotFoundException { // 获取SubClassA的Class对象 Class<?> clazzA = Class.forName("Annotation.SubClassA"); // 通过Class对象获取修饰类的所有注解 Annotation[] annotationsA = clazzA.getAnnotations(); for (Annotation annotation : annotationsA) { System.out.println(annotation); } Class<?> clazzB = Class.forName("Annotation.SubClassB"); Annotation[] annotationsB = clazzB.getAnnotations(); for (Annotation annotation : annotationsB) { System.out.println(annotation); } } }
运行结果:
@Annotation.InheritedAnnotation()
上面在介绍元注解的时候,用到了自定义注解。
@Documented
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
String name() default "default name";
}
自定义注解@AnnotationDemo
,在帮助文档中会保留此注解信息,可以被子类通过反射获取(需要注解的生存期为Runtime),只能用来修饰类型(类,接口,包括注解,枚举)以及方法,生存期到运行期间。
在自定义注解@AnnotationDemo中,声明了一个String类型的name元素,默认值为"default name"。注意,注解中元素的声明需要使用类似于方法的方式,同时可选择使用default提供默认值。@AnnotationDemo的使用方式如下:
@AnnotationDemo(name = "annotation test")
class AnnotationTest {
}
在注解中可以使用的元素类型除了刚才的String,总共有下列几种:
使用其他数据类型,会编译报错。声明注解元素时,可以使用基本类型,但是不能使用包装类型。注解可以作为注解元素的类型。
enum WeekEnum { WEEKDAY, WEEKEND } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationDemo { String name() default "defaultName"; } @interface AnnotationElementDemo { // 基本类型boolean作为注解内元素的类型 boolean support() default false; // String作为注解内元素的类型 String name() default ""; // Class对象作为注解内元素的类型 Class<?> clazz() default Void.class; // 枚举作为注解内元素的类型 WeekEnum week() default WeekEnum.WEEKDAY; // 注解作为注解内元素的类型 AnnotationDemo annotationDemo() default @AnnotationDemo(name = "defaultName01"); // 数组作为注解内元素的类型 int[] count(); }
自定义一个简单注解
public @interface AnnotationTest {
String name() default "";
}
编译后反编译,获取到反编译的代码
public interface Annotation.AnnotationTest extends java.lang.annotation.Annotation {
public abstract java.lang.String name();
}
可以看到,注解在编译后,会自动继承java.lang.annotation.Annotation接口,但我们无法通过在代码中直接继承此接口来实现注解功能。
使用注解时,对注解内元素进行赋值,方式是key=value。
public @interface AnnotationTest {
String name() default "";
}
@AnnotationTest(name = "test01")
class TestClass {}
注解中有名为value的元素,当使用该注解时,若只需要给value进行赋值时,可以不使用key=value形式,只写value即可。当给value及其他元素赋值时,则必须需要key=value。
@Target(ElementType.METHOD)
public @interface AnnotationTest {
int value() default 0;
String name() default "";
}
class TestClass {
// 只给value赋值,可以使用快捷方式
@AnnotationTest(10)
public void method01() {}
// 给多个元素赋值,必须使用key=value
@AnnotationTest(value = 20, name = "name01")
public void method02() {}
}
为了能在运行时获取到注解相关的信息,Java在java.lang.reflect反射包下新增了AnnotatedElement接口,它主要用来表示目前在VM中运行的程序中已使用注解的元素,通过该接口提供的方法,可以利用反射技术读取注解的信息,反射中常用的Class类、Constructor类、Method类与Field类等都实现了这个接口,在运行期间都可以通过反射获取修饰他们的注解信息。
AnnotatedElement接口中常用的几个方法:
方法名称 | 返回值 | 说明 |
---|---|---|
isAnnotationPresent(Class<? extends Annotation> annotationClass) | boolean | 判断此元素是否被指定类型的注解修饰,是的话返回true,否则返回false |
getAnnotation(Class<T> annotationClass) | T(<T extends Annotation>) | 返回此元素的指定类型的注解(包括从父类继承的),若没有,则返回null |
getAnnotations() | Annotation[] | 返回修饰此元素的所有注解,包括从父类继承的 |
getDeclaredAnnotation(Class<T> annotationClass) | T(<T extends Annotation>) | 返回此元素的指定类型的注解(不包括从父类继承的),若没有,则返回null |
getDeclaredAnnotations() | Annotation[] | 返回修饰此元素的所有注解,不包括从父类继承的 |
代码演示如下:
package Annotation; import java.lang.annotation.*; import java.util.Arrays; @Inherited @Retention(RetentionPolicy.RUNTIME) @interface AnnotationA {} @Retention(RetentionPolicy.RUNTIME) @interface AnnotationB {} @AnnotationA class SuperDemo {} @AnnotationB class SubDemo extends SuperDemo{} public class Test { public static void main(String[] args) { Class<?> clazz = SubDemo.class; // 是否被指定类型的注解修饰 boolean annotationPresent = clazz.isAnnotationPresent(AnnotationB.class); System.out.println("SubDemo被AnnotationB类型注解修饰:" + annotationPresent); // 获取指定类型的注解,包括父类的 AnnotationA annotation = clazz.getAnnotation(AnnotationA.class); System.out.println(annotation); // 获取全部注解,包括父类的 Annotation[] annotations = clazz.getAnnotations(); System.out.println(Arrays.toString(annotations)); // 获取指定类型的注解,不包括父类的 AnnotationA declaredAnnotation = clazz.getDeclaredAnnotation(AnnotationA.class); System.out.println(declaredAnnotation); // 获取全部注解,不包括父类的 Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations(); System.out.println(Arrays.toString(declaredAnnotations)); } }
运行结果:
SubDemo被AnnotationB类型注解修饰:true
@Annotation.AnnotationA()
[@Annotation.AnnotationA(), @Annotation.AnnotationB()]
null
[@Annotation.AnnotationB()]
在运行期间,可以通过上述方法拿到相关的注解以及注解的相关值,从而对不同注解修饰的方法或类进行不同的处理。
@Retention(RetentionPolicy.RUNTIME) @interface AnnotationValue { String name() default "name01"; int count() default 0; } @AnnotationValue(name = "ClassTest01", count = 10) class TestValue { @AnnotationValue(name = "MethodShow", count = 20) public void show() {} } public class Test { public static void main(String[] args) throws NoSuchMethodException { // 获取Class对象 Class<TestValue> testValueClass = TestValue.class; // 判断是否被相关注解修饰 boolean annotationPresent = testValueClass.isAnnotationPresent(AnnotationValue.class); if (annotationPresent) { // 获取相关注解 AnnotationValue annotation = testValueClass.getAnnotation(AnnotationValue.class); // 输出相关注解的值 System.out.println("name:" + annotation.name() + ". count:" + annotation.count()); } // 获取Method对象 Method show = testValueClass.getMethod("show"); // 判断是否被相关注解修饰 boolean annotationPresent1 = show.isAnnotationPresent(AnnotationValue.class); if (annotationPresent1) { // 获取相关注解 AnnotationValue annotation = show.getAnnotation(AnnotationValue.class); // 输出相关注解的值 System.out.println("name:" + annotation.name() + ". count:" + annotation.count()); } } }
运行结果:
name:ClassTest01. count:10
name:MethodShow. count:20
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。