Java 后端的 “透视挂”:反射技术从入门到 “玩嗨”

109 阅读6分钟

Hello 各位后端搬砖人!今天咱们聊个 Java 里超有意思的 “黑科技”—— 反射。要是把 Java 类比作一个包装严实的快递盒,那反射就是能隔着盒子看清里面装了啥、甚至不用拆封就能摆弄里面东西的 “透视挂”。别觉得这玩意儿离咱远,你天天用的 Spring、MyBatis,背后都有它的身影!

一、先搞懂:反射到底是个啥?

官方定义说得玄乎:“反射是 Java 程序在运行时获取类信息、操作类成员的能力”。咱翻译成人话就是:平时咱写代码都是 “正着来”—— 知道类名,new 个对象调用方法;而反射是 “反着来”—— 哪怕不知道类叫啥,只要拿到它的 “身份证”(Class 对象),就能扒出它的构造方法、成员变量、成员方法,还能随便调用、修改。

打个比方:平时你去奶茶店,得知道 “珍珠奶茶” 这个名字才能点(正着来);反射就像你拿着奶茶店的配方表(Class 对象),不管这奶茶叫啥,都能自己凑材料做出来,甚至还能偷偷多加两勺糖(修改私有变量)。

二、反射这么秀,到底能干嘛?(应用场景大揭秘)

别以为反射是 “花架子”,后端开发没它真不行,这几个场景你绝对眼熟:

1. 框架的 “核心发动机”

咱天天用的 Spring IOC,为啥能 “自动帮你 new 对象”?其实就是 Spring 读取配置文件(比如 xml 里的),拿到类名后,用反射找到构造方法,帮你创建对象;MyBatis 为啥能把数据库结果直接转成 Java 对象?也是反射在背后匹配字段名、调用 set 方法赋值。

2. 动态代理 “好搭档”

写 AOP 的时候,JDK 动态代理就是靠反射实现的!代理类在运行时才通过反射拿到目标类的方法,然后在方法前后加日志、事务这些 “增强逻辑”,不用手动写一堆代理类,简直是懒癌福音。

3. 通用工具类 “神器”

比如你想写个工具类,能给任意对象的任意字段赋值,总不能每个类都写一遍 set 方法吧?用反射就能搞定:传入对象和字段名,直接通过反射找到字段并修改,一行代码适配所有类,效率直接拉满。

三、第一步:拿到 “身份证”—— 获取 Class 对象

要玩反射,首先得拿到类的 “身份证”——Class 对象。Java 给了三种常用方式,各有各的用法,记牢别搞混!

方式 1:对象.getClass ()——“从已有对象查身份证”

适合已经创建了对象的场景,比如你手里有个User对象,想看看它的类信息:

User user = new User();
Class<?> clazz = user.getClass(); // 拿到User类的Class对象
System.out.println(clazz.getName()); // 输出:com.example.User(全类名)

方式 2:类名.class——“直接查类的身份证”

不用创建对象,直接通过类名获取,适合知道具体类名的场景:

Class<?> clazz = User.class; // 直接拿User类的Class对象

这种方式还有个小妙用:判断对象类型时,比instanceof更精准(比如user.getClass() == User.class,不会包含子类)。

方式 3:Class.forName ("全类名")——“按名字查身份证”

最灵活的方式!不用导入类,只要知道全类名(比如从配置文件读的 “com.example.User”),就能动态加载类并获取 Class 对象,框架最爱用这个:

// 注意:全类名要写对,不然会抛ClassNotFoundException
Class<?> clazz = Class.forName("com.example.User");

小提醒:一个类在 JVM 里只有一个 Class 对象,不管用哪种方式拿,拿到的都是同一个,别担心重复创建~

四、玩起来:反射操作三大核心成员

拿到 Class 对象后,咱就能开始 “透视” 操作了 —— 构造方法、成员方法、成员变量,一个都跑不了!

1. 操作构造方法:帮你 “new 对象”

平时 new 对象靠new 构造方法(),反射靠Constructor类,哪怕是私有构造方法也能调用(但要注意安全哦)。

示例:用反射创建 User 对象

假设 User 类长这样:

public class User {
    private String name;
    private int age;
    // 无参构造(默认有,这里显式写出来)
    public User() {}
    // 有参构造(私有)
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // getter/setter省略...
}

步骤 1:获取构造方法

  • 无参构造:getConstructor()(public 修饰)
  • 有参构造:getDeclaredConstructor(参数类型.class)(支持私有)
Class<?> userClass = Class.forName("com.example.User");
// 1. 获取无参构造
Constructor<?> noArgConstructor = userClass.getConstructor();
// 2. 获取私有有参构造(String, int)
Constructor<?> argConstructor = userClass.getDeclaredConstructor(String.class, int.class);

步骤 2:创建对象

  • 调用 public 构造:直接newInstance()
  • 调用私有构造:先设setAccessible(true)(打破封装)
// 1. 用无参构造创建对象
User user1 = (User) noArgConstructor.newInstance();
// 2. 用私有有参构造创建对象(关键:打开访问权限)
argConstructor.setAccessible(true); // 告诉JVM:别拦着,我要访问私有方法!
User user2 = (User) argConstructor.newInstance("张三", 25);
System.out.println(user2.getName()); // 输出:张三(成功创建!)

2. 操作成员方法:帮你 “调用方法”

不管是 public 方法还是私有方法,反射都能帮你调用,哪怕是父类的方法也能拿到(用getMethod()会找父类,getDeclaredMethod()只找当前类)。

示例:用反射调用 User 的方法

给 User 加个方法:

public class User {
    // 公开方法
    public void sayHello() {
        System.out.println("Hello, 我是" + name);
    }
    // 私有方法(带参数)
    private void setInfo(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

步骤 1:获取成员方法

Class<?> userClass = User.class;
User user = new User();
// 1. 获取公开方法sayHello(无参)
Method sayHelloMethod = userClass.getMethod("sayHello");
// 2. 获取私有方法setInfo(参数:String, int)
Method setInfoMethod = userClass.getDeclaredMethod("setInfo", String.class, int.class);

步骤 2:调用方法

// 1. 调用公开方法:invoke(对象, 方法参数)
sayHelloMethod.invoke(user); // 输出:Hello, 我是null(此时name还没设)
// 2. 调用私有方法:先开权限,再invoke
setInfoMethod.setAccessible(true);
setInfoMethod.invoke(user, "李四", 30); // 给user设置name和age
// 再调用sayHello,看看效果
sayHelloMethod.invoke(user); // 输出:Hello, 我是李四(成功!)

3. 操作成员变量:帮你 “读 / 写变量”

平时要改私有变量得靠 setter,反射直接跳过 setter,直接修改变量值,简直是 “直达核心”。

示例:用反射修改 User 的私有变量

步骤 1:获取成员变量

Class<?> userClass = User.class;
User user = new User();
// 获取私有变量name(getDeclaredField()支持私有)
Field nameField = userClass.getDeclaredField("name");

步骤 2:读 / 写变量值

// 写变量:先开权限,再set
nameField.setAccessible(true);
nameField.set(user, "王五"); // 直接给user的name赋值
// 读变量:get
String userName = (String) nameField.get(user);
System.out.println(userName); // 输出:王五(成功修改并读取!)

五、总结:反射是把 “双刃剑”

反射的优点很明显:灵活、强大,是框架和通用工具的核心;但缺点也不能忽视:

  1. 破坏封装:能访问私有成员,可能导致代码不安全;
  1. 性能略差:反射是运行时操作,比直接调用慢一点(但一般场景下影响不大,别过度焦虑);
  1. 代码可读性低:别人看你用反射的代码,可能会懵:“这咋没 new 对象就调用方法了?”

所以咱用反射的原则是:能不用则不用,该用的时候别犹豫—— 比如写框架、通用工具时,反射是最佳选择;但平时写业务代码,还是老老实实用 new 和直接调用更清晰。

好了,今天的反射 “透视挂” 教程就到这!各位后端小伙伴要是在项目里用反射踩过坑,或者有更骚的用法,评论区聊聊~ 觉得有用的话,点赞收藏走一波,下次想看啥知识点,评论区告诉我!