前言:反射是一种强大而实用的特性。它可以帮助我们编写更加灵活、动态的代码。 

目录

一、反射是什么?

二、获取Class对象:打开反射世界的大门

三、类信息大揭秘

反射能看清啥?

查看类的基本信息

查看类的父类和接口

查看类的字段

查看类的方法

四、动态操作对象

反射的神奇能力

动态创建对象

动态调用方法

五、打破封装?访问私有字段与方法

访问私有字段

访问私有方法

六、反射的双面性

优点与缺点

优点

缺点

七、反射的实战舞台

应用场景

总结

反射是把双刃剑,且用且珍惜


一、反射是什么?

反射,简单来说,就是Java在运行时能够“自我检查”的能力。就好比给程序装上了一面魔镜,程序可以通过这面镜子,清楚地看到自己内部的结构:有哪些类、类里有哪些属性和方法,还能知道这些属性和方法的访问权限。更厉害的是,反射不仅能查看,还能直接操作这些属性和方法,即使它们原本是私有的。

二、获取Class对象:打开反射世界的大门

要使用反射,首先得拿到类的Class对象。Class对象就像是一个班级的总辅导员,掌握着这个类所有学生(对象)的详细情况。获取Class对象有三种常见方法:

  1. 直接通过类名获取 :最 straightforward 的方式,例如Class<?> cls = String.class;,直接告诉Java:“我就是要研究String类的结构!”。

  2. 通过对象获取 :如果你已经有了某个类的对象,比如String str = "反射真好玩";,那就可以用Class<?> cls = str.getClass();,意思是让这个对象指向它的“班级总辅导员”。

  3. 通过类的全限定名获取 :这种方式有点像按图索骥,用Class.forName("java.lang.String");,通过完整的类名路径去加载对应的Class对象。

三、类信息大揭秘

反射能看清啥?

拿到Class对象后,就好比拿到了开启宝藏的钥匙,可以开始挖掘类的各种信息了。

查看类的基本信息
Class<?> cls = String.class;
// 获取类的全限定名,就像一个人的完整身份证号
String className = cls.getName();
System.out.println("类的全限定名:" + className); // 输出:java.lang.String
// 获取类的简单名,相当于大家日常喊的昵称
String simpleName = cls.getSimpleName();
System.out.println("类的简单名:" + simpleName); // 输出:String
// 获取类所在的包,知道它住在哪个“小区”
Package pkg = cls.getPackage();
System.out.println("类所在的包:" + pkg.getName()); // 输出:java.lang
查看类的父类和接口
Class<?> cls = String.class;
// 找到类的爸爸(父类)
Class<?> superCls = cls.getSuperclass();
System.out.println("父类是:" + superCls.getName()); // 输出:java.lang.Object
// 看看类的好伙伴(实现的接口)
Class<?>[] interfaces = cls.getInterfaces();
for (Class<?> intf : interfaces) {
    System.out.println("实现的接口:" + intf.getName());
}
// 输出内容可能包括:java.io.Serializable、java.lang.Comparable<java.lang.String>、java.lang.CharSequence 等
查看类的字段
Class<?> cls = String.class;
// 获取所有公共字段(包括从父类继承来的)
Field[] publicFields = cls.getFields();
for (Field field : publicFields) {
    System.out.println("公共字段:" + field.getName());
}
// 获取类自己声明的字段(不包括继承的)
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
    System.out.println("自己声明的字段:" + field.getName());
}
查看类的方法
Class<?> cls = String.class;
// 获取所有公共方法(包括从父类继承的)
Method[] publicMethods = cls.getMethods();
for (Method method : publicMethods) {
    System.out.println("公共方法:" + method.getName());
}
// 获取类自己声明的方法(不包括继承的)
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
    System.out.println("自己声明的方法:" + method.getName());
}

四、动态操作对象

反射的神奇能力

反射真正让人拍案叫绝的地方,是它能动态地创建对象、调用方法、修改属性,就像赋予了程序灵活的手脚,能在运行时完成很多原本编译期才能确定的事。

动态创建对象
Class<?> cls = String.class;
try {
    // 调用无参构造方法,相当于让类“生”出一个新对象
    Object obj = cls.newInstance();
    System.out.println("创建的对象是:" + obj); // 输出类似:(空白字符串)
} catch (InstantiationException | IllegalAccessException e) {
    e.printStackTrace();
}
动态调用方法
Class<?> cls = String.class;
try {
    // 找到名为 toUpperCase 的方法
    Method method = cls.getMethod("toUpperCase");
    // 在某个具体对象上调用这个方法
    Object result = method.invoke("hello");
    System.out.println("方法调用结果:" + result); // 输出:HELLO
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    e.printStackTrace();
}

五、打破封装?访问私有字段与方法

虽然封装是面向对象的重要原则,但反射就像一把万能钥匙,能打开私有字段和方法的 “保险箱”。不过,这招得谨慎用,毕竟破坏了封装可能会引发各种预料之外的问题。

访问私有字段

class Person {
    private String name = "张三";
}

Class<?> cls = Person.class;
try {
    // 找到私有字段 name
    Field nameField = cls.getDeclaredField("name");
    // 告诉反射,我要强行访问这个私有字段
    nameField.setAccessible(true);
    Person person = new Person();
    // 修改私有字段的值
    nameField.set(person, "李四");
    // 获取私有字段的值
    String name = (String) nameField.get(person);
    System.out.println("私有字段的值被改成了:" + name); // 输出:李四
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}

访问私有方法

class Person {
    private void sayHello() {
        System.out.println("你好呀,我是私有方法!");
    }
}

Class<?> cls = Person.class;
try {
    // 找到私有方法 sayHello
    Method sayHelloMethod = cls.getDeclaredMethod("sayHello");
    // 告诉反射,我要强行访问这个私有方法
    sayHelloMethod.setAccessible(true);
    Person person = new Person();
    // 调用私有方法
    sayHelloMethod.invoke(person); // 输出:你好呀,我是私有方法!
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    e.printStackTrace();
}

六、反射的双面性

优点与缺点

反射的能力如此出众,但它不是没有代价的。

优点
  • 高度灵活性 :程序能根据运行时的信息,决定要操作哪些类和方法,这对于开发框架、插件系统等极为重要。

  • 强大的动态性 :可以在不知晓具体类的情况下,创建对象、调用方法,给了程序极大的自由度。

缺点
  • 性能损耗 :反射操作会绕过很多编译期的优化,速度比正常调用慢不少。如果在性能敏感的场景滥用反射,就像让短跑冠军背着沙袋比赛,优势全无。

  • 安全风险 :可以访问私有字段和方法,这就像给了程序一把能打开所有房间的钥匙,不小心就会引发安全问题。

  • 代码可读性变差 :反射代码往往比较晦涩难懂,对于后来阅读代码的人来说,理解反射的意图和流程,就像在迷雾中摸索前行。

七、反射的实战舞台

应用场景

反射可不是徒有虚名的花架子,在很多领域都能看到它的身影。

  • 框架开发 :Spring、Hibernate等大名鼎鼎的框架,背后都靠反射支撑。比如 Spring 用反射实现依赖注入,把合适的对象“塞”到需要的地方;Hibernate 用反射和数据库打交道,把数据表和 Java 对象紧密联系起来。

  • 插件系统 :很多软件支持插件扩展功能,而插件往往是运行时才加载进来。反射能让主程序识别并调用插件里的功能,就像给软件搭了个“乐高积木”平台,随时添加新部件。

  • 测试工具 :在编写测试代码时,为了彻底检查类的功能,有时需要测试私有方法。反射就像测试人员的 “特殊通行证”,能畅通无阻地进入类的私密领域。

总结

反射是把双刃剑,且用且珍惜

Java反射机制,如同一把精巧却锋利的手术刀。在面对复杂多变的编程需求时,它能精准地切开问题的表象,直达核心;但在日常编码中,我们又不能随意挥舞它,以免引发性能的 “大出血” 或架构的 “感染炎症”。只有真正理解反射的原理、权衡好利弊,并结合实际场景慎重使用,我们才能让这把手术刀在关键时刻发挥出它挽救项目的神奇功效,而不是成为项目故障的导火索。当我们掌握了这门技术,就如同解锁了Java世界的隐藏超能力,能在编程的江湖中更加游刃有余地应对各种挑战。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