Java(1)——注解
常用注解
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限定了被修饰的注解的作用范围。@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.TYPE_PARAMETER)以外的,其他所有ElementType枚举值的作用范围。
- 若注解想要有多个作用范围,可以在@Target()中写多个值,用{}包裹并用逗号隔开
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
- 若注解被@Target({})修饰,那么该注解不可用于直接修饰任何元素,只能作为其他复杂注解的成员类型。
@Target({}) @interface AnnotationEmpty {} @interface AnnotationTest { // 作为其他注解的成员类型 AnnotationEmpty annotationEmpty(); }
@Retention
注解的生存期分为:只存在于源码中,保存到.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() }
通过上述字节码可以看到:
文件中有四个方法,分别是默认无参构造方法+3个代码中定义的方法
- class文件并没有保存SourceMethod()方法的相关注解信息
- ClassMethod()方法使用RuntimeInvisibleAnnotations描述注解信息,RuntimeMethod()方法使用RuntimeVisibleAnnotations描述注解信息。
在运行时查看注解信息
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
@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
被@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,总共有下列几种:
所有的基本类型(boolean, byte, char, short, int, long, float, double)
- String
- Class
- enum
- Annotation
- 上述类型的数组
使用其他数据类型,会编译报错。声明注解元素时,可以使用基本类型,但是不能使用包装类型。注解可以作为注解元素的类型。
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接口中常用的几个方法:
代码演示如下:
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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_46927507/article/details/115665091
您的资助是我最大的动力!
金额随意,欢迎来赏!