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 里没有找到对应方法。
最近更新:
发布时间: