☕ 第九章 反射机制和注解
📝 一句话概括

9.1 拿到的是说明书的封面(Class 对象),这一节把说明书翻开:先搞清 Class 类本身也是个类,再用它的一堆 get 方法把类名、字段、方法、构造器统统拆出来看——这就是反射的弹药库。

一、先解开一个绕脑的问题:Class 类也是个类

Java 里万物皆对象。那"类"这个概念本身呢?它也是对象——谁来描述它?答案就是 JDK 自带的 java.lang.Class 类。

名字 它是什么 它的对象描述谁
Student 类 我们自己写的类 描述"学生"
Class 类 JDK 自带的类(java.lang 包) 描述"别的类"
Class 对象 Class 类的实例 就是一份类的说明书

绕脑点(也是名考点):Class 类的构造器是私有的,JVM 加载类时自动帮你创建,你永远 new 不出来。这也正好呼应 9.1——三把钥匙拿到的都是同一份,因为压根只能有一份。

类加载时机回顾(1.4 运行机制的账):JVM 第一次用到某个类(new 它、访问它的 static 成员、Class.forName(...)……)时,把 .class 文件读进内存,顺便生成唯一一份 Class 对象,往后就一直用这一份。

二、说明书里到底写了什么

Class 对象就是一座弹药库——反射的全部能力都从这里取货。你写的每一个类,加载后它的类名、字段、方法、构造器、注解、父类、接口……全都被装进这一份对象里。

三、Class 常用方法:四大族

大规律(记这一句就够):

返回值拿到的 Field / Method / Constructor 就是反射三兄弟(都在 java.lang.reflect 包里)——这节先混脸熟,下节开始真刀真枪用它们。

你想要 方法 返回
全类名 com.it.Student getName() String
简名 Student getSimpleName() String
父类 getSuperclass() Class
实现的接口 getInterfaces() Class[]
所有公有字段 getFields() Field[]
所有字段(含私有) getDeclaredFields() Field[]
指定名字的字段 getDeclaredField("name") Field
所有公有方法 getMethods() Method[]
所有方法(含私有) getDeclaredMethods() Method[]
所有公有构造器 getConstructors() Constructor[]
指定构造器 getDeclaredConstructor(参数类型...) Constructor

四、收网旧账

五、动手:给 Student 类做个体检

写个 Student(带私有字段、公有/私有方法),然后用 Class 对象把它拆开看:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

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

    public void study() { }
    private void cheat() { }
}
import java.lang.reflect.Class;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Demo02 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<Student> c = Student.class;   // 三把钥匙随便用一把

        System.out.println("全类名:" + c.getName());
        System.out.println("简名:" + c.getSimpleName());
        System.out.println("父类:" + c.getSuperclass().getSimpleName());

        System.out.println("--- 公有字段 getFields ---");
        for (Field f : c.getFields()) {
            System.out.println(f);
        }
        System.out.println("--- 全部字段 getDeclaredFields ---");
        for (Field f : c.getDeclaredFields()) {
            System.out.println(f);
        }
        System.out.println("--- 全部方法 getDeclaredMethods ---");
        for (Method m : c.getDeclaredMethods()) {
            System.out.println(m);
        }
    }
}

运行后你会发现:getFields() 只数出 age(public),getDeclaredFields() 把 name(private)也揪出来了——Declared 规律当场验证。

三个 30 秒实验

  1. 把 c.getMethods() 打印出来,数一数——怎么多了 wait()、notify()、toString()?那是从 Object 继承来的(4.5 的账)。
  2. 打印 int.class.getName() 和 String[].class.getName()——基本类型和数组也有 Class 对象(名字长得怪:int、[Ljava.lang.String;)。
  3. 打印 c.getInterfaces()——如果让 Student 实现 Comparable,接口也会出现在说明书里。

六、常见误区

⚠️ 五个新手坑
  1. ❌ "Class 类这么神奇,我 new Class() 一个玩玩" —— 构造器私有,只有 JVM 能造。你只能领,不能造。
  2. ❌ "getDeclaredFields() 会把父类的字段也拿全" —— 恰恰相反:Declared = 全要但只管自己家;父类的得先 getSuperclass() 再查。
  3. ❌ "拿到的 Field 就是字段的值" —— Field 是字段的说明书(名字、类型、修饰符),值要另外取(后面章节)。
  4. ❌ "getMethods() 数出来比我写的方法多,是不是错了" —— 没错,它把 Object 继承来的也算上了,数数别慌。
  5. ❌ "只有引用类型才有 Class 对象" —— int、double、数组甚至 void 都有(int.class、void.class),万物皆可说明书。

七、自测一下

❓ Q1:Class 类的构造器是什么修饰的?为什么你 new 不出来?

private(私有)。说明书由 JVM 在类加载时统一创建,保证每个类全 JVM 只有一份——这正是 9.1 三把钥匙 == 成立的根本原因。

❓ Q2:getFields() 和 getDeclaredFields() 的区别,一句话说清?

带 Declared = 全都要(含私有)但不含父类;不带 = 只要 public 但含继承来的。两个"全"字方向相反,别背反了。

❓ Q3:想要"简名 Student"用哪个方法?想要"com.it.Student"呢?

简名:getSimpleName();全类名:getName()。

❓ Q4:getMethods() 的结果里为什么会混进 wait() 和 notify()?

因为不带 Declared 的方法含继承来的——这俩是 Object 类的方法,Student 继承了它们,自然被算进来。

❓ Q5(毕业题):写一段反射代码,给任意类做"体检报告"

目标:打印类名、全部字段(含私有)、全部方法(含私有),并各数出个数。

Class<Student> c = Student.class;
System.out.println("体检对象:" + c.getName());
Field[] fs = c.getDeclaredFields();
Method[] ms = c.getDeclaredMethods();
System.out.println("字段 " + fs.length + " 个:");
for (Field f : fs) System.out.println("  " + f);
System.out.println("方法 " + ms.length + " 个:");
for (Method m : ms) System.out.println("  " + m);

能独立写出这段,下节"反射造对象"你就能看懂 Spring 的影子了。

下一步学什么

相关笔记