CodeGym /课程 /JAVA 25 SELF /反射入门:Class 类及其用途

反射入门:Class 类及其用途

JAVA 25 SELF
第 62 级 , 课程 0
可用

1. 什么是反射

Java 中的反射是一种机制,允许程序在运行时检查甚至修改自身的结构。就好像你能看进自己的内部并发问:“我有哪些字段和方法?我有什么构造器?我究竟是个什么类?”——甚至还能调用自己的私有方法。

正式定义:
反射是一套 API,允许在程序运行时获取关于类、接口、字段、方法和构造器的信息,并对其进行操作。

为什么需要反射?

  • 框架:Spring、Hibernate、JUnit 利用反射来做“魔法”:自动创建对象、依赖注入、按名称或注解调用方法。
  • 序列化:将对象转换为 JSON/XML 及其逆过程——需要知道字段并能访问它们。
  • 测试:自动查找并调用带有注解(例如 @Test)的方法。
  • 动态加载类:在运行时按名称加载类(插件、驱动)。
  • IDE 与代码分析:自动补全、检查、重构。

生活中的例子

public class Person {
    private String name;
    private int age;

    public void sayHello() {
        System.out.println("你好,我叫 " + name);
    }
}

借助反射,我们可以在运行时得知类 Person 具有字段 nameage,以及方法 sayHello。更进一步,还可以修改私有字段的值或按名称调用方法。

一点幽默
可以说,反射就像《黑客帝国》:你意识到你的全部代码其实都是可在运行时被观察和修改的数据。只是别忘了那颗关于安全性的“红色药丸”!

2. Class 类:反射的核心

在 Java 中,每个对象和每个类型在运行时都关联着一个特殊的 Class 类型的对象。这个对象就是该类型的“元信息”。类 java.lang.Class<T> 是进入反射世界的中心入口。

如何获得对象 Class

有几种方式:

  • 通过 .class
    如果类在编译期已知,这是最直接且安全的方式:
    Class<Person> personClass = Person.class;
  • 通过对象:getClass()
    如果你已经有实例:
    Person p = new Person();
    Class<?> clazz = p.getClass();
    这里 clazz 是一个 Class 对象,描述了对象 p 的实际类。
  • 通过字符串:Class.forName()
    当类名只在运行时才知道时:
    Class<?> clazz = Class.forName("com.example.Person");
    适用于加载插件、驱动等动态场景。

示例:获取类名

Person p = new Person();
Class<?> clazz = p.getClass();
System.out.println(clazz.getName()); // 输出:Person or com.example.Person

表格:获取 Class 对象的主要方式

获取方式 何时使用 示例
MyClass.class
当类在编译期已知时
String.class
object.getClass()
当你已有对象时
someList.getClass()
Class.forName(name)
当类名仅在运行时可知时
Class.forName("java.util.Date")

示意图:对象及其 Class

+-------------------+
|   Person p        |
+-------------------+
         |
         v
+-------------------+
|  p.getClass()     |
+-------------------+
         |
         v
+-------------------+
|   Class<Person>   |
+-------------------+

通过 Class 进行类型检查

有时需要判断对象是否属于某个确切的类:

if (p.getClass() == Person.class) {
    System.out.println("This is definitely a Person!");
}

或者考虑继承关系——使用常规的 instanceof 运算符:

if (p instanceof Person) {
    // 按常规方式工作
}

3. 什么时候需要反射,什么时候不需要

能力越大,责任越大。反射是强大的工具,但只有在确有必要时才应使用。

反射必不可少的场景

  • 框架和库:Spring、Hibernate、JUnit、Jackson——自动化、“魔法”,减少“手写”代码。
  • 序列化:需要获知对象的字段以将其转换为 JSON/XML。
  • 插件与扩展:类是动态加载的,其结构事先未知。

为什么不应滥用反射

  • 丧失类型安全:错误在运行时才暴露。
  • 维护难度上升:代码不那么直观。
  • 性能:比直接调用更慢。
  • 安全性:可能绕过封装并暴露私有数据。

示例:何时不需要反射

p.sayHello();

示例:何时必须使用反射
你在编写一个迷你测试框架:用户用注解 @Test 标记方法,程序需要自动查找并调用它们——没有反射就做不到。

4. 演示:认识 Class

示例:输出类名及其父类

public class ReflectionDemo {
    public static void main(String[] args) {
        String s = "Hello, reflection!";
        Class<?> clazz = s.getClass();

        System.out.println("Class name: " + clazz.getName());
        System.out.println("简单Class name: " + clazz.getSimpleName());
        System.out.println("包: " + clazz.getPackageName());
        System.out.println("父类: " + clazz.getSuperclass().getName());
    }
}

输出:

Class name: java.lang.String
简单Class name: String
包: java.lang
父类: java.lang.Object

示例:获取基本类型的 Class

Class<Integer> intClass = int.class;
System.out.println(intClass.getName()); // int

Class<?> doubleClass = double.class;
System.out.println(doubleClass.getName()); // double

示例:获取数组的 Class

int[] arr = new int[10];
Class<?> arrClass = arr.getClass();
System.out.println(arrClass.getName()); // [I(数组的特定格式)

示例:检查类型归属

if (arrClass.isArray()) {
    System.out.println("This is an array!");
}

5. 实践:迷你程序“这是什么类?”

我们编写一个程序,接收类的全限定名(例如,"java.util.ArrayList"),并输出其基本信息。

import java.util.Scanner;

public class ClassInfoPrinter {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入类的全限定名:");
        String className = scanner.nextLine();

        Class<?> clazz = Class.forName(className);

        System.out.println("Class name: " + clazz.getName());
        System.out.println("包: " + clazz.getPackageName());
        System.out.println("父类: " + clazz.getSuperclass().getName());

        Class<?>[] interfaces = clazz.getInterfaces();
        System.out.print("实现的接口: ");
        for (Class<?> i : interfaces) {
            System.out.print(i.getName() + " ");
        }
        System.out.println();
    }
}

运行示例:

请输入类的全限定名:java.util.ArrayList
Class name: java.util.ArrayList
包: java.util
父类: java.util.AbstractList
实现的接口: java.util.List java.util.RandomAccess java.lang.Cloneable java.io.Serializable 

关于 Class 与反射的有趣事实

  • JVM 中每个已加载的类型都对应唯一的代表——对象 Class。同一类型的所有实例共享同一个 Class<T>
  • 可以判定类型的类别:isInterface()isEnum()isArray()isPrimitive() 等。
  • 如果在 Class.forName 中给出错误的名称,会得到异常 ClassNotFoundException
  • 借助反射,即便在编译期不知道类,也能创建对象——我们将在下一讲讨论。

6. 初识反射时的常见错误

错误 1:指望反射很快。
实际上,反射比普通的方法调用和字段访问要慢。不要把它用在每一件小事上。

错误 2:尝试为不存在的类获取 Class。
如果名称写错,会得到 ClassNotFoundException。请对该异常进行处理。

错误 3:混淆类对象与实例。
对象 Class 描述的是类型的结构,它不是该类型的实例。

错误 4:不必要地使用反射。
如果能用普通代码解决——那就优先这样做。反射适用于动态性、框架、插件等场景。

错误 5:不慎处理私有字段和方法。
反射可以绕过封装,这在大型项目中可能导致错误和安全问题。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION