注解与反射
注解简单介绍
注解的基本结构
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
注解的基本方式如下 @An(value1 = “”, value2 = “” …);
需要注意的是,如果注解内不需要填写属性值 @An() 和 @An 等价
与注释不同,注释是会被编译器绝对忽视的,而注解根据作用范围的不同可以在程序的不同阶段起作用。
自定义注解
元注解
元注解就是解释注解的注解,它注解的对象是前面介绍的注解
四种元注解:
Retention:指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
Target:指定注解可以在哪些地方使用
Documented :指定该注解是否会在 javadoc 体现
Inherited:子类会继承父类注解
Retention
只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间,@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值(值有三种)。
RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释。
RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中 . 当运行 Java 程序时 , JVM 不会保留注解。 这是默认值。
RetentionPolicy.RUNTIME: 编译器将把注解记录在 class 文件中 . 当运行 Java 程序时 , JVM 会保留注解 . 程序可以 通过反射获取该注解。
Target
用于修饰 Annotation 定义,指定被修饰的 Annotation 能用于修饰哪些程序元素。@Target 也包含一个名为 value 的成员变量。取值如下:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
如何自定义注解并使用
/**
用于指定被修饰的 Annotation 能用于修饰哪些程序元素
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
//指定生命周期
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
/**
1.使用default关键字初始化参数配置值
2.如果只有一个参数配置,建议参数配置名称使用value,这样在使用时可以不使用变量名
*/
String value() default "test";
}
复用注解
在默认情况下,一个类/变量/…上只能使用一个同名注解,若需要在同一个对象上使用复数个注解,可以通过以下方式来实现
1.JAVA8之后的版本可以通过新建一个注解来实现
/**
此处范围需要大于需要可以复数使用的注解范围
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotations {
/**
1.使用default关键字初始化参数配置值
2.如果只有一个参数配置,建议参数配置名称使用value,这样在使用时可以不使用变量名
*/
MyAnnotation[] value;
}
/**
用于指定被修饰的 Annotation 能用于修饰哪些程序元素
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
//指定生命周期
@Retention(RetentionPolicy.SOURCE)
@Repeatable(RobotCommands.class)
public @interface MyAnnotation {
/**
1.使用default关键字初始化参数配置值
2.如果只有一个参数配置,建议参数配置名称使用value,这样在使用时可以不使用变量名
*/
String value() default "test";
}
这样就可以在同一个对象上多次使用MyAnnotation了。
需要注意的是,当对象上有多个MyAnnotation时,使用反射读取到的注解是MyAnnotations([MyAnnotation,MyAnnotation,……]);若只有一个MyAnnotation,则反射读取到的是MyAnnotation。如下所示:
//定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotations {
MyAnnotation[] value();
}
package ASP;
//使用注解
public class testASP {
@MyAnnotation("testA - 1")
public void TestA() {
}
@MyAnnotation("testB - 1")
@MyAnnotation("testB - 2")
public void TestB() {
}
}
//通过反射获取注解信息
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("ASP.testASP");
Map<String, List<String>> map1 = new HashMap<>();
Map<String, String> map2 = new HashMap<>();
//用getDeclaredMethods可以只取到声明的函数,引用的函数不被包含(比如说String啥的
Method[] methodList = clazz.getDeclaredMethods();
for (Method method : methodList) {
//Command command = method.getAnnotation(Command.class);
java.lang.annotation.Annotation[] annotates = method.getAnnotations();
for (java.lang.annotation.Annotation annotation : annotates) {
if (annotation instanceof MyAnnotations) {
java.lang.annotation.Annotation[] annotations = ((MyAnnotations) annotation).value();
for (Annotation annotations1 : annotations) {
MyAnnotation annotation1 = (MyAnnotation) annotations1;
System.out.println("MyAnnotations - " + annotation1.value());
}
}
if (annotation instanceof MyAnnotation) {
MyAnnotation annotation2 = method.getAnnotation(MyAnnotation.class);
System.out.println("MyAnnotation - " + annotation2.value());
}
}
}
}
MyAnnotations - testB - 1
MyAnnotations - testB - 2
MyAnnotation - testA - 1
最终结果如上所示,在使用反射时需要注意分别处理。
JAVA8之前我们也可以通过容器的写法达到类似的效果,如下:
@TestRepeatables({
@TestRepeatable(name = "test1"),
@TestRepeatable(name = "test2")}
)
public class TestAnnotation1 {
}
反射简单介绍
反射是什么
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.Stirng")
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。
反射能做到什么
反射提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
优点:可以实现动态创建和编译,体现出很大的灵活性
缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这样总是慢于直接执行相同的操作
反射和注解相结合
注解只是程序的附加信息,但是加上反射,我们就可以获取这部分信息,并动态地运行特殊的逻辑。例如我们可以通过反射和注解的方式将某些信息,比如方法的名字和具体描述绑定在方法上,并对这些信息进行收集和统一处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZC2fn7P-1678933383325)(C:\Users\linqiang11\AppData\Roaming\Typora\typora-user-images\image-20230316093855034.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIeuJCeZ-1678933383329)(C:\Users\linqiang11\AppData\Roaming\Typora\typora-user-images\image-20230316093826676.png)]
如上所示,如果我们需要记录并统计某些方法的数量及某些信息,就可以在方法上加入注解描述,再通过反射获取。这样一来,后续方法的拓展性就得到增强,只需要在方法上规范加入注解就可以自动拓展,而不需要额外在另外的地方加入信息,保证了可拓展性。
在具体的项目上,我们也可以结合注解,让方法根据需求运行于不同的环境中,可以自由控制方法的作用范围。
一处理。
如上所示,如果我们需要记录并统计某些方法的数量及某些信息,就可以在方法上加入注解描述,再通过反射获取。这样一来,后续方法的拓展性就得到增强,只需要在方法上规范加入注解就可以自动拓展,而不需要额外在另外的地方加入信息,保证了可拓展性。
在具体的项目上,我们也可以结合注解,让方法根据需求运行于不同的环境中,可以自由控制方法的作用范围。