Java中的反射、注解及枚举
约 4329 个字 439 行代码 4 张图片 预计阅读时间 20 分钟
反射
在Java中,反射的作用是获取类对象,通过这个类对象获取对应类中的成员属性(重新赋值)、成员方法(调用方法)和构造方法(实例化对象)
类对象:在Java中,一切皆是对象,而class文件加载进内存就会生成对应的对象,该对象就被称为class对象
Class类:描述Class对象就是Class类
同样,对于成员变量、成员方法和构造方法来说也有对应的对象和类:
- 成员变量:对应的成员变量对象为
Field对象,描述Field对象的类称为Field类 - 成员方法:对应的成员方法对象为
Method对象,描述Method对象的类称为Method类 - 构造方法:对应的构造方法对象为
Constructor对象,描述Constructor对象的类称为Constructor类
获取类对象
获取类对象是反射成立的第一步,常见的有三种方式获取类对象:
- 调用
Object中的方法:Class <?> getClass(),该方法返回一个类对象。该方法依赖于一个具体类的对象 - 通过
class成员获取类对象:基本数据类型/引用数据类型.class Class类中的静态方法:static Class<?> forName(String className),该方法的参数为类的全限定名,该方法返回一个类对象。该方法需要知道类的全限定名
Note
类的全限定名即为类所在的包名,即package后面的内容+指定类名
例如,下面的代码:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | public class Test {
public static void main(String[] args) throws Exception{
// 1. 调用Object中的方法:Class <?> getClass(),该方法返回一个类对象
Scanner scanner = new Scanner(System.in);
Class<? extends Scanner> aClass = scanner.getClass();
System.out.println(aClass);
// 2. 通过class成员获取类对象:基本数据类型/引用数据类型.class
Class<Scanner> scannerClass = Scanner.class;
System.out.println(scannerClass);
// 3. Class类中的静态方法:static Class<?> forName(String className),该方法的参数为类的全限定名,该方法返回一个类对象
Class<?> aClass1 = Class.forName("com.epsda.advanced.test_reflect.Test");
System.out.println(aClass1);
}
}
输出结果:
class java.util.Scanner
class java.util.Scanner
class com.epsda.advanced.test_reflect.Test
|
在IDEA中获取类的全限定名:
- 右键需要全限定名的类->选择
Copy Path/Reference...->选择Copy Reference - 在
forName方法中输入需要全限定名的类名,按下Tab或者Enter
Tip
如果按住 Ctrl + Left Button 点击类的全限定名可以跳转到指定类时,说明全限定名正确
在实际开发中最常用的获取类对象的方式是第二种,但是最通用的方式是第三种,因为第三种的参数是String类型,后面可以结合配置文件xxx.properties和Properties集合中的load方法加载类的全限定名
获取类对象的构造方法
获取类对象的构造方法一共有四种方法:
- 获取类对象中所有
public构造方法:使用Class类中的方法:Constructor<?>[] getConstructors(),该方法返回一个构造方法类的对象 - 获取类对象中指定的
public构造方法:Class类中的方法:Constructor<T> getConstructor (Class<?>... parameterTypes),该方法的参数为指定的public构造方法参数类型对应的类对象,返回一个构造方法类的对象。如果指定的public构造方法没有参数,则可以不传递任何内容 - 获取类对象中所有构造方法(包括
public和private):Constructor<?>[] getDeclaredConstructors(),该方法返回一个构造方法类的对象 - 获取类对象中指定的构造方法(包括
public和private):Constructor<T> getDeclaredConstructor (Class<?>... parameterTypes),该方法的参数为指定的构造方法的参数,返回一个构造方法类的对象。如果指定的构造方法没有参数,则可以不传递任何内容
下面示例使用的类:
| Java |
|---|
| @Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
public Integer age;
private Person(String name) {
this.name = name;
}
}
|
使用如下:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | public class Test01 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
// 获取所有public构造方法
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// 获取指定public构造方法
Constructor<Person> constructor = personClass.getConstructor(String.class, Integer.class);
System.out.println(constructor);
// 获取所有构造方法
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
// 获取指定的构造方法
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
System.out.println(declaredConstructor);
}
}
|
使用反射获取的构造方法创建对象
使用Constructor类中的方法: T newInstance(Object...initargs),参数传递对应对象初始值,如果获取到的是无参构造,则参数不传递,否则传递对应的值,该方法返回一个对应类的对象
| Java |
|---|
| public class Test02 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class, Integer.class);
Person person = declaredConstructor.newInstance("张三", 16);
System.out.println(person);
}
}
|
如果获取到的是无参构造,则可以直接使用Class对象.newInstance(),简写为如下的代码:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13 | public class Test03 {
public static void main(String[] args) throws Exception{
// 无参构造默认反射方式
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
System.out.println(person);
// 简写为
Person person1 = personClass.newInstance();
System.out.println(person);
}
}
|
Note
但是,上面的简写形式已经被弃用(修饰为@Deprecated),不过依旧可以使用
如果获取到的是私有构造方法,则需要使用Constructor的父类AccessibleObject中的方法void setAccessible(boolean flag)将私有构造方法的权限修改为public,参数有两个值:true代表修改,false表示不修改
| Java |
|---|
| public class Test04 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
Constructor<Person> personConstructor = personClass.getDeclaredConstructor(String.class);
// 修改权限
personConstructor.setAccessible(true);
Person person = personConstructor.newInstance("张三");
System.out.println(person);
}
}
|
上面获取私有构造方法并创建对象也被称为暴力反射
获取类对象的成员方法
获取类对象的成员方法一共有四种方法:
- 获取类对象中所有
public成员方法:使用Class类中的方法:Method[] getMethods(),该方法返回一个成员方法类的对象 - 获取类对象中指定的
public成员方法:Class类中的方法:Method getMethod (String name, Class<?>... parameterTypes),该方法的第一个参数为指定的public成员方法名,第二个参数为指定的public成员方法参数类型对应的类对象,返回一个成员方法类的对象。如果指定的public成员方法没有参数,则第二个参数可以不传递任何内容 - 获取类对象中所有成员方法(包括
public、private和protected):Method[] getDeclaredMethods(),该方法返回一个成员方法类的对象 - 获取类对象中指定的成员方法(包括
public和private):Method getDeclaredMethod (String name, Class<?>... parameterTypes),该方法的参数为指定的成员方法的参数,返回一个成员方法类的对象。如果指定的public成员方法没有参数,则第二个参数可以不传递任何内容
Note
需要注意,getMethods方法会获取到本类和其父类的所有public方法,但是getDeclaredMethods方法只会获取到本类中的private、public和protected方法
例如下面的代码:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | public class Test05 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
// 获取所有public方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
// 获取指定的public方法
Method method = personClass.getMethod("setName", String.class);
System.out.println(method);
// 获取所有的方法
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
// 获取指定的方法
Method walk = personClass.getDeclaredMethod("walk");
System.out.println(walk);
}
}
|
使用反射获取的成员方法
使用成员方法类对象调用Object类中的方法Object invoke(Object obj, Object... args)可以使用获取到的成员方法,第一个参数传递成员方法所在类的对象,第二个参数传递获取到的成员方法参数对应的值,该方法返回一个Object对象。该方法的返回值根据调用对象对应的方法是否有返回值决定,如果调用对象对应的方法有返回值,则与调用对象对应的方法的值相同,否则为null
| Java |
|---|
| public class Test06 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
Method setName = personClass.getDeclaredMethod("setName", String.class);
Method getName = personClass.getDeclaredMethod("getName");
Object set = setName.invoke(person, "张三");
Object get = getName.invoke(person);
System.out.println(get);
}
}
|
如果需要操作类对象中的私有方法,与私有构造方法一样,需要使用AccessibleObject中的方法void setAccessible(boolean flag)将私有构造方法的权限修改为public
| Java |
|---|
| public class Test06 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
Method declaredMethod = personClass.getDeclaredMethod("walk");
declaredMethod.setAccessible(true);
declaredMethod.invoke(person);
}
}
|
获取类对象的成员属性
获取类对象的构造方法一共有四种方法:
- 获取类对象中所有
public成员属性:使用Class类中的方法:Field[] getFields(),该方法返回一个成员属性类的对象 - 获取类对象中指定的
public成员属性:Class类中的方法:Field getField(String name),该方法的参数为指定的public成员属性名,返回一个成员属性类的对象 - 获取类对象中所有成员属性(包括
public和private):Field[] getDeclaredFields(),该方法返回一个成员属性类的对象 - 获取类对象中指定的成员属性(包括
public、private和protected):Field getDeclaredField(String name),该方法的参数为指定的public成员属性名,返回一个成员属性类的对象
例如:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | public class Test07 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
// 获取所有public成员属性
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
// 获取指定的public成员属性
Field age = personClass.getField("age");
System.out.println(age);
// 获取所有成员属性
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
// 获取指定成员属性
Field declaredField = personClass.getDeclaredField("name");
System.out.println(declaredField);
}
}
|
使用反射获取的成员属性
使用成员属性类对象调用Object中的方法:void set(Object obj, Object value)为获取到的成员属性赋值,第一个参数传递成员属性所在类的对象,第二个参数传递属性值
使用成员属性类对象调用Object中的方法:Object get(Object obj)获取指定成员属性的值,参数传递成员属性所在类的对象,该方法返回一个Object对象,该对象中的值即为成员属性的值
| Java |
|---|
| public class Test08 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
Field age = personClass.getField("age");
age.set(person, 18);
Object o = age.get(person);
System.out.println(o);
}
}
|
同样,如果是私有成员,则需要使用AccessibleObject中的方法void setAccessible(boolean flag)将私有构造方法的权限修改为public
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12 | public class Test08 {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
Field declaredField = personClass.getDeclaredField("name");
declaredField.setAccessible(true);
declaredField.set(person, "张三");
Object o1 = declaredField.get(person);
System.out.println(o1);
}
}
|
反射实用案例
在配置文件中,配置类的全限定名,以及配置一个方法名,通过解析配置文件,让配置好的方法执行起来,配置文件的内容如下:
| Properties |
|---|
| className=包名.Person
methodName=walk
|
步骤:
- 创建
properties配置文件,配置信息,需要注意,这个配置文件不能直接放到模块或者项目下,否则在out文件夹中不存在该文件,最常见的做法是在指定目录下创建一个名为resources文件夹,并将这个文件夹标记为Resources Root,然后将配置文件放在这个文件夹内部 - 读取配置文件,解析配置文件。读取配置文件可以使用
properties集合中的load方法,解析配置文件时不建议直接在创建IO流对象时传递properties文件的地址,这样导致该地址为死地址,而且out文件夹下不会存在resources文件夹。推荐方法:因为配置文件也属于文件,在Java中加载该文件时会产生对应的对象,使用ClassLoader获取当前类的类加载器对象,再使用该对象调用getResourceAsStream ("配置文件名")方法获取InputStream对象。这种方式会自动扫描resources下的文件,可以简单理解为扫描out路径下的配置文件 - 根据解析出来的
className,创建Class对象 - 根据解析出来的
methodName,获取对应的方法 - 执行方法
IDEA中「将这个文件夹标记为Resources Root」的步骤图:

