一、“类对象”是什么意思?
1. 概念
一句话总结,类对象是JVM在加载一个类时,创建的用于描述这个类、记录这个类的一些信息的这么一个对象。
2. 解释
在 Java 中,每一个 .class 文件(比如 User.class)在程序运行时都会被 JVM 加载。
当 JVM 加载一个类时,会在内存中创建一个属于该类的“描述对象”,这个对象的类型是:
java.lang.Class
所以:
| Java 源代码中的类 | 运行时中的对应对象 |
|---|---|
class User {} | 一个 Class<User> 对象 |
比如你写了:
class User {...}
运行时,会存在一个对象:
Class<User> clazz = User.class;
这个对象就叫 类对象(Class Object)。
二、类对象的作用
| 作用类别 | 具体作用 | 示例代码 | 说明 |
|---|---|---|---|
| 1. 描述类结构(元数据) | 获取类的字段、方法、构造器等 | clazz.getDeclaredFields() | Class 对象保存了 类的“说明书”,包含所有结构信息 |
| 2. 创建类实例(反射) | 使用反射 new 对象 | A a = clazz.newInstance(); | 不通过 new A(),而是通过类对象创建实例 |
| 3. 作为类锁(synchronized) | 同步静态方法或 class 代码块 | synchronized(A.class) {} | Class 对象是类级别唯一的,用来做类锁 |
| 4. 类的唯一标识 | JVM 中一个类对应唯一一个 Class 对象 | A.class == a.getClass() | 判断两个对象是否是同一类型的根本依据 |
| 5. 类加载相关信息 | 获取类加载器 | clazz.getClassLoader() | Class 对象记录类由谁加载(比如 AppClassLoader) |
| 6. 反射操作字段/方法 | 访问与调用字段、方法 | field.set(a, 10); | 必须先拿到类对象才能继续反射 |
| 7. 泛型检查(擦除后类型信息) | 在运行时检查对象类型 | obj.getClass() | instanceof 与 getClass 均依赖类对象 |
| 8. 获取父类与接口信息 | 查看继承结构 | clazz.getSuperclass() | 查看类的继承链、实现的接口 |
| 9. 资源加载(常见技巧) | 通过类对象读取资源文件 | clazz.getResourceAsStream("config.txt") | 类对象会找到与它同路径的资源 |
三、类对象 和 类的对象 区别
1. 具体区别
一个类A,它的类对象不是他的对象。
A的类对象:是一个 Class 类型实例,由 JVM 在加载 A 时创建,它不是 A 的实例,而是 “描述 A 的对象”。
A的对象:是一个A类型的实例。
下面是具体区别
| 对比项目 | 类对象(Class 对象) | 类的对象(实例对象) |
|---|---|---|
| 是什么 | 描述某个类的“说明书” | 该类真正创建出来的对象 |
| 类型 | Class<A> | A |
| 创建者 | JVM 自动创建(类加载时,由类加载器ClassLoader创建) | 使用 new A() 或反射创建 |
| 数量 | 每个类只有 1 个 类对象 | 可以有 无数个实例对象 |
| 生命周期 | 在类加载时产生,直到类卸载才消失 | new 出来,GC 后销毁 |
| 用途 | 反射、类锁、查看类结构 | 执行对象的行为,存数据 |
| 是否包含成员变量数据 | ❌ 不包含实例字段数据 | ✔ 包含自己的字段(state) |
| 是否能调用方法 | ✔ 能反射调用方法(但不能执行具体实例行为) | ✔ 直接调用实例方法 |
| 是否能当锁使用 | ✔ 可以(类锁)synchronized(A.class) | ✔ 可以(对象锁)synchronized(obj) |
| 获取方式 | A.class 或 obj.getClass() | new A() |
| 代表的含义 | 类的元数据(Metadata) | 类的具体数据对象(Instance) |
| 举例(类型) | Class<Person> | Person p = new Person() |
2. 在JVM中的区别
2.1 JVM内存结构
JVM(HotSpot)中和类/对象相关的区域主要有:
| 内存区域 | 内容 |
|---|---|
| 方法区(Method Area / MetaSpace) | 存类的元数据 |
| 堆(Heap) | 存实例对象(包括类对象 Class 信息) |
| 栈(Stack) | 存局部变量(引用等) |
| 程序计数器/本地方法栈 | 不相关,此处略 |
所以:
-
类的元数据存在方法区(元空间),因为它是类真正的本体,一个模板
-
类对象(Class 对象)存在堆,因为它是对象
-
实例对象存在堆
2.2 类对象内存结构(Class 对象)
每个类加载时,JVM 会在 堆区 中创建它的类对象(Class 实例)。
✔ 类对象内存结构示意图(Class<Person>)
堆(Heap)
┌───────────────────────────────────────────┐
│ 类对象(Class<Person>) │
│-------------------------------------------│
│ 类名:Person │
│ 字段列表(Field 信息) │
│ - name : String │
│ - age : int │
│ 方法列表(Method 信息) │
│ - eat() │
│ - run() │
│ 构造方法信息 │
│ 父类信息:Object │
│ 实现接口信息 │
│ 类加载器引用 │
│ 运行时常量池引用 │
└───────────────────────────────────────────┘
注意:
➡ 类对象不保存实例字段的实际值(那是实例对象的数据)
2.3 实例对象内存结构(Person 对象)
实例对象被 new 出来后放在 堆中(Heap)。
堆(Heap)
┌──────────────────────────────────────────┐
│ 实例对象(Person p1) │
│------------------------------------------│
│ 对象头(Mark Word) │
│ 类指针(指向 Class<Person>)-------------│ → 指向方法区的类对象
│------------------------------------------│
│ 实例字段实际值: │
│ name = "Tom" │
│ age = 20 │
└──────────────────────────────────────────┘
✔ 实例对象中真正保存数据的是字段的值
这些值属于每个对象独立拥有。
2.4 类对象 vs 实例对象 对比图(完整版)
JVM 内存模型
────────────
堆(Heap)
┌─────────────────────────────┐
│ Class<Person> 类对象 │
│ ───────────────────────── │
│ 字段列表(name, age) │
│ 方法列表(eat(), run()) │
│ 父类、接口信息 │
│ 类加载器 │
└─────────────────────────────┘
▲
│ 类指针
│
┌───────────────┴───────────────┐
│ │
▼ ▼
堆(Heap) 堆(Heap)
┌──────────────────┐ ┌──────────────────┐
│ Person 实例 p1 │ │ Person 实例 p2 │
│──────────────────│ │──────────────────│
│ 对象头 │ │ 对象头 │
│ class pointer →──┼────────────►│ class pointer │
│ name="Tom" │ │ name="Alice" │
│ age=20 │ │ age=30 │
└──────────────────┘ └──────────────────┘
从图你看到:
-
类对象只有一个,放堆区
-
每个实例对象都在堆里
-
每个对象内部的 class pointer 指向类对象(说明它是哪种类型)
四、类对象的获取方法
1. 编译期方式:类名.class(最常用、最简单)
Class<?> clazz = Person.class;
✔ 不需要创建对象
✔ 代码写在哪都可以
✔ 编译器保证安全
用途: 反射、类锁、读取元数据等。
2. 通过对象获取:obj.getClass()
Person p = new Person(); Class<?> clazz = p.getClass();
✔ 必须先有实例对象
✔ 运行时获取
✔ 不依赖具体类型(可以用在多态)
用途:
-
判断两个对象是不是同一类型
-
运行时动态获取其类结构
3. 通过 Class.forName("类的全限定名")(动态加载)
Class<?> clazz = Class.forName("com.example.Person");
特点:
✔ 字符串加载类
✔ 可以做 动态加载(最重要的用途)
✔ 常用于 JDBC、插件式框架、反射工具等
例如 JDBC:
Class.forName("com.mysql.jdbc.Driver");
4. 类加载器:ClassLoader.loadClass()
ClassLoader loader = Main.class.getClassLoader(); Class<?> clazz = loader.loadClass("com.example.Person");
✔ 专业级用法
✔ 在框架、容器、自定义类加载器中常用
✔ 能实现 “热加载”、“隔离加载” 等高级功能v

被折叠的 条评论
为什么被折叠?



