【Java基础】反射与注解:核心原理、自定义注解、注解解析方式(附《思维导图》+《面试高频考点清单》)

简介: Java反射与注解是框架底层核心:反射在运行时动态获取类结构(Class/Field/Method),实现IoC、ORM等;注解作为元数据标记代码,配合反射(RUNTIME)或注解处理器(SOURCE)实现声明式编程。二者结合支撑Spring、MyBatis等主流框架。

思维导图

Java基础:反射与注解 系统性知识体系总结

一、反射(Reflection)核心原理

1.1 反射的本质与定义

反射是Java语言提供的一种动态获取类信息和动态调用对象方法的机制。在运行状态中:

  • 对于任意一个类,都能够知道这个类的所有属性和方法
  • 对于任意一个对象,都能够调用它的任意一个方法和属性
  • 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制

核心思想:将类的各个组成部分(字段、方法、构造器、注解等)封装成一个个对象(FieldMethodConstructorAnnotation),通过这些对象在运行时操作类和对象。

1.2 反射的底层原理:Class类

  • Java程序在编译后会生成.class字节码文件
  • JVM加载字节码文件时,会在堆内存中生成一个对应的java.lang.Class对象
  • 这个Class对象包含了类的完整结构信息,是反射的入口
  • 每个类在JVM中有且仅有一个Class对象

1.3 获取Class对象的三种方式

方式 代码示例 适用场景
类名.class Class<?> clazz=Person.class; 编译期已知类名,最安全、性能最好
对象.getClass() Person p=new Person(); Class<?> clazz=p.getClass(); 已有对象实例时使用
Class.forName() Class<?> clazz=Class.forName("com.example.Person"); 运行期动态加载类,如JDBC驱动加载

1.4 反射常用API

1.4.1 操作构造器

// 获取所有public构造器
Constructor<?>[] constructors=clazz.getConstructors();
// 获取指定参数的public构造器
Constructor<?> constructor=clazz.getConstructor(String.class, int.class);
// 获取所有构造器(包括private)
Constructor<?>[] declaredConstructors=clazz.getDeclaredConstructors();
// 获取指定参数的构造器(包括private)
Constructor<?> declaredConstructor=clazz.getDeclaredConstructor(String.class);

// 创建对象实例
Object obj=constructor.newInstance("张三", 20);
// 访问private构造器需要设置可访问性
declaredConstructor.setAccessible(true);
Object privateObj=declaredConstructor.newInstance("李四");

1.4.2 操作字段

// 获取所有public字段
Field[] fields=clazz.getFields();
// 获取指定名称的public字段
Field field=clazz.getField("name");
// 获取所有字段(包括private)
Field[] declaredFields=clazz.getDeclaredFields();
// 获取指定名称的字段(包括private)
Field declaredField=clazz.getDeclaredField("age");

// 获取字段值
Object value=field.get(obj);
// 设置字段值
field.set(obj, "王五");
// 访问private字段需要设置可访问性
declaredField.setAccessible(true);
declaredField.set(obj, 25);

1.4.3 操作方法

// 获取所有public方法(包括继承的)
Method[] methods=clazz.getMethods();
// 获取指定名称和参数的public方法
Method method=clazz.getMethod("sayHello", String.class);
// 获取所有本类声明的方法(包括private,不包括继承的)
Method[] declaredMethods=clazz.getDeclaredMethods();
// 获取指定名称和参数的方法(包括private)
Method declaredMethod=clazz.getDeclaredMethod("privateMethod");

// 调用方法
Object result=method.invoke(obj, "你好");
// 调用private方法需要设置可访问性
declaredMethod.setAccessible(true);
declaredMethod.invoke(obj);

1.5 反射的优缺点

优点

  • 动态性:运行时动态加载类、创建对象、调用方法,提高程序的灵活性和扩展性
  • 通用性:可以编写通用代码处理不同类型的对象,如ORM框架、序列化框架
  • 突破访问限制:可以访问类的私有成员(虽然不推荐,但在某些场景下必要)

缺点

  • 性能开销:反射操作需要解析字节码,比直接调用慢10-100倍
  • 安全风险:可以访问私有成员,破坏了封装性和安全性
  • 代码可读性差:反射代码比直接调用复杂,难以理解和维护
  • 编译期无法检查:反射操作的错误只能在运行时发现,增加了调试难度

1.6 反射的典型应用场景

  • 框架开发:Spring IoC容器、MyBatis ORM框架、Hibernate
  • 序列化与反序列化:JSON解析(Jackson、Gson)
  • 动态代理:JDK动态代理
  • 单元测试:JUnit、TestNG
  • 插件开发:Eclipse、IntelliJ IDEA插件
  • 配置文件解析:读取配置文件中的类名并实例化

二、注解(Annotation)核心原理

2.1 注解的本质与定义

