Java - 注解
目录
Annotation(注解)
注解在 Java 1.5 添加,在学习面向对象时使用 IDEA 生成覆盖方法会在方法上一行添加 @Override
,另一个地方就是在使用注释 /** @return **/
IDEA 也会生成对应 @ 开头注解。
注解是一种 tag,可以用在包、类、构造方法、方法、成员变量、形参、局部变量、注解上,这个 tag 里可传递 metadata。如果说注释是对代码做解释,那 metadata 就是对代码做补充,后续可以通过反射获取到 metadata 自己实现注解逻辑。比如后续学习 JavaWeb Spring 框架时,会发现大量使用注解做配置,以往配置需要写 XML,现在只用通过一行注解代替,大大提高效率。
- @Override,说明这个方法时重写的,如果没有重写编译会报错;
- @Deprecated,废弃方法,在编译时会产生警告;
- @SuppressWarnings,让编译器不产生警告,最好不要这样做。
@Override
创建 OverrideTest.java。
package learnAnnotation; public class OverrideTest { @Override public String toString() { return super.toString(); } }
很明显用 @Override 说明 toString 方法是重写 Object 类 toString 方法,不加也是重写,那好处在哪?
此时添加个 returnStatus() 方法。
package learnAnnotation; public class OverrideTest { @Override public String toString() { return super.toString(); } @Override public boolean returnStatus() { return false; } }
@Override 会报 Checked Exception,说 returnStatus() 方法没有重写或实现父类方法,这就很好防止防止自己写错方法名。
另一个好处,解决了父类更改导致的隐藏问题。如父类本身有 returnStatus 方法,某一天更改了参数或方法名而子类没跟进修改,这很有可能就导致编译器认为是子类自己新增的方法,而不是重写。
用了 @Override 就能强制检查当前方法是重写的,不是就抛异常,在以后重写都要加上让编译器兜底。
@Deprecated
使用 new Integer("");
IDEA 会出现在方法名上画上删除线,编译时也会提示使用过时 API,这说明此方法不推荐使用或者有更好的替代方案。
实际对某个对象进行使用时,首先在注释里说明 @deprecated 为什么弃用,还传递了参数 since=9 指定在 Java 9 版本就不推荐使用。其实还有个参数 forRemoval 是指到了这个版本是否会删除,默认不填是 false。
/** * ...... * * @deprecated * It is rarely appropriate to use this constructor. * Use {@link #parseInt(String)} to convert a string to a * {@code int} primitive, or use {@link #valueOf(String)} * to convert a string to an {@code Integer} object. */ @Deprecated(since="9") public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10); }
用法也很简单,需要想让哪个内容废弃就再哪里添加即可。
package learnAnnotation; @Deprecated public class OverrideTest { @Deprecated public boolean tmpStat = true; @Deprecated(since = "11", forRemoval = true) public boolean returnStatus() { return false; } } class invokeTest { public static void main(String[] args) { System.out.println(new OverrideTest().tmpStat); System.out.println(new OverrideTest().returnStatus()); System.out.println(new OverrideTest()); } }
@SuppressWarnings
几种警告。
- unchecked,运行时异常
- deprecation,废弃警告
- removal,移除警告
- all,所有警告
使用同样简单,只需要抑制一个警告时直接传递对应值即可。
@SuppressWarnings("unchecked")
抑制多个警告需要用花括号括起来。
@SuppressWarnings({"unchecked", "deprecation"})
自定义注解
先看 @Deprecated 源码怎么写来学习咋自定义注解。
package java.lang; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated { /** * Returns the version in which the annotated element became deprecated. * The version string is in the same format and namespace as the value of * the {@code @since} javadoc tag. The default value is the empty * string. * * @return the version string * @since 9 */ String since() default ""; /** * Indicates whether the annotated element is subject to removal in a * future version. The default value is {@code false}. * * @return whether the element is subject to removal * @since 9 */ boolean forRemoval() default false; }
其中 @Deprecated 注解定义语法是。
public @interface Deprecated { ... }
头上其余以 @ 开头的也是注解,称作元注解。
@Documented @Retention @Target
Meta-Annotation(元注解)
给注解做注解称作元注解。
@Retention
@Retention()
定义注解保存状态。
值使用的是 java/lang/annotation/RetentionPolicy.java 定义注解。
- SOURCE,只停留在源码中,一旦编译完就消失。
- CLASS,不指定 @Retention() 修饰当前创建的注解,则采取 CLASS 作为默认值,编译完会写入 .class 字节码文件里,当字节码加载到内存中会被抛弃。
- RUNTIME,也写入 .class 字节码文件,但注解会被加载进内存,能被反射机制读取。
@Target
@Target()
注解定义了当前注解可以在哪里使用。一旦用错地方会报错
值使用的是 java/lang/annotation/ElementType.java 枚举。
- TYPE,指类、接口、枚举、注解。
- FIELD,类里字段,通常是指成员变量
- METHOD,方法
- PARAMETER,方法形参和异常方法形参类型。
- CONSTRUCTOR,构造方法
- LOCAL_VARIABLE,本地局部变量
- ANNOTATION_TYPE,注解
- PACKAGE,包
- MODULE,Java 9 支持模块。
- TYPE_PARAMETER,Java 8 支持类型参数。
- TYPE_USE,Java 8 支持类型。
注解声明(annotation type declaration)
注解定义语法由 InterfaceModifier(访问修饰符),@interface 关键字,注解名称(Identifier 标识符),注解体(AnnotationTypeBody)构成。
看起来和接口类似,只是 interface 前面加个 @ 做区分。
{InterfaceModifier} @interface TypeIdentifier AnnotationTypeBody
实际长成这样。
public @interface 注解名称 { ... }
仅仅定义了注解,如果不使用 @Target 元注解定义使用位置,则所有地方都能用。
元素声明
@Deprecated 在使用时你会发现可以传递参数,这和定义方法类似,在注解里不叫形参叫 Element。
AnnotationTypeBody 里声明 Element,有个括号看起来像定义方法。
{public} RetunType Identifier() [DefaultValue];
声明 Element 的返回类型只能使用以下类型。
- primitive type
- String
- Class or an invocation of Class
- enum type
- annotation type
- array type
创建注解 AnnotationTest.java
package learnAnnotation; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** * 注解声明测试 */ @Target(ElementType.CONSTRUCTOR) @interface AnnotationTest { char character(); int[] ids(); String desc() default ""; }
创建了一个 ids 元素类型是数组,desc 元素类型是字符串,默认值是空字符串。@Target 指定只能在构造方法上用。
元素传递
创建注解示例程序 AnnotationInvoke.java。
package learnAnnotation; public class AnnotationInvoke { public AnnotationInvoke(String tmpTest) { System.out.println(tmpTest); } public static void main(String[] args) { new AnnotationInvoke("1"); } }
下面元素传递都是放在构造方法上,不再说明。
多个元素传递
定义了几个 Element 调用注解时也要传几个 ElementValuePair。ElementValuePair 语法是 Identifier = ElementValue。有一种情况是例外,当属性指定了默认值就不用填。
@AnnotationTest(character = 'a', ids = 1, desc = "介绍")
由于 ids 是数组,只传递 1 个值可以直接用等号,多个值需要用花括号 {value1, value2, value3}
。
元素默认值传递
默认值可以省略不填就自动取定义的默认值,也可以填了覆盖。
// desc 取默认值 @AnnotationTest(char = 'a', ids = 1) // desc 覆盖默认值 @AnnotationTest(char = 'a', ids = 1, desc = "覆盖默认值啦"})
这里 desc 就是空字符串,覆盖后为 "覆盖默认值啦"。
单个元素传递
当注解中只有一个属性并且名为 value。
@interface AnnotationTest { String value(); }
或者第一个属性为 value,后面是默认值的情况可以省略。
@interface AnnotationTest2{ String value(); int[] ids() default {}; String desc() default ""; }
传参可以不指定元素名。
@AnnotationTest("String") @AnnotationTest2("String")
也可以指定元素名。
@AnnotationTest(value = "String") @AnnotationTest2(value = "String", ids = 1, desc = "介绍下")
数组元素传递
ids 是数组,向数组传递多个值,要用花括号包裹起来。
@AnnotationTest(char = 'a', ids = {1, 2}, desc = "介绍")
单个则不用。
@AnnotationTest(char = 'a', ids = 1, desc = "介绍")
反射获取注解
AnnotationTest.java 声明的注解。
package learnAnnotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 注解声明测试 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.CONSTRUCTOR) @interface AnnotationTest { char character(); int[] ids(); String desc() default ""; }
AnnotationInvoke.java 使用注解的类。
package learnAnnotation; public class AnnotationInvoke { @AnnotationTest(character = 'a', ids = 1, desc = "this is char desc") public AnnotationInvoke(String tmpTest) { System.out.println(tmpTest); } }
获取注解步骤。
- 注解 @Retention 一定是 RUNTIME。
- 确定注解的位置,获取对象。如果在类上先获取类,方法就获取方法对象。
- 确认对象是否有注解。
- 获取注解对象。
- 通过注解对象,获取注解元素值。
实际获取获取注解 AnnotatedElement 接口定义了几个方法,Class,Constructor,Field, Method,Module,Package,Parameter 这几个反射类都有实现。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
,判断当前这个 annotationClass 注解是否存在,annotationClass 是注解 Class 对象。<T extends Annotation> T getAnnotation(Class<T> annotationClass)
,传入注解 Class 对象获取注解对象。跟调方法类似annotationObj.元素名();
获取注解元素值。<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
,传入注解 Class 对象获取注解对象(忽略继承来的注解)。Annotation[] getAnnotations()
,获取所有注解。Annotation[] getDeclaredAnnotations()
,获取所有注解,忽略继承过来的注解(注解可以继承有疑问再查资料)。
reflectionAnnotation.java 反射获取注解的类。
package learnAnnotation; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.Arrays; public class reflectionAnnotation { public static void main(String[] args) { // 1. 获取类 Class<AnnotationInvoke> classObj = AnnotationInvoke.class; // 2. 获取构造方法 try { Constructor<AnnotationInvoke> constructorObj = classObj.getDeclaredConstructor("tmpTest".getClass()); // 3. 将注解 Class 对象传入 isAnnotationPresent(),确认构造方法对象上是否有注解。 if (constructorObj.isAnnotationPresent(AnnotationTest.class)) { // 4. 将注解 Class 对象传入 getAnnotation(),获取单个注解。 AnnotationTest annotationObj1 = constructorObj.getAnnotation(AnnotationTest.class); System.out.println("getAnnotation() 方法获取的注解:"); System.out.println(annotationObj1.character()); System.out.println(Arrays.toString(annotationObj1.ids())); System.out.println(annotationObj1.desc()); System.out.println(); // 4.1 将注解 Class 对象传入 getDeclaredAnnotation(),获取单个注解。 AnnotationTest annotationObj2 = constructorObj.getDeclaredAnnotation(AnnotationTest.class); System.out.println("getDeclaredAnnotation() 方法获取的注解:"); System.out.println(annotationObj2.character()); System.out.println(Arrays.toString(annotationObj2.ids())); System.out.println(annotationObj2.desc()); System.out.println(); // 4.2 getDeclaredAnnotation() 获取所有注解 Annotation[] allAnnotation = constructorObj.getAnnotations(); System.out.println("getDeclaredAnnotation() 方法获取的注解:"); System.out.println(Arrays.toString(allAnnotation)); System.out.println(); // 4.3 getDeclaredAnnotations() 获取所有注解 Annotation[] annotationObjs = constructorObj.getDeclaredAnnotations(); System.out.println("getDeclaredAnnotations() 方法获取的注解:"); System.out.println(Arrays.toString(annotationObjs)); } } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } }
成功获取到 AnnotationInvoke.java 定义的注解 @AnnotationTest(character = 'a', ids = 1, desc = "this is char desc")
。
getAnnotation() 方法获取的注解: a [1] this is char desc getDeclaredAnnotation() 方法获取的注解: a [1] this is char desc getDeclaredAnnotation() 方法获取的注解: [@learnAnnotation.AnnotationTest(desc="this is char desc", character='a', ids={1})] getDeclaredAnnotations() 方法获取的注解: [@learnAnnotation.AnnotationTest(desc="this is char desc", character='a', ids={1})]
获取到注解就能根据是否存在注解或 ElementValue 处理应用。
问题
- @Override 注解作用范围是
@Retention(RetentionPolicy.SOURCE)
不能通过反射获取,怎么还能做检查呢? - getDeclaredAnnotation()/getDeclaredAnnotations() 返回的 Annotation 对象,怎么获取注解元素值呢?API 里没有找到对应方法。
最近更新:
发布时间: