目录
注解:
1、什么是注解
注解本身是一个比较好理解也比较容易学习的东西,但是和一些其他的Java机制结合起来会变得复杂且功能性强大。在普通的日常编程中其实自己使用注解进行开发的情况并不多,注解长什么样呢?
@Override
这个东西想必大家并不陌生吧,这就是一个注解,只不过已经是由Oracle公司开发好的。那么这个注解有什么用呢,单独来说Override这个注解是用来检查父类是否存在这个原方法,不存在则编译错误。
我们进入到Override的源码来看一下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
我们发现是这么一个玩意儿, 这啥啊,这明明啥也没写啊。他是咋能实现这么伟大的功能的。这个注解的详细原理我们待会儿再说。我们先知道其实注解原本就长这样。
我们定义注解的代码就是这么简单:
public @interface Pillow {
}
我甚至直接这样定义也没有问题,这样的注解我就已经可以使用了。这就是注解的定义方式。
2、元注解
我们发现Override注解和我们自定义的注解上有一些不同,怎么Override注解头上还有注解。其实不止是这样,我们进入@Target注解看一下:

Target注解定义头上居然还有一个Target,什么情况,这是在玩套娃吗。
其实这些注解上的注解就是元注解,元注解是用于定义在自定义类型注解上的注解,其中比较重要的两个元注解就是@Retention 和 @Target
先说@Target, 这个元注解的意思是想要将你自定义的注解应用到哪些地方:类上、方法上、参数上、注解上等等,其中应用于注解上的ElementType.ANNOTATION_TYPE就让自定义注解变为了元注解,这种属性可以添加多个,用{}括起来即可。
再说@Retention,这个元注解的意思是你想要将自定义注解保留在哪个阶段,一共有三种:源码级别(SOURCE)、编译级别(CLASS)、运行时级别(RUNTIME),级别逐级递增,大的级别包含小级别时期,比如运行时级别在源码和编译阶段都存在。来做一个练习:
分析@Override
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
答:作用在方法上且保留源码级别
3、如何使用注解
使用注解的方式非常简单,只需在想添加注解的地方加注解就可以了,注意所添加的注解要符合元注解定义。
我们在定义注解的时候,可以给注解添加属性,这些属性必须是基本数据类型、String、Class、枚举类型、注解类型或这些类型的数组,并且可以添加默认值。注意添加属性的时候有()别漏掉。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pillow {
String value() default "xxx"; // 默认值为xxx
int id() default 0; // 注意不能用Integer
}
这里定义属性名也有讲究,如果定义的属性名有一个叫value() 那么注意了:
一般使用注解是这样的 @Pillow(value = "run", id = 666)
而如果只有一个value值需要指定的话,就可以这么简写 @Pillow("run"),也就是这么写默认是给value指定值,但如果有两个及以上的值必须要指定的时候就不能简写。
4、注解级别应用场景
刚才我们讲了Rentention来指明注解级别,下面讲解不同级别的应用场景,最主要部分是运行时与反射联合操作的情况

源码级别:源码级别因为其核心是源码,大多应用场景是语法检查与编码,比如@Override就提供了父类检查机制,其原理是编译器内置了对这个注解的处理,就像是定义int a = "123"会编译异常一样。在Lombok中@Data注解自动提供getter和setter等方法,它也是在源码级别,原理是编译期的源码增强技术。
字节码级别:字节码级别是在编译后操作字节码文件,所以多用于字节码增强技术。AOP面向切面编程就是典型的字节码技术通过描字节码中的注解,根据注解信息对类进行修改(如添加代理逻辑、插入日志代码等),但运行时无需保留注解信息。
运行时级别:这也是我们在定义的时候用的最多的一个阶段,其主要是通过编译后通过反射拿到对应注解信息,再对相应的目标进行操作。如果没学过反射,蒙圈是正常的,往下会细讲反射就会慢慢理解。
反射:
反射是 Java 中一种强大的机制,允许程序在运行时获取类的信息、操作类的属性和方法,而无需在编译期知道类的具体信息。简单来说,反射让程序能够 “看透” 类的内部结构,并动态操作它们。
反射都是基于class的,只要拿到一个类的class,就可以知道他里边的全部信息,甚至修改。
下面是反射的常用方法
反射获取对象实例
自定义一个Student类
package com.software.demo18;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
private void study(String other) {
System.out.println(name + "正在学习," + other + "别来沾边!!");
}
public String getName() {
return name;
}
}
四种方式获取类对象,一般最常用第三种:
// 1. 通过类名.class获取(编译期已知类)
Class<Student> cls1 = Student.class;
// 2. 通过对象.getClass()获取(已知实例)
Student student = new Student();
Class<? extends Student> cls2 = student.getClass();
// 3. 通过Class.forName("全类名")获取(运行时动态加载,最常用)
Class<?> cls3 = Class.forName("com.software.demo18.Student");
// 4. 通过类加载器获取
ClassLoader classLoader = Run.class.getClassLoader();
Class<?> cls4 = classLoader.loadClass("com.software.demo18.Student");
获取Student对象实例:
// 通过构造器获取对象实例
Constructor<?> constructor = cls1.getConstructor(String.class, int.class);
Student student2 = (Student) constructor.newInstance("张三", 18);
System.out.println(student2.name);
反射操作对象字段
我们直接实例化一个Student:
Student student = new Student("李四",21);
在上面我们已经创建好了类对象,这里我们用cls3来演示,这里要说明一下,我们只是拿到了类对象,就只是拿到了类而已。
下面演示如何把李四这个学生的姓名改成张三,这里还要提前说一点,在我们定义的Student类中,只有一个name的get方法,并且属性值都是私有的。那么如何访问私有属性呢?其实这里就用到了反射的特性,可以直接通过操作字节码将私有属性改为共有的就能访问了。方法为setAccessible(true)。
Student student = new Student("李四",21);
Class<?> cls3 = Class.forName("com.software.demo18.Student");
Field name = cls3.getDeclaredField("name");
name.setAccessible(true); // 修改访问权限
name.set(student,"张三"); // 传入实例对象和修改后的内容
System.out.println(student.getName()); // 这里的输出就变成张三了
还有一个问题需要注意,我们虽然修改了访问权限,但是在编码的时候直接访问。原因是反射是通过动态修改,他是在运行时将权限修改,而在源码时期该权限并未修改。
反射调用对象方法
我将调用Student的study方法:

我们还可以通过反射去调对象相应的方法,实现如下:
这里我们还是用李四的实例。
Student student = new Student("李四",21);
Class<?> cls3 = Class.forName("com.software.demo18.Student");
// 第一个参数为方法名,第二个参数以后就是要传入的形参
Method method = cls3.getDeclaredMethod("study", String.class);
method.setAccessible(true); // 防止权限不足
method.invoke(student,"张三"); // 输出"李四正在学习,张三别来沾边!!"
由此我们得出,我们可以通过反射动态的调用对象的任意方法,即使他是私有的。
通过这几个例子,我们初步的了解到了反射的作用,当然我只是演示了反射比较典型的例子,他还有更加强大的功能,详细都在最开始的树形图中。
注解+反射模式的优势与弊端
优势:
提高代码灵活性与动态性
注解定义规则,反射在运行时动态解析,无需硬编码逻辑。
简化配置与逻辑
传统开发中,配置(如 XML)与代码分离,需要手动解析配置。注解 + 反射将配置嵌入代码,反射自动处理。。
支持动态行为定制
反射可以在运行时根据注解信息动态调整逻辑。
弊端:
增大性能开销
通过演示我们发现,反射通过查找类的属性。即使没找到,每一次调用反射的时候也会遍历整个文件,这无疑提高了性能的开销,据统计通过反射调用方法一般比直接调用慢10~100倍。
绕过编译期检查,提高风险
演示中,我们直接修改了访问权限,调用了私有方法,这就增加了代码风险。
总结:
注解和反射是Java的高级语言特性,他们结合在一起特别在一些框架的开发中起到了举足轻重的作用,在面试中也难免作为面试内容出现。使用其便捷的同时也要注意到性能和安全方面的问题。

2175

被折叠的 条评论
为什么被折叠?