注解是Java 5引入的一种元数据,用于为代码(类、方法、字段、参数等)添加额外信息。这些信息可以在编译期、类加载期或运行期被读取和处理。

核心思想:注解本身不直接影响代码的执行,它只是一种标记,需要通过专门的工具(注解处理器)来解析和处理。

2.2 注解的分类

2.2.1 按来源分类

  • JDK内置注解@Override@Deprecated@SuppressWarnings@FunctionalInterface
  • 第三方注解:Spring的@Controller@Service@Autowired,MyBatis的@Mapper
  • 自定义注解:开发者根据自己的需求定义的注解

2.2.2 按生命周期分类

生命周期 说明 适用场景
SOURCE 只保留在源码中,编译时被丢弃 编译期检查,如@Override
CLASS 保留在字节码文件中,JVM加载时被丢弃 字节码级别的处理,如一些字节码增强工具
RUNTIME 保留在字节码文件中,JVM加载时读入内存,运行时可以通过反射获取 运行时动态处理,如Spring的依赖注入

2.3 元注解(Meta-Annotation)

元注解是用于修饰注解的注解,JDK提供了4个标准元注解:

2.3.1 @Target

指定注解可以应用的目标元素类型,取值来自java.lang.annotation.ElementType枚举:

  • TYPE:类、接口、枚举
  • FIELD:字段
  • METHOD:方法
  • PARAMETER:方法参数
  • CONSTRUCTOR:构造器
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYPE:注解
  • PACKAGE:包

示例:

@Target({
   ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
   
}

2.3.2 @Retention

指定注解的生命周期,取值来自java.lang.annotation.RetentionPolicy枚举:

  • RetentionPolicy.SOURCE
  • RetentionPolicy.CLASS(默认值)
  • RetentionPolicy.RUNTIME

示例:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
   
}

2.3.3 @Documented

指定被修饰的注解会被javadoc工具提取到API文档中。

2.3.4 @Inherited

指定被修饰的注解具有继承性。如果一个类使用了被@Inherited修饰的注解,那么它的子类会自动继承这个注解。

注意@Inherited只对类级别的注解有效,对方法和字段级别的注解无效。

三、自定义注解

3.1 自定义注解的语法

[元注解]
public @interface 注解名称 {
   
    // 注解元素(属性)
    类型 元素名称() [default 默认值];
}

3.2 注解元素的类型限制

注解元素的类型只能是以下几种:

  • 基本数据类型(byte、short、int、long、float、double、char、boolean)
  • String
  • Class
  • 枚举
  • 注解
  • 以上类型的数组

3.3 自定义注解示例

import java.lang.annotation.*;

@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
   
    // 字符串类型元素
    String value() default "";

    // 整数类型元素
    int age() default 18;

    // 枚举类型元素
    Gender gender() default Gender.MALE;

    // 数组类型元素
    String[] hobbies() default {
   };

    // 注解类型元素
    OtherAnnotation other() default @OtherAnnotation;
}

// 枚举类型
enum Gender {
   
    MALE, FEMALE
}

// 其他注解
@interface OtherAnnotation {
   
    String name() default "other";
}

3.4 注解的使用

// 使用注解,指定元素值
@MyAnnotation(value="张三", age=20, gender=Gender.MALE, hobbies={
   "读书", "跑步"})
public class Person {
   

    @MyAnnotation("sayHello方法")
    public void sayHello() {
   
        System.out.println("Hello");
    }
}

特殊说明

  • 如果注解只有一个名为value的元素,使用时可以省略元素名,直接写值
  • 如果元素是数组类型,且只有一个值,可以省略大括号
  • 如果元素有默认值,使用时可以不指定该元素的值

四、注解解析方式

注解解析是指读取注解中的信息并进行相应处理的过程。根据注解的生命周期,注解解析分为两种方式:运行时解析编译时解析

4.1 运行时解析(反射解析)

运行时解析是指在程序运行期间,通过反射获取注解信息并进行处理。这是最常用的注解解析方式,适用于RetentionPolicy.RUNTIME生命周期的注解。

4.1.1 运行时解析API

// 判断类上是否存在指定注解
boolean hasAnnotation=clazz.isAnnotationPresent(MyAnnotation.class);

// 获取类上的指定注解
MyAnnotation classAnnotation=clazz.getAnnotation(MyAnnotation.class);

// 获取方法上的指定注解
Method method=clazz.getMethod("sayHello");
MyAnnotation methodAnnotation=method.getAnnotation(MyAnnotation.class);

// 获取字段上的指定注解
Field field=clazz.getField("name");
MyAnnotation fieldAnnotation=field.getAnnotation(MyAnnotation.class);

// 获取注解元素的值
String value=classAnnotation.value();
int age=classAnnotation.age();

4.1.2 运行时解析示例

public class AnnotationParser {
   
    public static void parseClass(Class<?> clazz) {
   
        // 解析类上的注解
        if (clazz.isAnnotationPresent(MyAnnotation.class)) {
   
            MyAnnotation annotation=clazz.getAnnotation(MyAnnotation.class);
            System.out.println("类注解信息:");
            System.out.println("value: " + annotation.value());
            System.out.println("age: " + annotation.age());
            System.out.println("gender: " + annotation.gender());
            System.out.println("hobbies: " + String.join(", ", annotation.hobbies()));
        }

        // 解析方法上的注解
        Method[] methods=clazz.getMethods();
        for (Method method : methods) {
   
            if (method.isAnnotationPresent(MyAnnotation.class)) {
   
                MyAnnotation annotation=method.getAnnotation(MyAnnotation.class);
                System.out.println("\n方法" + method.getName() + "注解信息:");
                System.out.println("value: " + annotation.value());
            }
        }
    }

    public static void main(String[] args) {
   
        parseClass(Person.class);
    }
}

4.2 编译时解析(注解处理器)

编译时解析是指在Java代码编译期间,通过注解处理器(Annotation Processor)读取注解信息并进行处理。适用于RetentionPolicy.SOURCERetentionPolicy.CLASS生命周期的注解。

4.2.1 注解处理器的工作原理

  • 注解处理器是javax.annotation.processing包中的API
  • 它在Java编译阶段运行,在生成字节码之前处理注解
  • 可以生成新的Java源文件或修改现有代码
  • 常见的注解处理器:Lombok、MapStruct、AutoService

4.2.2 自定义注解处理器步骤

  1. 定义一个继承自AbstractProcessor的类
  2. 重写process()方法,实现注解处理逻辑
  3. 使用@SupportedAnnotationTypes指定要处理的注解
  4. 使用@SupportedSourceVersion指定支持的Java版本
  5. 注册注解处理器(通过META-INF/services文件或@AutoService注解)

4.2.3 自定义注解处理器示例

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
   

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   
        // 遍历所有被MyAnnotation注解的元素
        for (TypeElement annotation : annotations) {
   
            roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> {
   
                // 处理注解信息
                MyAnnotation myAnnotation=element.getAnnotation(MyAnnotation.class);
                System.out.println("编译时处理注解:" + element.getSimpleName() + ", value=" + myAnnotation.value());

                // 可以在这里生成新的Java源文件
                // ...
            });
        }
        return true;
    }
}

4.3 两种解析方式对比

对比项 运行时解析 编译时解析
执行时机 程序运行期间 Java编译期间
依赖技术 反射 注解处理器
适用生命周期 RUNTIME SOURCE、CLASS
性能 较差(反射开销) 较好(编译时处理,运行时无开销)
功能 可以动态处理,灵活性高 只能生成或修改代码,不能动态处理
典型应用 Spring依赖注入、MyBatis映射 Lombok、MapStruct、AutoService

五、反射与注解的结合使用

反射和注解是Java中最强大的组合之一,几乎所有的Java框架都基于这两个技术实现。它们的结合可以实现:

  • 配置与代码分离
  • 声明式编程
  • 自动化处理

5.1 典型应用:简单的依赖注入框架

// 自定义依赖注入注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
   
}

// 服务类
public class UserService {
   
    public void addUser() {
   
        System.out.println("添加用户");
    }
}

// 控制器类
public class UserController {
   
    @Autowired
    private UserService userService;

    public void addUser() {
   
        userService.addUser();
    }
}

// 简单的IoC容器
public class SimpleIocContainer {
   
    public static <T> T getInstance(Class<T> clazz) throws Exception {
   
        // 创建对象实例
        T instance=clazz.newInstance();

        // 遍历所有字段
        Field[] fields=clazz.getDeclaredFields();
        for (Field field : fields) {
   
            // 如果字段上有@Autowired注解
            if (field.isAnnotationPresent(Autowired.class)) {
   
                // 设置可访问性
                field.setAccessible(true);
                // 获取字段类型
                Class<?> fieldType=field.getType();
                // 创建字段类型的实例
                Object fieldInstance=getInstance(fieldType);
                // 注入字段值
                field.set(instance, fieldInstance);
            }
        }

        return instance;
    }

    public static void main(String[] args) throws Exception {
   
        UserController controller=getInstance(UserController.class);
        controller.addUser(); // 输出:添加用户
    }
}

六、常见面试题

  1. 什么是反射?反射的原理是什么?
    反射是Java提供的一种动态获取类信息和动态调用对象方法的机制。原理是JVM加载类时会生成一个Class对象,这个对象包含了类的完整结构信息,通过这个对象可以在运行时操作类和对象。

  2. 获取Class对象的三种方式有什么区别?

    • 类名.class:编译期已知类名,最安全、性能最好
    • 对象.getClass():已有对象实例时使用
    • Class.forName():运行期动态加载类,如JDBC驱动加载
  3. 反射有什么优缺点?
    优点:动态性、通用性、可以突破访问限制
    缺点:性能开销大、安全风险、代码可读性差、编译期无法检查

  4. 什么是注解?注解的本质是什么?
    注解是Java 5引入的一种元数据,用于为代码添加额外信息。本质是一个继承自Annotation接口的接口。

  5. 元注解有哪些?分别有什么作用?

    • @Target:指定注解可以应用的目标元素类型
    • @Retention:指定注解的生命周期
    • @Documented:指定注解会被javadoc工具提取到API文档中
    • @Inherited:指定注解具有继承性
  6. 注解的生命周期有哪几种?分别适用于什么场景?

    • SOURCE:只保留在源码中,适用于编译期检查
    • CLASS:保留在字节码文件中,适用于字节码级别的处理
    • RUNTIME:保留在内存中,适用于运行时动态处理
  7. 注解解析方式有哪几种?分别有什么区别?

    • 运行时解析:通过反射在程序运行期间处理注解,灵活性高但性能较差
    • 编译时解析:通过注解处理器在Java编译期间处理注解,性能好但只能生成或修改代码
  8. @Inherited注解的作用是什么?有什么限制?
    @Inherited指定被修饰的注解具有继承性,子类会自动继承父类上的该注解。但它只对类级别的注解有效,对方法和字段级别的注解无效。


Java反射与注解 面试版核心考点清单(可直接背诵)

一、反射核心考点(面试必问)

  1. 反射定义:运行时动态获取类信息、动态调用对象方法的机制,核心是将类的组成部分(字段、方法、构造器)封装为对象。
  2. 底层原理:JVM加载.class字节码时生成唯一的Class对象,包含类的完整结构信息,是反射的唯一入口。
  3. 获取Class对象的3种方式(必考)
    • 类名.class:编译期确定,性能最好,最安全
    • 对象.getClass():已有实例时使用
    • Class.forName("全类名"):运行期动态加载,如JDBC驱动
  4. 核心API分类(高频手写)
    • 构造器:getConstructors()/getDeclaredConstructors()
    • 字段:getFields()/getDeclaredFields()
    • 方法:getMethods()/getDeclaredMethods()
    • 访问控制:setAccessible(true) 突破private限制
  5. 反射优缺点(必考)
    • 优点:动态性、通用性、可编写通用框架代码
    • 缺点:性能差(比直接调用慢10-100倍)、破坏封装、编译期无法检查错误
  6. 典型应用场景:Spring IoC、MyBatis ORM、JSON序列化、JDK动态代理、JUnit单元测试

二、注解核心考点(面试必问)

  1. 注解定义:Java 5引入的元数据,为代码添加额外信息,本身不影响代码执行,需通过工具解析。
  2. 本质:所有注解都继承自java.lang.annotation.Annotation接口。
  3. 4个元注解(必考)
    • @Target:指定注解可应用的元素类型(TYPE、FIELD、METHOD等)
    • @Retention:指定注解生命周期(SOURCE/CLASS/RUNTIME,默认CLASS)
    • @Documented:注解会被javadoc提取到API文档
    • @Inherited:类级注解可被子类继承(方法/字段级无效)
  4. 注解生命周期(必考)
    • SOURCE:仅源码保留,编译时丢弃(如@Override
    • CLASS:字节码保留,JVM加载时丢弃(如字节码增强)
    • RUNTIME:运行时保留,可通过反射获取(如Spring注解)
  5. 注解元素类型限制:只能是基本类型、String、Class、枚举、注解及以上类型的数组。
  6. 特殊语法:只有一个名为value的元素时,使用时可省略元素名。

三、注解解析核心考点

  1. 两种解析方式对比(必考)
对比项 运行时解析 编译时解析
执行时机 程序运行时 Java编译时
依赖技术 反射 注解处理器
适用生命周期 RUNTIME SOURCE、CLASS
性能 较差 较好
典型应用 Spring依赖注入 Lombok、MapStruct
  1. 运行时解析APIisAnnotationPresent()getAnnotation()getAnnotations()
  2. 编译时解析流程:继承AbstractProcessor→重写process()方法→指定处理的注解→注册处理器

四、反射与注解结合核心考点

  1. 核心价值:实现声明式编程、配置与代码分离、自动化处理
  2. 典型实现:依赖注入、ORM映射、AOP切面、接口权限校验
  3. 关键注意点:注解必须声明为RUNTIME生命周期才能被反射解析

10道高频手写题(含标准答案)

题1:写出获取Class对象的三种方式

public class ClassGetWays {
   
    public static void main(String[] args) throws ClassNotFoundException {
   
        // 方式1:类名.class(最常用,性能最好)
        Class<?> clazz1=String.class;

        // 方式2:对象.getClass()
        String str="hello";
        Class<?> clazz2=str.getClass();

        // 方式3:Class.forName()(动态加载)
        Class<?> clazz3=Class.forName("java.lang.String");

        // 验证:同一个类的Class对象唯一
        System.out.println(clazz1==clazz2); // true
        System.out.println(clazz1==clazz3); // true
    }
}

考点:三种方式的区别与适用场景

题2:通过反射创建对象(含私有构造器)

class Person {
   
    private String name;

    // 私有构造器
    private Person(String name) {
   
        this.name=name;
    }

    @Override
    public String toString() {
   
        return "Person{name='" + name + "'}";
    }
}

public class ReflectCreateObject {
   
    public static void main(String[] args) throws Exception {
   
        Class<?> clazz=Person.class;
        // 获取私有构造器
        var constructor=clazz.getDeclaredConstructor(String.class);
        // 突破访问限制
        constructor.setAccessible(true);
        // 创建对象
        Person person=(Person) constructor.newInstance("张三");
        System.out.println(person); // Person{name='张三'}
    }
}

考点getDeclaredConstructor()setAccessible(true)的使用

题3:通过反射调用私有方法

class Calculator {
   
    private int add(int a, int b) {
   
        return a+b;
    }
}

public class ReflectInvokePrivateMethod {
   
    public static void main(String[] args) throws Exception {
   
        Calculator calculator=new Calculator();
        Class<?> clazz=calculator.getClass();

        // 获取私有方法
        Method addMethod=clazz.getDeclaredMethod("add", int.class, int.class);
        // 突破访问限制
        addMethod.setAccessible(true);
        // 调用方法
        int result=(int) addMethod.invoke(calculator, 10, 20);
        System.out.println(result); // 30
    }
}

考点getDeclaredMethod()invoke()方法的使用

题4:通过反射修改私有字段

class User {
   
    private String username="admin";
}

public class ReflectModifyField {
   
    public static void main(String[] args) throws Exception {
   
        User user=new User();
        Class<?> clazz=user.getClass();

        // 获取私有字段
        Field usernameField=clazz.getDeclaredField("username");
        // 突破访问限制
        usernameField.setAccessible(true);
        // 修改字段值
        usernameField.set(user, "root");
        // 验证
        System.out.println(usernameField.get(user)); // root
    }
}

考点getDeclaredField()set()/get()方法的使用

题5:自定义运行时注解并通过反射解析

// 自定义注解
@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface RequestMapping {
   
    String value() default "";
    String method() default "GET";
}

// 使用注解
@RequestMapping("/user")
class UserController {
   
    @RequestMapping(value="/add", method="POST")
    public void addUser() {
   
    }
}

// 解析注解
public class RuntimeAnnotationParser {
   
    public static void main(String[] args) {
   
        Class<?> clazz=UserController.class;

        // 解析类上的注解
        if (clazz.isAnnotationPresent(RequestMapping.class)) {
   
            RequestMapping classAnnotation=clazz.getAnnotation(RequestMapping.class);
            System.out.println("类路径:" + classAnnotation.value());
        }

        // 解析方法上的注解
        Method[] methods=clazz.getDeclaredMethods();
        for (Method method : methods) {
   
            if (method.isAnnotationPresent(RequestMapping.class)) {
   
                RequestMapping methodAnnotation=method.getAnnotation(RequestMapping.class);
                System.out.println("方法路径:" + methodAnnotation.value());
                System.out.println("请求方法:" + methodAnnotation.method());
            }
        }
    }
}

考点:自定义注解的语法和运行时解析流程

题6:实现简单的依赖注入(@Autowired)

// 依赖注入注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Autowired {
   
}

// 服务类
class UserService {
   
    public void addUser() {
   
        System.out.println("添加用户成功");
    }
}

// 控制器类
class UserController {
   
    @Autowired
    private UserService userService;

    public void addUser() {
   
        userService.addUser();
    }
}

// 简单IoC容器
public class SimpleIoc {
   
    public static <T> T getInstance(Class<T> clazz) throws Exception {
   
        T instance=clazz.getDeclaredConstructor().newInstance();

        // 遍历所有字段
        Field[] fields=clazz.getDeclaredFields();
        for (Field field : fields) {
   
            if (field.isAnnotationPresent(Autowired.class)) {
   
                field.setAccessible(true);
                // 递归创建依赖对象并注入
                Object dependency=getInstance(field.getType());
                field.set(instance, dependency);
            }
        }

        return instance;
    }

    public static void main(String[] args) throws Exception {
   
        UserController controller=getInstance(UserController.class);
        controller.addUser(); // 输出:添加用户成功
    }
}

考点:反射与注解结合的核心应用,Spring IoC的基本原理

题7:实现简单的ORM映射(@Table/@Column)

// 表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
   
    String value();
}

// 列注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Column {
   
    String value();
}

// 实体类
@Table("t_user")
class User {
   
    @Column("id")
    private Long id;

    @Column("username")
    private String username;

    @Column("password")
    private String password;

    // getter和setter
    public Long getId() {
    return id; }
    public void setId(Long id) {
    this.id=id; }
    public String getUsername() {
    return username; }
    public void setUsername(String username) {
    this.username=username; }
    public String getPassword() {
    return password; }
    public void setPassword(String password) {
    this.password=password; }
}

// ORM工具类
public class SimpleORM {
   
    // 生成查询所有用户的SQL
    public static String generateSelectAll(Class<?> clazz) {
   
        if (!clazz.isAnnotationPresent(Table.class)) {
   
            throw new IllegalArgumentException("类没有@Table注解");
        }

        Table table=clazz.getAnnotation(Table.class);
        String tableName=table.value();

        StringBuilder columns=new StringBuilder();
        Field[] fields=clazz.getDeclaredFields();
        for (Field field : fields) {
   
            if (field.isAnnotationPresent(Column.class)) {
   
                Column column=field.getAnnotation(Column.class);
                columns.append(column.value()).append(", ");
            }
        }

        // 移除最后一个逗号
        if (columns.length()>0) {
   
            columns.delete(columns.length()-2, columns.length());
        }

        return "SELECT " + columns + " FROM " + tableName;
    }

    public static void main(String[] args) {
   
        String sql=generateSelectAll(User.class);
        System.out.println(sql); // SELECT id, username, password FROM t_user
    }
}

考点:MyBatis/Hibernate等ORM框架的核心原理

题8:验证@Inherited注解的继承性

// 带继承性的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface InheritedAnnotation {
   
    String value();
}

// 不带继承性的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface NoInheritedAnnotation {
   
    String value();
}

// 父类
@InheritedAnnotation("父类注解")
@NoInheritedAnnotation("父类注解")
class Parent {
   
}

// 子类
class Child extends Parent {
   
}

public class InheritedTest {
   
    public static void main(String[] args) {
   
        Class<?> childClass=Child.class;

        // 检查@InheritedAnnotation是否被继承
        System.out.println(childClass.isAnnotationPresent(InheritedAnnotation.class)); // true
        InheritedAnnotation inheritedAnnotation=childClass.getAnnotation(InheritedAnnotation.class);
        System.out.println(inheritedAnnotation.value()); // 父类注解

        // 检查@NoInheritedAnnotation是否被继承
        System.out.println(childClass.isAnnotationPresent(NoInheritedAnnotation.class)); // false
    }
}

考点@Inherited注解的作用和限制(仅对类级注解有效)

题9:反射获取方法的泛型返回类型

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

class UserService {
   
    public List<String> getUsernames() {
   
        return null;
    }
}

public class GenericTypeTest {
   
    public static void main(String[] args) throws Exception {
   
        Method method=UserService.class.getMethod("getUsernames");
        // 获取泛型返回类型
        Type returnType=method.getGenericReturnType();

        if (returnType instanceof ParameterizedType) {
   
            ParameterizedType parameterizedType=(ParameterizedType) returnType;
            // 获取原始类型(List)
            Type rawType=parameterizedType.getRawType();
            System.out.println("原始类型:" + rawType); // interface java.util.List

            // 获取泛型参数类型(String)
            Type[] actualTypeArguments=parameterizedType.getActualTypeArguments();
            for (Type type : actualTypeArguments) {
   
                System.out.println("泛型参数:" + type); // class java.lang.String
            }
        }
    }
}

考点:反射处理泛型的方法,解决泛型擦除问题

题10:自定义编译时注解处理器(生成代码)

// 自定义编译时注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@interface GenerateBuilder {
   
}

// 注解处理器(需要单独放在一个模块中)
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import java.io.Writer;
import java.util.Set;

@SupportedAnnotationTypes("com.example.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BuilderAnnotationProcessor extends AbstractProcessor {
   

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   
        for (TypeElement annotation : annotations) {
   
            roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> {
   
                if (element instanceof TypeElement) {
   
                    TypeElement typeElement=(TypeElement) element;
                    generateBuilderClass(typeElement);
                }
            });
        }
        return true;
    }

    private void generateBuilderClass(TypeElement typeElement) {
   
        String className=typeElement.getSimpleName().toString();
        String builderClassName=className + "Builder";
        String packageName=processingEnv.getElementUtils().getPackageOf(typeElement).toString();

        try {
   
            JavaFileObject builderFile=processingEnv.getFiler().createSourceFile(packageName + "." + builderClassName);
            Writer writer=builderFile.openWriter();

            writer.write("package " + packageName + ";\n\n");
            writer.write("public class " + builderClassName + " {\n");
            writer.write("    private " + className + " instance=new " + className + "();\n\n");
            writer.write("    public " + builderClassName + " build() {\n");
            writer.write("        return instance;\n");
            writer.write("    }\n");
            writer.write("}\n");

            writer.close();
        } catch (Exception e) {
   
            e.printStackTrace();
        }
    }
}

考点:编译时注解处理器的基本结构和代码生成方法,Lombok的核心原理


Java反射与注解 一页纸速记版(面试前5分钟必看)

🔹 反射核心速记

  • 本质:运行时操作Class对象,将类成员封装为Field/Method/Constructor
  • 3种Class获取:类名.class(最优) > 对象.getClass() > Class.forName(动态)
  • API口诀:带Declared拿所有,不带拿public;setAccessible(true)破私有
  • 性能问题:比直接调用慢10-100倍,原因:类解析、安全检查、方法调用开销
  • 应用:Spring IoC、MyBatis、JSON序列化、动态代理

🔹 注解核心速记

  • 本质:继承自Annotation接口的特殊接口
  • 4个元注解:Target(作用范围)、Retention(生命周期)、Documented(文档)、Inherited(继承)
  • 3种生命周期:SOURCE(编译) → CLASS(字节码) → RUNTIME(运行),默认CLASS
  • 元素类型:基本类型、String、Class、枚举、注解及它们的数组
  • 特殊语法:单value元素可省略名称

🔹 注解解析速记

  • 运行时解析:反射+RUNTIME注解,灵活但慢,Spring全家桶
  • 编译时解析:注解处理器+SOURCE/CLASS注解,性能好,Lombok/MapStruct
  • 核心API:isAnnotationPresent()、getAnnotation()、getAnnotations()

🔹 结合应用速记

  • 核心价值:声明式编程、配置与代码分离
  • 典型实现:@Autowired依赖注入、@Table ORM映射、@RequestMapping路由
  • 关键前提:运行时解析必须使用RUNTIME生命周期注解

5道进阶面试题(含深度解析)

题1:反射为什么比直接调用慢?如何优化反射性能?

答案
反射慢的核心原因:

  1. 类结构解析开销:每次反射调用都需要动态解析类的字节码结构
  2. 安全检查开销:JVM会执行访问权限检查,即使setAccessible(true)也有开销
  3. 方法调用开销:反射调用是通过Method.invoke()间接调用,无法被JIT编译器优化
  4. 参数装箱拆箱:invoke()方法参数是Object数组,基本类型需要装箱拆箱
  5. 返回值类型转换:返回值是Object,需要强制类型转换

优化方法

  1. 缓存Class对象:避免重复调用Class.forName()
  2. 缓存反射对象:将Field、Method、Constructor对象缓存起来复用
  3. 使用setAccessible(true):关闭访问权限检查,提升30%-50%性能
  4. 使用反射工具类:如Apache Commons Lang的MethodUtils、FieldUtils
  5. 使用字节码生成技术:如ASM、CGLIB,生成直接调用的字节码,性能接近直接调用
  6. Java 9+优化:使用MethodHandles.Lookup,性能比传统反射提升2-3倍

考点:对反射底层原理的深入理解,以及实际开发中的性能优化经验

题2:为什么注解的元素类型只能是基本类型、String、Class、枚举、注解及它们的数组?

答案
这是由Java注解的设计和实现原理决定的:

  1. 编译期常量要求:注解的元素值必须在编译期就能确定,是编译期常量
  2. 字节码存储限制:注解信息存储在字节码的RuntimeVisibleAnnotations属性中,只能存储常量池中的常量
  3. 类型安全考虑:限制类型可以保证注解的类型安全,避免运行时类型转换错误
  4. 解析效率考虑:简单类型的解析效率更高,复杂对象无法在编译期序列化到字节码中

反例:如果允许使用自定义对象作为注解元素,那么在编译期无法确定对象的值,也无法将其序列化到字节码中,运行时解析时会出现问题。

考点:对注解底层实现原理的理解

题3:如何通过反射获取方法参数的注解?为什么JDK 8之前无法获取方法参数名?

答案

获取方法参数注解的代码:

Method method=clazz.getMethod("test", String.class, int.class);
Annotation[][] parameterAnnotations=method.getParameterAnnotations();

for (int i=0; i<parameterAnnotations.length; i++) {
   
    System.out.println("参数" + (i+1) + "的注解:");
    for (Annotation annotation : parameterAnnotations[i]) {
   
        System.out.println(annotation);
    }
}

JDK 8之前无法获取方法参数名的原因:

  1. 字节码中不存储参数名:Java编译时默认不会将方法参数名写入字节码文件,只存储参数类型和顺序
  2. 节省字节码空间:早期Java为了减小字节码文件大小,省略了参数名信息
  3. JDK 8的改进:JDK 8引入了-parameters编译参数,开启后会将方法参数名写入字节码,此时可以通过Parameter.getName()获取参数名

代码示例(JDK 8+)

Method method=clazz.getMethod("test", String.class, int.class);
Parameter[] parameters=method.getParameters();
for (Parameter parameter : parameters) {
   
    System.out.println("参数名:" + parameter.getName());
}

考点:反射处理方法参数的细节,以及JDK版本差异带来的影响

题4:Spring中@Transactional注解的实现原理(反射+AOP)

答案
@Transactional注解的实现基于反射AOP(面向切面编程),核心流程:

  1. 注解扫描:Spring容器启动时,通过反射扫描所有被@Transactional注解标记的类和方法
  2. 动态代理生成:为目标类生成动态代理对象(JDK动态代理或CGLIB动态代理)
  3. 事务拦截器:代理对象在调用目标方法时,会被TransactionInterceptor拦截
  4. 事务管理
    • 方法执行前:通过反射获取方法上的@Transactional注解属性
    • 根据注解属性创建事务(传播行为、隔离级别、超时时间等)
    • 执行目标方法
    • 方法正常返回:提交事务
    • 方法抛出异常:根据注解的rollbackFor属性判断是否回滚事务

关键细节

  • @Transactional注解必须标注在public方法上,因为Spring AOP只能拦截public方法
  • 同一个类内部调用@Transactional方法不会生效,因为不会经过代理对象
  • 注解的属性会覆盖类上的注解属性

考点:Spring核心原理,反射与AOP的结合应用

题5:泛型擦除后,反射如何获取泛型信息?

答案
Java泛型采用擦除式实现,泛型信息在编译期会被擦除,但以下情况泛型信息会被保留在字节码中:

  1. 类的泛型父类
  2. 类的泛型接口
  3. 字段的泛型类型
  4. 方法的泛型返回类型
  5. 方法的泛型参数类型

反射通过java.lang.reflect.Type接口及其子接口获取这些保留的泛型信息:

  • ParameterizedType:表示参数化类型,如List<String>
  • GenericArrayType:表示泛型数组类型,如List<String>[]
  • TypeVariable:表示类型变量,如T
  • WildcardType:表示通配符类型,如? extends Number

代码示例(获取类的泛型父类)

class Parent<T> {
   
}

class Child extends Parent<String> {
   
}

public class GenericTest {
   
    public static void main(String[] args) {
   
        Type superType=Child.class.getGenericSuperclass();
        if (superType instanceof ParameterizedType) {
   
            ParameterizedType parameterizedType=(ParameterizedType) superType;
            Type[] actualTypeArguments=parameterizedType.getActualTypeArguments();
            System.out.println("泛型参数类型:" + actualTypeArguments[0]); // class java.lang.String
        }
    }
}

考点:泛型擦除的原理,以及反射如何绕过泛型擦除获取泛型信息

相关文章
|
10天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
2964 20
|
7天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
2729 5
|
22天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23562 14
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
3天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
1675 2
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
9天前
|
人工智能 JSON BI
DeepSeek V4-Pro 接入 Claude Code 完全实战:体验、测试与关键避坑指南
Claude Code 作为当前主流的 AI 编程辅助工具,凭借强大的代码理解、工程执行与自动化能力深受开发者喜爱,但原生模型的使用成本相对较高。为了在保持能力的同时进一步降低开销,不少开发者开始寻找兼容度高、价格更友好的替代模型。DeepSeek V4 系列的发布带来了新的选择,该系列包含 V4-Pro 与 V4-Flash 两款模型,并提供了与 Anthropic 完全兼容的 API 接口,理论上只需简单修改配置,即可让 Claude Code 无缝切换为 DeepSeek 引擎。
2322 3
|
8天前
|
人工智能 安全 开发工具
Claude Code 官方工作原理与使用指南
Claude Code 不是传统代码补全工具,而是 Anthropic 推出的终端 AI 代理,具备代理循环、双驱动架构(模型+工具)、全局项目感知、6 种权限模式等核心能力,本文基于官方文档系统解析其工作原理与高效使用技巧。
1243 0
|
16天前
|
人工智能 缓存 Shell
Claude Code 全攻略:命令大全 + 实战工作流(完整版)
Claude Code 是一款运行在终端环境下的 AI 编码助手,能够直接在项目目录中理解代码结构、编辑文件、执行命令、执行开发计划,并支持持久化记忆、上下文压缩、后台任务、多模型切换等专业能力。对于日常开发、项目维护、快速重构、代码审查等场景,它可以大幅减少手动操作、提升编码效率。本文从常用命令、界面模式、核心指令、记忆机制、图片处理、进阶工作流等维度完整说明,帮助开发者快速上手并稳定使用。
3662 6

热门文章

最新文章