|
1、注解定义
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
- 辅助执行部分代码检查,便于在编译期提前发现问题。
- 配合代码处理,可以简化配置,之前依赖外部文件的配置方式可以简化为在代码中。
- 基于注解,可以在编译期自动生成一些模板式代码,比如lombok。
- 底层框架,基于注解+反射可灵活实现一些算法逻辑,而不用关注具体对象。
- 通过代码里标识的注解生成文档。
2、自定义注解
创建自定义注解使用@interface。
- 使用@interface声明,不能继承其他类或者接口。
- 注解方法不能带有参数。
- 注解方法返回值类型限定为:基本类型、String、Class、Enum、Annotation或者是这些类型的数组。
- 注解方法可以有默认值,且不能为null。
- 注解本身能够包含元注解,元注解被用来注解其它注解。
- 注解类中的方法只能用public或者默认这两个访问权修饰,不写public就是默认。
- 如果注解类中只有一个成员,最好把方法名设置为"value"。
3、元注解(注解的注解)
用@Target指定ElementType属性
public enum ElementType {
// 只能修饰类、接口(包含注解类型)或者枚举
TYPE,
// 只能修饰成员变量,包含枚举常量
FIELD,
// 只能修饰方法
METHOD,
// 只能修饰参数
PARAMETER,
// 只能修饰构造函数
CONSTRUCTOR,
// 只能修饰局部变量
LOCAL_VARIABLE,
// 只能修饰Annotation(注解)
ANNOTATION_TYPE,
// 只能修饰包定义
PACKAGE,
// 只能修饰泛型的类型(jdk1.8之后的新特性 类型注解)
TYPE_PARAMETER,
// 只能写在使用类型的任何语句中(jdk1.8之后的新特性 类型注解)
TYPE_USE
}
用@Retention指定RetentionPolicy
public enum RetentionPolicy {
// 此类型会被编译器丢弃
SOURCE,
// 此类型注解会保留在class文件中,但JVM会忽略它
CLASS,
// 此类型注解会保留在class文件中,JVM会读取它
RUNTIME
}
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被自动用于该class的子类。
*:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承 annotation,方法并不从它所重载的方法继承annotation。
@Repeatable:标注某注解可以在同一个声明上使用多次
4、内置注解
@Override
当我们想要复写父类中的方法时,我们需要使用该注解去告知编译器我们想要复写这个方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。
<hr/>@Deprecated
当我们希望编译器知道某一方法不建议使用时,我们应该使用这个注解。Java在javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。
<hr/>@SuppressWarnings
这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。它的保留策略是SOURCE(译者注:在源文件中有效)并且被编译器丢弃。
其参数有:
deprecation,使用了过时的类或方法时的警告
unchecked,执行了未检查的转换时的警告
fallthrough,当 switch 程序块直接通往下一种情况而没有 break 时的警告
path,在类路径、源文件路径等中有不存在的路径时的警告
serial,当在可序列化的类上缺少serialVersionUID 定义时的警告
finally ,任何 finally 子句不能正常完成时的警告
all,关于以上所有情况的警告
<hr/>@SafeVarargs 修饰”堆污染”警告
Java 7之前在使用可变长参数的方法时,如果参数传递的是不可具体化的类型(如泛型类型List)会产生警告信息,如果希望进制该警告,需要使用@SuppressWarnings(&#34;unchecked&#34;)注解进行声明,Java 7中,如果开发人员确信某个使用了可变长参数的方法在与泛型类一起使用时不会出现类型安全问题,就可以使用@SafeVarargs注解来声明。注意该注解只能用于可变长参数的方法或者构造方法,并且方法必须声明为static或final。
<hr/>@FunctionalInterface Java8开始提供的函数式接口
@FunctionalInterface标记在接口上,函数式接口”是指仅仅只包含一个抽象方法的接口。
1)、该注解只能标记在&#34;有且仅有一个抽象方法&#34;的接口上。
2)、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3)、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4)、该注解不是必须的,如果一个接口符合&#34;函数式接口&#34;定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
5、注解使用机制
当Java源代码被编译时,编译器的一个插件annotation处理器则会处理这些annotation。处理器可以产生报告信息,或者创建附加的Java源文件或资源。如果annotation本身被加上了RententionPolicy的运行时类,则Java编译器则会将annotation的元数据存储到class文件中。然后,Java虚拟机或其他的程序可以查找这些元数据并做相应的处理。
当然除了annotation处理器可以处理annotation外,我们也可以使用反射自己来处理annotation。Java5有一个名为AnnotatedElement的接口,Java的反射对象类Class,Constructor,Field,Method以及Package都实现了这个接口。这个接口用来表示当前运行在Java虚拟机中的被加上了annotation的程序元素。通过这个接口可以使用反射读取annotation。AnnotatedElement接口可以访问被加上RUNTIME标记的annotation,相应的方法 getAnnotation,getAnnotations,isAnnotationPresent。由于Annotation类型被编译和存储在二进制文件中就像class一样,所以可以像查询普通的Java对象一样查询这些方法返回的Annotation。
6、代码示例
自定义注解,然后根据注解动态生成模拟数据。
注解类:
/**
* 模拟生成指定范围内的Long数字,输出类型由outType指定
* * String:数值型字符串类型
* * Boxed:数值包装类型
* * Primitive:数值基本类型
*/
@Documented
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MockLong {
/**
* 最小值,默认0
* @return 最小值
*/
long min() default 0L;
/**
* 最大值,默认2^63-1
* @return 最大值
*/
long max() default Long.MAX_VALUE;
/**
* 出参类型
* @return 出参类型
*/
NumberType value() default NumberType.Boxed;
}Java Bean:
@Getter
@Setter
@ToString
@NoArgsConstructor
public class MockObject {
@MockLong(min = 1000000L, max = 9999999L)
private Long id;
@MockLong(NumberType.Primitive)
private long privLong;
@MockLong(NumberType.String)
private String strLong;
}应用示例:
public class AnnotationLearn {
public static void main(String[] args) {
MockObject mockObject = null;
try {
mockObject = mockObject(MockObject.class);
} catch (InstantiationException|IllegalAccessException ex) {
Console.error(&#34;mockObject error.&#34;, ex);
}
Console.log(&#34;模拟生成对象MockObject: {0}&#34;, mockObject);
}
private static MockObject mockObject(Class<MockObject> mockObjectClass) throws InstantiationException
, IllegalAccessException {
MockObject mockObject = mockObjectClass.newInstance();
Field[] fields = mockObjectClass.getDeclaredFields();
Arrays.stream(fields).forEach(field -> {
field.setAccessible(true);
try {
if (field.isAnnotationPresent(MockLong.class)) {
setFieldForMockLong(field, mockObject);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
return mockObject;
}
private static void setFieldForMockLong(Field field, MockObject mockObject) throws IllegalAccessException {
MockLong mockLong = field.getDeclaredAnnotation(MockLong.class);
long result = ThreadLocalRandom.current().nextLong(mockLong.min(), mockLong.max());
if (NumberType.String.equals(mockLong.value())) {
field.set(mockObject, String.valueOf(result));
return;
}
if (NumberType.Primitive.equals(mockLong.value())) {
field.set(mockObject, result);
return;
}
field.set(mockObject, Long.valueOf(result));
}
}
// 运行结果:
模拟生成对象MockObject: MockObject(id=8790546, privLong=3295705427040741742, strLong=1792198973564350701)7、思维导图
 |
|