反射
1. 反射机制概述
Java 的反射机制允许程序在运行时获取类的信息,并对其进行操作,而无需在编译时知道具体的类结构。简单来说,反射使得程序可以动态加载类、检查类的成员(字段、方法、构造函数等)、调用方法和创建对象。正因为这种动态性,反射在很多框架(如 Spring、Hibernate)、依赖注入、动态代理等领域中被广泛使用。
2. 基本使用
2.1 获取 Class 对象
反射的入口是 java.lang.Class 对象,获取方式主要有三种:
使用
.class语法:java
复制编辑
Class<String> clazz1 = String.class;通过对象的
getClass()方法:java
复制编辑
String str = "Hello"; Class<?> clazz2 = str.getClass();使用
Class.forName()方法:
适合动态加载类,传入类的全限定名。java
复制编辑
Class<?> clazz3 = Class.forName("java.lang.String");
2.2 访问字段(Field)
可以通过 getField()(仅限 public 字段)或 getDeclaredField()(包括 private 字段)来获取字段对象,并通过 Field.set() 或 Field.get() 进行读写操作。
示例:访问 public 字段
java
复制编辑
class Person { public String name; } Person p = new Person(); Field nameField = p.getClass().getField("name"); nameField.set(p, "Alice"); System.out.println(p.name); // 输出:Alice示例:访问 private 字段
java
复制编辑
class Person { private int age; } Person p = new Person(); Field ageField = p.getClass().getDeclaredField("age"); ageField.setAccessible(true); // 关闭访问控制检查 ageField.set(p, 30); System.out.println(ageField.get(p)); // 输出:30
2.3 调用方法(Method)
同样地,利用 getMethod() 和 getDeclaredMethod() 分别获取 public 和所有方法(包括 private),通过 Method.invoke() 来调用方法。
示例:调用 public 方法
java
复制编辑
class Dog { public void bark() { System.out.println("Woof!"); } } Dog dog = new Dog(); Method barkMethod = dog.getClass().getMethod("bark"); barkMethod.invoke(dog); // 输出:Woof!示例:调用 private 方法
java
复制编辑
class Dog { private void secret() { System.out.println("Secret method invoked!"); } } Dog dog = new Dog(); Method secretMethod = dog.getClass().getDeclaredMethod("secret"); secretMethod.setAccessible(true); secretMethod.invoke(dog); // 输出:Secret method invoked!
2.4 构造函数和对象创建(Constructor)
通过反射也可以调用构造函数来创建对象,即使构造函数是 private 的
- isVarArgs : 是否泛型
- getParamaterTypes: 以Class数组形式按照声明顺序获得构造方法的各个参数类型
- getExceptionTypes: 以Class数组形式获得该构造方法可能抛出的异常
- newInstance() : 通过对应参数的构造方法调用 public 或者 protected 构造函数
- setAccessible() : 设置权限(true, false)
- getModifiers() : 获得可以解析出该构造方法所修饰的整数
Modifiers类:
- isPublic
- isProtected
- isPrivate
- isStatic
- isFinal
- toString
- 示例:通过构造函数创建对象
1 | import java.io.*; |
3. 实现原理
Java 反射机制依赖于 JVM 在加载类时将类的元数据信息(如类名、字段、方法、构造函数、注解等)存储在内存中的 Class 对象中。反射 API 就是通过读取这些元数据实现动态操作的:
类元数据:
每个被加载的类都有一个对应的Class对象,它包含了所有类的结构信息。这个信息主要来源于编译后的.class文件。安全检查:
通过反射操作时(例如访问 private 字段),JVM 会进行安全检查。如果存在安全管理器(SecurityManager),在调用setAccessible(true)时会验证权限,防止随意突破封装。动态调用:
当你调用Method.invoke()时,JVM 内部会动态查找对应的方法并执行。由于这种动态查找机制,相比于直接调用方法,反射通常要慢一些。优化手段:
为了减少性能损失,常见的做法是缓存反射得到的Method、Field或Constructor对象。此外,Java 7 引入了MethodHandle,通过这种方式可以获得比传统反射更高效的调用。
Java 注解(Annotation)详解
1. 什么是 Annotation(注解)?
注解(Annotation) 是 Java 语言提供的一种元数据(Metadata)机制,可以用来标注代码,为编译器、运行时环境或其他工具提供额外的信息。
注解本身不会影响代码的逻辑运行,它们通常用于:
- 编译时检查(如
@Override) - 代码生成(如 Lombok 的
@Getter、@Setter) - 运行时处理(如 Spring 框架的
@Autowired)
2. 注解的常见作用
- 提供信息给编译器
- 帮助编译器检查错误,例如
@Override
- 帮助编译器检查错误,例如
- 在运行时进行反射操作
- 通过反射获取注解信息,实现配置驱动开发,如 Spring 的
@Component
- 通过反射获取注解信息,实现配置驱动开发,如 Spring 的
- 生成代码
- 例如 Lombok 自动生成
getter/setter方法
- 例如 Lombok 自动生成
- 替代 XML 配置
- 例如 Spring Boot 用
@Configuration代替 XML 进行 Bean 配置
- 例如 Spring Boot 用
3. 常见的 Java 内置注解(标准注解)
(1)编译时注解
| 注解名 | 作用 |
|---|---|
@Override |
标记一个方法重写了父类方法,避免拼写错误 |
@Deprecated |
标记方法已过时,调用时会有警告 |
@SuppressWarnings |
关闭编译器警告,如 @SuppressWarnings("unchecked") |
@SafeVarargs |
让编译器忽略泛型参数的类型安全警告 |
(2)元注解(用于定义自定义注解)
| 注解名 | 作用 |
|---|---|
@Retention |
指定注解的生命周期(SOURCE / CLASS / RUNTIME) |
@Target |
指定注解可用于哪些元素(类、方法、字段等) |
@Documented |
使注解出现在 JavaDoc 文档中 |
@Inherited |
让注解可被子类继承 |
4. 自定义注解
(1)定义一个简单的注解
java
复制编辑
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) // 运行时可用 @Target(ElementType.METHOD) // 只能用于方法 public @interface MyAnnotation { String value() default "默认值"; // 注解的参数 }
(2)在方法上使用该注解
java
复制编辑
class Test { @MyAnnotation(value = "Hello") public void sayHello() { System.out.println("Hello, Annotation!"); } }
(3)通过反射读取注解
java
复制编辑
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Method method = Test.class.getMethod("sayHello"); if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); System.out.println("注解值:" + annotation.value()); } } }
输出:
复制编辑
注解值:Hello
5. 注解的生命周期(RetentionPolicy)
注解的生命周期由 @Retention 指定,有三种:
| 值 | 作用 |
|---|---|
SOURCE |
仅存在于源码中,编译后会被丢弃(如 @Override) |
CLASS |
存在于class 文件中,但不会进入 JVM 运行时 |
RUNTIME |
存在于运行时,可以通过反射获取(如 Spring 的 @Autowired) |
6. @Target 注解的作用
@Target 指定注解可以作用的代码元素:
| 值 | 作用 |
|---|---|
ElementType.TYPE |
只能作用于类 |
ElementType.FIELD |
只能作用于字段 |
ElementType.METHOD |
只能作用于方法 |
ElementType.PARAMETER |
只能作用于参数 |
ElementType.CONSTRUCTOR |
只能作用于构造方法 |
ElementType.LOCAL_VARIABLE |
只能作用于局部变量 |
ElementType.ANNOTATION_TYPE |
只能作用于注解 |
ElementType.PACKAGE |
只能作用于包 |
7. Java 框架中常见的注解
(1)Spring 相关
| 注解 | 作用 |
|---|---|
@Component |
标记为 Spring 组件(Bean) |
@Service |
标记为业务层组件 |
@Repository |
标记为数据访问层组件 |
@Autowired |
自动注入依赖 |
@Transactional |
事务管理 |
@RestController |
组合了 @Controller + @ResponseBody |
(2)Junit 相关
| 注解 | 作用 |
|---|---|
@Test |
标记测试方法 |
@BeforeEach |
在每个测试方法前执行 |
@AfterEach |
在每个测试方法后执行 |
(3)Lombok
| 注解 | 作用 |
|---|---|
@Getter |
自动生成 getter 方法 |
@Setter |
自动生成 setter 方法 |
@Data |
组合 @Getter、@Setter、@ToString |
@Builder |
生成建造者模式代码 |
8. 什么时候使用注解?
- 简化代码:例如 Lombok 自动生成
getter/setter - 配置驱动:Spring 依赖注入(
@Autowired) - 运行时处理:Spring AOP(
@Transactional) - 编译时检查:
@Override避免拼写错误 - 替代 XML:Spring
@Configuration代替 XML 配置
9. 总结
- 注解是 Java 的元数据机制,可以标记类、方法、字段等
- 注解不会影响程序逻辑,但可以提供额外信息给编译器、工具或框架
- 常见内置注解:
@Override、@Deprecated、@SuppressWarnings - 注解有生命周期(SOURCE、CLASS、RUNTIME),影响其能否被反射获取
- 可以自定义注解,并通过反射获取注解信息
- 注解在 Java 框架(如 Spring、Junit、Lombok)中广泛使用