如果上面的方式中没有显示Resources Root,则可以考虑下面的步骤:

参考代码如下:
| Properties |
|---|
| // 配置文件
className=com.epsda.advanced.test_reflect_exercise.Person
methodName=walk
|
测试:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 | // 自定义类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
public void walk() {
System.out.println("人在行走");
}
}
// 测试
package com.epsda.advanced.test_reflect_exercise;
import org.junit.Test;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* ClassName: Test09
* Description: 测试
*
* @author 憨八嘎
* @version 1.0
*/
public class Test09 {
@Test
public void method () throws Exception{
ClassLoader classLoader = Test09.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties");
// 读取配置文件
Properties properties = new Properties();
properties.load(resourceAsStream);
// 根据key取出其中的值
String methodName = properties.getProperty("methodName");
String className = properties.getProperty("className");
// 创建Class对象
Class<?> aClass = Class.forName(className);
Object o = aClass.newInstance();
Method declaredMethod = aClass.getDeclaredMethod(methodName);
declaredMethod.invoke((Person)o);
}
}
输出结果:
人在行走
|
目录结构:

注解
介绍
在Java中,注解也是一种引用数据类型,与类、接口、枚举和Record类同层次
注解常见的作用如下:
- 说明:对代码进行说明,生成doc文档(API文档)
- 检查:检查代码是否符合条件,例如:
@Override、@FunctionalInterface - 分析:对代码进行分析,起到了代替配置文件的作用
JDK中常见的注解:
@Override:检测此方法是否为重写方法 - JDK5版本,支持父类的方法重写
- JDK6版本,支持接口的方法重写
@Deprecated:表示方法已经过时,不推荐使用,但是依旧可以使用 @SuppressWarnings:消除警告,例如消除所有警告:@SuppressWarnings("all")
在IDEA中,一般被警告的方法默认会有黄色底色,例如下图:

