Class 类
你有多少个名字?
普通人可能有两个:身份证上的大名叫什么,平时别人叫你什么。但 Class 对象更复杂——它有全限定名、简单名、还有规范名(Cannonical Name)。
不信?试试:
java
System.out.println(new ArrayList<String>().getClass().getName()); // java.util.ArrayList
System.out.println(new ArrayList<String>().getClass().getSimpleName()); // ArrayList在反射的世界里,Class 对象就是一切的开始。理解它,就理解了一半的反射。
Class 是什么?
Class 是一个特殊的类。它是 JVM 对「类」这种概念的抽象。
当你写下 class Person {},JVM 会:
- 加载这个类的字节码
- 创建一个 Class 对象来描述这个类
- Class 对象会被缓存(同一个类只会有一个 Class 对象)
这个 Class 对象存储了一个类的所有元信息:字段列表、方法列表、构造方法、继承关系、注解...
重要结论:Class 本身也是一个类,所以 Class.class 也是合法的。这是元类(Metaclass)的概念,Java 里没有暴露出来,但原理类似。
三种获取方式的选择
java
public class ClassAcquisitionStrategies {
public static void main(String[] args) throws ClassNotFoundException {
// 场景一:你已经知道类名,编译期就确定
Class<String> clazz1 = String.class;
System.out.println("编译期确定: " + clazz1.getSimpleName());
// 场景二:你有个对象,想知道它的类型
String s = "hello";
Class<? extends String> clazz2 = s.getClass();
System.out.println("从对象获取: " + clazz2.getSimpleName());
// 场景三:类名是运行时才确定的(最灵活)
String className = "java.util.LinkedList";
Class<?> clazz3 = Class.forName(className);
System.out.println("动态加载: " + clazz3.getSimpleName());
}
}什么时候用哪个?
| 获取方式 | 何时使用 | 特点 |
|---|---|---|
.class | 编译期知道类 | 最快,IDE 还能帮你检查类是否存在 |
getClass() | 已有对象 | 适合多态场景,能拿到真实类型 |
forName() | 字符串来源动态 | 配置文件中读取、插件系统、热加载 |
有个经典陷阱:
java
public class ClassCastTrap {
public static void main(String[] args) throws Exception {
// 你可能以为两者相等...
Class<String> c1 = String.class;
Class<String> c2 = (Class<String>) Class.forName("java.lang.String");
// 但泛型在编译后被擦除了,运行时无法区分 List<String> 和 List<Integer>
Class<?> listString = Class.forName("java.util.List");
Class<?> listInt = Class.forName("java.util.List");
System.out.println("两者相等吗? " + (listString == listInt)); // true!
// List<String> 和 List<Integer> 在运行时是同一个 Class 对象
}
}这就是泛型擦除——类型参数信息在编译后就丢失了。
获取类信息
Class 对象提供了一大堆「问句」:
java
import java.util.*;
public class ClassIntrospection {
public static void main(String[] args) {
Class<ArrayList<String>> clazz = ArrayList.class;
// 名字系列
System.out.println("getName(): " + clazz.getName()); // java.util.ArrayList
System.out.println("getSimpleName(): " + clazz.getSimpleName()); // ArrayList
System.out.println("getCanonicalName():" + clazz.getCanonicalName()); // java.util.ArrayList
// 包信息
Package pkg = clazz.getPackage();
System.out.println("getPackage(): " + pkg.getName());
// 继承关系
System.out.println("getSuperclass(): " + clazz.getSuperclass().getSimpleName());
// 实现接口
System.out.println("getInterfaces(): " + Arrays.toString(clazz.getInterfaces()));
// 修饰符
int mods = clazz.getModifiers();
System.out.println("isPublic(): " + java.lang.reflect.Modifier.isPublic(mods));
System.out.println("isAbstract(): " + java.lang.reflect.Modifier.isAbstract(mods));
System.out.println("isFinal(): " + java.lang.reflect.Modifier.isFinal(mods));
System.out.println("Modifiers: " + java.lang.reflect.Modifier.toString(mods));
}
}注意区分几种名字的输出:
java
Class<Map.Entry<String, Integer>> entryClass = Map.Entry.class;
entryClass.getName(); // java.util.Map$Entry(内部类用 $ 分隔)
entryClass.getSimpleName(); // Entry
entryClass.getCanonicalName() // java.util.Map.Entry(人类友好的名字)创建实例
Class 对象能创建对应类的实例:
java
public class ClassInstantiation {
public static void main(String[] args) throws Exception {
// 方式一:newInstance() — JDK 9 已废弃
// 原因:只能调用无参构造,如果无参构造不存在会抛异常
@SuppressWarnings("deprecation")
Object obj1 = Object.class.newInstance(); // 废弃警告
// 方式二:getDeclaredConstructor().newInstance() — 推荐
Object obj2 = Object.class.getDeclaredConstructor().newInstance();
System.out.println("obj2: " + obj2);
// 方式三:调用有参构造
String s = String.class.getDeclaredConstructor(String.class)
.newInstance("hello");
System.out.println("s: " + s);
// 方式四:获取所有构造方法
System.out.println("=== String 的构造方法 ===");
for (java.lang.reflect.Constructor<?> c : String.class.getDeclaredConstructors()) {
System.out.println(c);
}
}
}为什么废弃 newInstance()?
因为它「吞」了异常。如果无参构造抛出异常(比如构造失败了),newInstance() 会把它包装成 InvocationTargetException,而不是直接抛出原异常。这让调试变得困难。
获取成员
java
import java.lang.reflect.*;
public class MemberRetrieval {
public String publicField;
private String privateField;
public static int staticField;
public void publicMethod() {}
private String privateMethod() { return ""; }
public MemberRetrieval() {}
public MemberRetrieval(String arg) {}
public static void main(String[] args) {
Class<MemberRetrieval> clazz = MemberRetrieval.class;
System.out.println("=== 字段 ===");
for (Field f : clazz.getDeclaredFields()) {
String mods = Modifier.toString(f.getModifiers());
System.out.println((mods.isEmpty() ? "" : mods + " ") +
f.getType().getSimpleName() + " " + f.getName());
}
System.out.println("\n=== 方法 ===");
for (Method m : clazz.getDeclaredMethods()) {
System.out.println(Modifier.toString(m.getModifiers()) + " " +
m.getReturnType().getSimpleName() + " " + m.getName());
}
System.out.println("\n=== 构造方法 ===");
for (Constructor<?> c : clazz.getDeclaredConstructors()) {
System.out.println(c.getName());
}
}
}注意用的是 getDeclaredFields() / getDeclaredMethods(),不是 getFields() / getMethods()。
| 方法 | 返回值 | 包含 Private 吗 | 包含继承的吗 |
|---|---|---|---|
| getFields() | Field[] | ❌ | ❌ |
| getDeclaredFields() | Field[] | ✅ | ❌ |
| getMethods() | Method[] | ❌ | ✅ |
| getDeclaredMethods() | Method[] | ✅ | ❌ |
动态加载类的陷阱
Class.forName() 有个容易被忽视的特性:它会触发类初始化。
java
public class ClassInitializationTrap {
static class HeavyClass {
static {
System.out.println("HeavyClass 被初始化了!");
}
public static final String CONST = "initialized"; // 编译期常量,不触发
public static String NON_CONST = "need init"; // 非 final,触发初始化
}
public static void main(String[] args) throws Exception {
System.out.println("准备加载...");
// 不会触发 static{} 块
Class<?> c1 = Class.forName("java.lang.String");
// 会触发 static{} 块(但如果是编译期常量,则不会)
Class<?> c2 = Class.forName("ClassInitializationTrap$HeavyClass");
System.out.println("加载完成");
}
}在 JDBC 驱动加载的场景下,这个特性是故意被利用的:
java
// JDBC 4.0 之前,需要手动加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// JDBC 4.0 之后,驱动类内部有 static{} 块,加载即注册
// 所以不需要显式 Class.forName()要点回顾
- Class 对象是单例的 — 每个类在 JVM 中只有一个 Class 对象
- 获取方式有三种 —
.class、obj.getClass()、Class.forName() - forName() 会触发类初始化 — static{} 块会执行
- 泛型信息被擦除 —
List<String>.class和List<Integer>.class是同一个对象 - getDeclaredXxx vs getXxx — 前者只获取本类声明,后者包括继承的
Class 是反射的起点,理解它就像理解 JVM 的「户口本系统」——每个类都有自己的档案,Class 对象就是那份档案。
