Java的反射与枚举
一. 反射
- Java 反射(Reflection)是 Java 语言的核心特性之一,它允许程序在运行时获取类的信息(如类名、父类、接口、字段、方法等),并动态操作类的成员(创建对象、调用方法、修改字段值等),而无需在编译期知道具体的类信息
- 可访问类的私有成员(通过特殊 API 绕过访问权限检查)
- 为框架开发提供基础(如 Spring 的 IOC、MyBatis 的 ORM 映射等)
1) 反射的使用步骤
以下类来自于java.lang.reflect 包
每个类都有一系列的方法
1.获取Class对象
Class对象是反射的入口,它代表类的字节码信息,是 JVM 对类的 “元数据描述”
对于Class对象的获取方式有三种
// 方式1:通过对象的getClass()方法(需先有对象)Students students = new Students();// 需要操作的对象.getClassClass<?> class1 = students.getClass();// 方式2:通过类名.class(无需创建对象,不触发类初始化)Class<?> class2 = Students.class;// 方式3:通过Class.forName("全类名")(最常用,触发类初始化)Class<?> class3; // 全类名=包名+类名//特别地 方法3需要处理ClassNotFoundException异常{try {class3 = Class.forName("src.Students");} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
注意:
- 全类名为 所在包(src包).类名(Students)
- 方法3需要处理异常(核心原因:异常类型与编译期检查的差异)
- == 一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的c1,c2,c3进行 equals 比较,发现都是true==
2.通过Class对象操作类的成员
- 获取Class对象后可通过Class对像操作对象的成员变量,方法,构造方法
- 可以通过newInstance()方法实例化对象
- 对成员操作的部分方法
- 以下以构造方法为例(假设无参构成方法是public,带参数的构造方法是private)
注意 !!! setAccessible方法在访问非public成员时必须使用设为true可以正常访问
public static void reflectTestDemo1() {Class<?> c;try {c = Class.forName("src.Students");//1.访问public成员构造方法Constructor<?> constructor1 = c.getConstructor();//通过反射实例化对象Students student = (Students) c.newInstance();} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}// 反射私有的构造方法 public static void reflectPrivateConstructor() {Class<?> c1;try {c1 = Class.forName("src.Students");// 获取构造方法(注意!!! 获取方法时参数类型)Constructor<Students> con = (Constructor)c1.getDeclaredConstructor(int.class,String.class);//注意 !!! setAccessible方法在访问非public成员时必须使用设为true可以正常访问con.setAccessible(true);Students student = con.newInstance(18,"zhangsan");System.out.println(student);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}}
- 不同类中的get方法返回类型与对应类相同,或者是对应类型的数组(如Constructor<?> constructor1 = c.getConstructor();)
- 获取构造方法(注意!!! 获取方法时参数类型)
getDeclaredConstructor(int.class,String.class); - 反射中有时要处理继承关系
// 父类
class Parent {public String pPublic = "父类public字段";private String pPrivate = "父类private字段"; // 子类无法继承
}// 子类
class Child extends Parent {public String cPublic = "子类public字段";private String cPrivate = "子类private字段";
}public class Test {public static void main(String[] args) throws Exception {Class<?> childClazz = Child.class;// 1. getFields():获取当前类+父类的所有public字段Field[] fields = childClazz.getFields();for (Field f : fields) {System.out.println(f.getName()); // 输出:pPublic、cPublic(父类和子类的public字段)}// 2. getDeclaredFields():仅获取当前类自身的所有字段(包括private)Field[] declaredFields = childClazz.getDeclaredFields();for (Field f : declaredFields) {System.out.println(f.getName()); // 输出:cPublic、cPrivate(仅子类自己的字段)}// 3. 若要获取父类的private字段,必须通过父类的Class对象Class<?> parentClazz = childClazz.getSuperclass(); // 获取父类Class对象Field parentPrivateField = parentClazz.getDeclaredField("pPrivate");parentPrivateField.setAccessible(true); // 取消权限检查Parent parent = new Parent();System.out.println(parentPrivateField.get(parent)); // 输出:父类private字段}
}
- 反射调用需处理大量受检异常(如ClassNotFoundException、IllegalAccessException等)
- 反射违反了Java中的封装(可以调用私有方法)因此存在一定安全问题
二. 枚举
枚举(Enum) 是一种特殊的类,用于定义一组固定的常量(枚举常量),本质上是对 “有限可数” 场景的抽象(如季节、星期、状态等)
修饰符 enum 枚举名 {枚举常量1, 枚举常量2, ..., 枚举常量n; // 结尾可加;(可选)// 成员变量、方法、构造器(可选)
}
1)核心性质
- 枚举本质上是java.lang.Enum的子类(无需显式继承,编译器自动处理),因此不能继承其他类(但可实现接口)。
- 枚举常量在类加载时被初始化,且仅存在一个实例(类似单例),不能通过new创建,可以通过如下例代码(Season s = Season.SPRING;)使用
- 默认实现Comparable接口,枚举常量的比较基于声明顺序(ordinal()值),compareTo()方法直接比较ordinal。
- 构造器必须是私有,枚举的构造器只能用private修饰(默认也是private),用于初始化枚举常量的属性,不能被外部调用
- 可以运用在switch语句
- 语法规则与示例如下
public enum Season {// 枚举常量必须在最前面,且需通过构造器初始化属性// 当没有自己写构造方法时默认自带无参的构造方法可以没有括号内的内容SPRING("春天", "温暖"),SUMMER("夏天", "炎热"),AUTUMN("秋天", "凉爽"),WINTER("冬天", "寒冷");// 成员变量private final String chineseName;private final String desc;// 私有构造器(必须private)private Season(String chineseName, String desc) {this.chineseName = chineseName;this.desc = desc;}// 自定义方法public String getChineseName() {return chineseName;}public String getDesc() {return desc;}
}class enumdemo {public static void main(String[] args) {Season s = Season.SPRING;System.out.println(s.getChineseName()); // 输出:春天System.out.println(s.getDesc()); // 输出:温暖}
}
2)与反射联系的特殊之处
-
枚举实例(常量)只能在枚举类内部定义,运行时无法通过反射(或 new)创建新的枚举对象
-
为什么反射不能创建枚举对象?
枚举的核心设计是 “有限且固定的常量集合”,JVM 从底层禁止了通过反射创建枚举实例的可能,原因包括:
单例性保证:每个枚举常量在类加载时唯一初始化,反射创建新实例会破坏单例
语义完整性:枚举的常量是 “有限可选值”,运行时新增实例会违背枚举的设计初衷 -
除了创建枚举对象以外,获取枚举的Class类,Method,Constructor等都是允许的
注意
在获取枚举类的Constructor时会出现隐式参数问题
根本原因:java.lang.Enum 的构造器protected Enum(String name, int ordinal)
一共三种情况
- 枚举没有自定义构造器(仅默认构造器)
此时,编译器会自动生成一个构造器,参数列表直接为 (String name, int ordinal),用于调用父类构造器
隐式参数会显式成为构造器的参数,反射时必须匹配这两个参数类型,否则会抛 NoSuchMethodException
// 无自定义构造器的枚举
enum Season {SPRING, SUMMER, AUTUMN, WINTER; // 仅枚举常量,无构造器定义
}// 反射获取构造器(必须匹配 (String, int))
public class Test {public static void main(String[] args) throws Exception {Class<Season> seasonClass = Season.class;// 正确:匹配编译器生成的 (String name, int ordinal) 构造器Constructor<Season> ctor = seasonClass.getDeclaredConstructor(String.class, int.class);ctor.setAccessible(true);// 尝试创建实例(仍会因JVM限制失败,但获取构造器步骤成功)try {Season newSeason = ctor.newInstance("WINTER", 3);} catch (Exception e) {System.out.println(e.getMessage()); // 输出:Cannot reflectively create enum objects}}
}
- 枚举有自定义构造器
此时,编译器会改造用户定义的构造器,在内部自动调用 super(name, ordinal)(传入隐式参数),但不会将 name 和 ordinal 暴露为构造器的参数,隐式参数被编译器 “隐藏处理”,反射只需匹配用户自定义的参数即可
// 有自定义构造器的枚举
enum Color {RED("红色"), GREEN("绿色");private final String desc;// 自定义构造器(仅含用户参数 String desc)private Color(String desc) { this.desc = desc; // 编译器自动添加:super(name, ordinal); (隐式调用,用户无需关心)}
}// 反射获取构造器(仅匹配用户参数 String.class)
public class Test {public static void main(String[] args) throws Exception {Class<Color> colorClass = Color.class;// 正确:匹配用户定义的 (String desc) 构造器Constructor<Color> ctor = colorClass.getDeclaredConstructor(String.class);ctor.setAccessible(true);}
}
- 枚举存在字段冲突(非法场景,应避免)
java.lang.Enum 已定义 public final String name 和 public final int ordinal(final 不可修改),若枚举重复定义同名字段(如 public int ordinal)会导致编译冲突(或反编译后逻辑混乱),迫使构造器强制暴露父类参数
enum TestEnum {A, B;public int ordinal; // 与父类冲突!编译报错// 被迫定义构造器(实际无法编译)private TestEnum(String name, int ordinal, int code) { this.ordinal = ordinal; // 试图覆盖 final 字段,非法}
}// 反射需匹配 (String, int, int)
Constructor<TestEnum> ctor = TestEnum.class.getDeclaredConstructor(String.class, int.class, int.class);