定义注解和属性
在Java中,可以使用下面的格式定义注解:
| Java |
|---|
| public @Interface 注解名 {
// 属性
}
|
在注解体内的属性,本质是抽象方法,但是在使用时,与成员属性相同,使用成员属性名=值的方式
属性的定义有两种方式:
数据类型 属性名():定义一个没有默认值的属性,使用注解时就必须赋值 数据类型 属性名() default 值:定义一个有默认值的属性,使用注解时可以不需要赋值
可以作为属性的类型:
- 所有基本数据类型
String类型 - 枚举类型
- 注解类型
Class类型 - 上面所有类型的一维数组(不可以是二维数组)
例如:
| Java |
|---|
| public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}
|
注解的使用
使用注解本质就是为每一个属性(抽象方法)赋值,一般使用位置有下面几种:
- 类名上
- 方法上
- 成员变量上
- 局部变量上
- 参数位置
使用格式如下:
- 普通属性:
@注解名(属性名 = 值, 属性名 = 值...) - 属性中有数组:
@注解名(属性名 = {元素1,元素2...})
注解使用时需要注意:
- 空注解(注解中没有任何的属性)可以直接使用
- 不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解
- 使用注解时,如果此注解中有属性没有默认值,则注解中对应的属性一定要赋值。如果有多个属性,用
,隔开;如果注解中的属性值有默认值,那么不用显示写,也不用重新赋值 - 如果注解中的属性有数组,使用
{} - 如果注解中只有一个属性,并且属性名叫
value,那么使用注解的时候,属性名不用写,直接写值(包括普通类型和数组)
例如:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | // 自定义注解
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}
// 测试
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}
|
解析注解
解析注解即为取出注解对应属性的值,注解涉及的接口是:AnnotatedElement接口,实现类有: AccessibleObject、Class、Constructor、Executable、Field、Method、Package、Parameter
解析思路如下:
- 判断指定位置上有没有使用指定的注解:使用方法:
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass),如果存在注解则返回为true,否则返回false - 如果有,则获取指定的注解:使用方法:
getAnnotation(Class<T> annotationClass) - 通过注解获取指定的值
例如:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | // 自定义注解
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}
// 自定义类
@Book(bookName = "寓言故事",author = {"张三","李四"},price = 10,count = 20)
public class BookShelf {
}
// 测试
public class Test01 {
public static void main(String[] args) {
//1.获取BookShelf的class对象
Class<BookShelf> bookShelfClass = BookShelf.class;
//2.判断bookShelf上有没有Book注解
boolean b = bookShelfClass.isAnnotationPresent(Book.class);
//3.判断,如果b为true就获取
if (b) {
Book book = bookShelfClass.getAnnotation(Book.class);
System.out.println(book.bookName());
System.out.println(Arrays.toString(book.author()));
System.out.println(book.price());
System.out.println(book.count());
}
}
}
|
上面的代码没有在控制台中打印运行结果,原因是注解并没有在内存中出现,而class文件在内存中运行,所以导致方法无法获取到对应的注解
元注解
元注解也是注解,这个注解用来管理其他注解,一般管理下面的方面:
- 控制注解的使用位置
- 控制注解是否能在类上使用
- 控制注解是否能在方法上使用
- 控制注解是否能在构造上使用等
- 控制注解的生命周期(加载位置)
- 控制注解是否能在源码中出现
- 控制注解是否能在
class文件中出现 - 控制注解是否能在内存中出现
使用元注解:
- 注解
@Target:控制注解的使用位置,其属性是个枚举数组ElementType[] value();,枚举的成员可以类名直接调用。常见的成员有: TYPE:控制注解能使用在类上 FIELD:控制注解能使用在属性上 METHOD:控制注解能使用在方法上 PARAMETER:控制注解能使用在参数上 CONSTRUCTOR:控制注解能使用在构造上 LOCAL_VARIABLE:控制注解能使用在局部变量上
- 注解
@Retention:控制注解的生命周期(加载位置),其属性是一个枚举对象RetentionPolicy,常见的成员有: SOURCE:控制注解能在源码中出现(默认) CLASS:控制注解能在class文件中出现 RUNTIME:控制注解能在内存中出现
Note
需要注意,@Target如果不指定属性,默认是全部可用
使用元注解就可以解决前面解析注解部分无法读取到注解的问题:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | @Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
//书名
String bookName();
//作者
String[] author();
//价格
int price();
//数量
int count() default 10;
}
// 测试
public class Test01 {
public static void main(String[] args) {
//1.获取BookShelf的class对象
Class<BookShelf> bookShelfClass = BookShelf.class;
//2.判断bookShelf上有没有Book注解
boolean b = bookShelfClass.isAnnotationPresent(Book.class);
//3.判断,如果b为true,就获取
if (b){
Book book = bookShelfClass.getAnnotation(Book.class);
System.out.println(book.bookName());
System.out.println(Arrays.toString(book.author()));
System.out.println(book.price());
System.out.println(book.count());
}
}
}
输出结果:
寓言故事
[张三, 李四]
10
20
|
枚举
基本使用
枚举属于五大引用数据类型中的一种:类、数组、接口、注解、枚举
定义枚举格式如下:
在Java中,所有枚举的父类都是Enum
枚举的特点如下:
- 每一个枚举都是
static final,但是定义枚举是不能显示写出 - 每一个枚举由逗号分隔
- 写完所有的枚举值之后,最后一个枚举后方需要加
; - 枚举值名字最好大写
- 使用时使用枚举类名直接调用枚举成员
- 枚举中的成员都是当前枚举类类型的对象
- 枚举类中的构造方法都是
private修饰
基本使用如下:
| Java |
|---|
| public enum Status {
RUNNING,
WAITING,
STOPPED;
}
|
如果想为每一个枚举赋值,可以在枚举类中定义成员和构造方法,并对外提供获取方法就可以获取到每一个枚举值对应的值
| Java |
|---|
| public enum Status {
RUNNING("运行"),
WAITING("等待"),
STOPPED("暂停");
private String name;
private Status(String name) {
this.name = name;
}
}
|
测试如下:
| Java |
|---|
| public class Test {
public static void main(String[] args) {
System.out.println(Status.RUNNING);
System.out.println(Status.RUNNING.getName());
}
}
输出结果:
RUNNING
运行
|
枚举类构造方法与反射
实际上,在Enum类中也提供了一个构造方法,如下:
| Java |
|---|
| protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
|
因为所有的枚举类都默认继承自Enum类,所以在自定义枚举类中使用显式提供的构造方法时理论上需要显式调用父类的构造方法初始化父类的成员,但是在枚举部分不需要是因为编译器已经自动提供了,例如可以理解Status枚举类的构造方法如下:
| Java |
|---|
| // 编译器生成的实际构造函数
private Status(String enumName, int ordinal, String name) {
super(enumName, ordinal); // 调用父类Enum的构造函数
this.name = name;
}
|
对应的,在反射时获取该构造方法就需要显式指定并且保证用于初始化父类成员的类型在子类成员类型之前:
| Java |
|---|
| // 获取Status枚举类的构造方法
Constructor<Status> constructor = Status.class.getDeclaredConstructor(String.class, int.class, String.class);
|
因为构造方法是私有的,接下来为了能够访问还需要设置访问权限:
| Java |
|---|
| constructor.setAccessible(true);
|
接下来,通过反射创建一个自定义枚举类对象:
| Java |
|---|
| Status status2 = constructor.newInstance("WAITING", 1, "警告");
|
此时运行代码就会看到编译器给出异常信息:
| Java |
|---|
| Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller
at java.base/java.lang.reflect.Constructor.newInstance
at com.epsda.advanced.test_Enum.Test01.main
|
出现这个问题的原因就是在Constructor类的newInstance()方法中,源码如下:
| Java |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
Class<?> caller = override ? null : Reflection.getCallerClass();
return newInstanceWithCaller(initargs, !override, caller);
}
T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller)
throws InstantiationException, IllegalAccessException,
InvocationTargetException
{
if (checkAccess)
checkAccess(caller, clazz, clazz, modifiers);
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(args);
return inst;
}
|
在newInstance()方法中调用了newInstanceWithCaller(),而该方法中抛出异常的逻辑为:
| Java |
|---|
| if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
|
在这个逻辑中的(clazz.getModifiers() & Modifier.ENUM) != 0就是表示如果当前枚举类的修饰符中包含ENUM,就抛出异常
所以,在Java中,不能使用反射获取到枚举类的构造方法创建枚举类对象
枚举中的常用方法
| 方法名 | 说明 |
String toString() | 返回枚举值的名字 |
values() | 返回所有与的枚举值 |
valueOf(String str) | 将一个字符串转成已有的枚举类型 |
ordinal() | 获取枚举成员的索引位置 |
compareTo(E o) | 比较两个枚举成员在定义时的顺序 |
Note
需要注意的是,values()方法实际上是由Java编译器自动生成的,而不是在Enum类中定义的
例如下面的代码: