Java-Spring入门指南(九)反射与反射对象
Java-Spring入门指南(九)反射与反射对象
- 前言
- 一、反射与反射对象
- 1.1 什么是反射?
- 1.2 反射的核心
- 二、获得反射对象
- 2.1 核心方式:Class.forName(类的全路径名)
- 三、得到Class类的几种方式
- 3.1 四种获取方式全解析
- 方式1:类名.class
- 方式2:对象.getClass()
- 方式3:Class.forName(全路径名)
- 方式4:基本数据类型的包装类.TYPE
- 3.2 四种方式对比与选择
- 四、所有类型的Class对象
- 4.1 各类类型的Class对象实战
- 4.2 关键结论
前言
在上一篇博客中,我们留下了一个关键伏笔:注解的核心能力(被程序读取)必须依赖反射实现。
- 学完之后还有没有一些问题?
“
@Log注解贴在方法上,程序是怎么‘看到’这个注解并自动打印日志的?”
“Spring的@Autowired为什么能‘凭空’找到并注入对象?”
答案正是反射。如果说注解是“贴在代码上的标签”,那反射就是“能读取标签、甚至修改标签对应内容的万能扫描仪”。
- 本文将从“反射的本质”切入,手把手带你掌握反射的核心:理解反射对象、吃透Class对象的获取方式、覆盖所有类型的反射场景。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482

一、反射与反射对象
1.1 什么是反射?
反射(Reflection)是Java的核心特性之一,指程序在运行时可以动态获取类的结构信息(属性、方法、构造器等),并可以动态操作对象的属性和方法的能力。
- 简单来说,正常的编程逻辑是“先有类,再创建对象”(编译期确定),而反射是“先有对象或类名,再反推类的结构”(运行期动态获取)。
可以用一个生活化的类比理解:
- 正常逻辑:你拿到一本《Java入门书》(类),直接翻阅目录(结构)、阅读内容(调用方法);
- 反射逻辑:你捡到一张写着“《Java入门书》”的书签(对象/类名),通过这张书签找到这本书,再拆解它的目录、修改里面的内容(动态操作)。
1.2 反射的核心
反射的所有操作都围绕一个核心——Class类对象(简称“Class对象”)。
JVM在加载一个类时,会自动为这个类创建一个唯一的java.lang.Class类的实例(即Class对象),这个对象会完整封装该类的所有结构信息,包括:
- 类的基本信息(类名、包名、父类、实现的接口);
- 类的属性(
Field对象数组); - 类的方法(
Method对象数组); - 类的构造器(
Constructor对象数组); - 类上的注解(
Annotation对象数组)。
一句话总结:Class对象是“类的说明书”,反射就是“阅读说明书并操作类”的过程。
二、获得反射对象
获得Class对象是反射操作的第一步。JVM保证:一个类在内存中只会有一个Class对象,无论通过哪种方式获取,其本质都是同一个实例。
2.1 核心方式:Class.forName(类的全路径名)
这是最常用的方式,尤其适用于编译期不知道类名,运行时动态指定类的场景(如框架加载配置文件中的类)。
实战代码
// 实体类:pojo/entity
class User {private String name;private int age;// 无参构造器(反射创建对象时常用)public User() {}// 有参构造器public User(String name, int age) {this.name = name;this.age = age;}// getter/setterpublic String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }@Overridepublic String toString() {return "User{name='" + name + "', age=" + age + "}";}
}// 测试反射获取Class对象
public class Test02 {public static void main(String[] args) {try {// 1. 通过Class.forName(类的全路径名)获取Class对象// 全路径名:包名 + 类名Class c1 = Class.forName("com.kuang.reflection.User");System.out.println("c1: " + c1); // 输出:class com.kuang.reflection.User// 2. 多次获取同一类的Class对象,验证唯一性Class c2 = Class.forName("com.kuang.reflection.User");Class c3 = Class.forName("com.kuang.reflection.User");Class c4 = Class.forName("com.kuang.reflection.User");System.out.println("c2.hashCode(): " + c2.hashCode());System.out.println("c3.hashCode(): " + c3.hashCode());System.out.println("c4.hashCode(): " + c4.hashCode());// 输出结果:四个hashCode完全相同,证明是同一个对象} catch (ClassNotFoundException e) {// 注意:必须处理ClassNotFoundException异常// 异常原因:类的全路径名写错、类未被加载到类路径e.printStackTrace();}}
}
关键说明
- 类的全路径名:必须包含包名(如
com.kuang.reflection.User而非User),否则会抛出ClassNotFoundException; - 唯一性验证:
c2~c4的hashCode相同,因为JVM对一个类只加载一次,对应唯一的Class对象; - 异常处理:
Class.forName()是受检异常,必须显式处理(try-catch或throws)。
三、得到Class类的几种方式
除了Class.forName(),Java还提供了另外3种获取Class对象的方式,不同方式适用于不同场景。我们先完整列出所有方式,再对比分析。
3.1 四种获取方式全解析
方式1:类名.class
直接通过“类名 + .class”获取,适用于编译期已知具体类的场景(如手动获取String、User的Class对象)。
// 方式1:类名.class
Class c1 = User.class;
Class c2 = String.class;
System.out.println("User的Class对象:" + c1); // class com.kuang.reflection.User
System.out.println("String的Class对象:" + c2); // class java.lang.String
方式2:对象.getClass()
通过类的实例对象调用getClass()方法获取,适用于已有对象,需要反推其类结构的场景(如方法参数为Object类型,需动态获取其具体类型)。
// 方式2:对象.getClass()
User user = new User("小明", 20);
Class c3 = user.getClass();
System.out.println("通过user对象获取:" + c3); // class com.kuang.reflection.User
方式3:Class.forName(全路径名)
前文已详细讲解,适用于编译期未知类,运行时通过字符串指定的场景(框架核心用法,如Spring读取配置文件中的className)。
方式4:基本数据类型的包装类.TYPE
基本数据类型(int、char等)没有getClass()方法,也不能用Class.forName()(因为基本类型不是类),需通过其包装类的.TYPE属性获取。
// 方式4:包装类.TYPE(针对基本数据类型)
Class c4 = int.class; // 基本类型int的Class对象
Class c5 = Integer.TYPE; // 包装类Integer的TYPE属性,等价于int.class
Class c6 = Integer.class; // 包装类Integer本身的Class对象(≠ int.class)System.out.println(c4 == c5); // true(都是基本类型int的Class对象)
System.out.println(c4 == c6); // false(c4是int,c6是Integer类)
补充:数组的Class对象
数组也是一种特殊的类型,其Class对象可通过“数组对象.getClass()”或“数组类型.class”获取,且数组的Class对象与元素类型、维度相关。
// 数组的Class对象
int[] arr1 = new int[10];
int[][] arr2 = new int[2][3];
String[] arr3 = new String[5];Class c7 = arr1.getClass();
Class c8 = arr2.getClass();
Class c9 = arr3.getClass();System.out.println("int[]的Class:" + c7); // class [I([表示数组,I表示int)
System.out.println("int[][]的Class:" + c8); // class [[I(二维数组)
System.out.println("String[]的Class:" + c9); // class [Ljava.lang.String;(L表示引用类型)
3.2 四种方式对比与选择
| 获取方式 | 适用场景 | 编译期是否已知类 | 能否获取基本类型Class |
|---|---|---|---|
| 类名.class | 已知具体类,手动获取 | 是 | 能(如int.class) |
| 对象.getClass() | 已有对象实例,反推类结构 | 是(已知对象类型) | 否(基本类型无对象) |
| Class.forName(全路径名) | 运行时动态加载(框架常用) | 否 | 否(基本类型无全路径) |
| 包装类.TYPE | 获取基本数据类型的Class对象 | 是 | 能(仅基本类型) |
四、所有类型的Class对象
除了普通类,Java中的接口、枚举、注解、基本类型、void、数组等,都有对应的Class对象。这也是Class类被称为“反射根源”的原因——它能封装所有Java类型的结构信息。
4.1 各类类型的Class对象实战
我们用代码覆盖所有类型的Class对象获取,直观感受其差异:
package com.kuang.reflection;import java.io.Serializable;
import java.lang.annotation.*;// 1. 定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {}// 2. 定义枚举
enum Season { SPRING, SUMMER }// 3. 定义实现接口的类
@MyAnnotation
class Student implements Serializable {public String id;
}// 测试所有类型的Class对象
public class TestAllClass {public static void main(String[] args) {// 1. 普通类Class c1 = Student.class;// 2. 接口Class c2 = Serializable.class;// 3. 枚举Class c3 = Season.class;// 4. 注解Class c4 = MyAnnotation.class;// 5. 基本数据类型Class c5 = int.class;// 6. void类型Class c6 = void.class;// 7. 数组Class c7 = String[].class;// 打印所有Class对象System.out.println("1. 普通类:" + c1); // class com.kuang.reflection.StudentSystem.out.println("2. 接口:" + c2); // interface java.io.SerializableSystem.out.println("3. 枚举:" + c3); // class com.kuang.reflection.SeasonSystem.out.println("4. 注解:" + c4); // interface com.kuang.reflection.MyAnnotationSystem.out.println("5. 基本类型:" + c5); // intSystem.out.println("6. void类型:" + c6); // voidSystem.out.println("7. 数组:" + c7); // class [Ljava.lang.String;}
}
4.2 关键结论
- 无论哪种类型(类、接口、枚举等),其Class对象都是
Class类的实例; - 不同类型的Class对象打印结果有差异(如接口前缀为
interface,基本类型直接显示名称); - 所有类型的Class对象都可以通过反射API操作(如获取注解、方法等),这是框架实现“通用逻辑”的基础(如Spring可以扫描所有类型的组件)。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482
| 非常感谢您的阅读,喜欢的话记得三连哦 |

