⭐️前言⭐️
本篇文章主要介绍Java语法中的反射与枚举部分。
博客主页: 【如风暖阳】
精品Java专栏【JavaSE】、【备战蓝桥】、【JavaEE初阶】、【MySQL】、【数据结构】
欢迎点赞 收藏 ⭐留言评论 私信必回哟本文由 【如风暖阳】 原创,首发于 CSDN
博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言
博客中涉及源码及博主日常练习代码均已上传码云(gitee)、GitHub
Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
Java程序运行前,首先会通过编译将Java代码生成.class文件;程序运行过程中使用某个类时,如果该类还未被加载到内存中,系统会将该类的.class字节码文件读入内存,同时JVM会产生一个java.lang.Class对象代表该.class字节码文件。
通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类所包括的方法、构造器和属性等,反射的工作原理就是通过这些对象来执行实际的功能,例如调用方法、创建实例等。
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量/类的属性 |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
方法 | 用途 |
---|---|
getClassLoader() | 获得类的加载器 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的) |
forName(String className) | 根据类名返回类的对象 |
newInstance() | 创建类的实例 |
getName() | 获得类的完整路径名字 |
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象(指定参数) |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象(指定参数) |
getDeclaredFields() | 获得所有属性对象 |
方法 | 用途 |
---|---|
getConstructor(Class…> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class…> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
方法 | 用途 |
---|---|
getMethod(String name, Class…> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class…> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
注意:在方法中声明“Declared”后,就可以获得对象私有的属性和方法,但是必须将该对象的accessible=true,否则抛出IllegalAccessException异常。
首先我们在一个包下写一个类,该类中有私有和公有的各类成员变量和方法,然后我们利用反射来获取该类的方法、构造器或属性等。
package reflect;
public class Person {
//私有属性name
private String name = "张三";
//公有属性age
public int age;
//不带参数的构造方法
public Person() {
System.out.println("Person()");
}
//带参数的私有构造方法
private Person(String name, int age) {
this.age = age;
this.name = name;
System.out.println("Person(String,name)");
}
private void eat() {
System.out.println("I am eating");
}
public void sleep() {
System.out.println("I am sleeping");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
想获取类的方法、构造器或属性等,必须先获取该类的Class对象;
且一个类只有一个Class对象,所以代码示例中的打印结果全为true.
//1.使用 Class.forName() 静态方法,需要知道类的全路径名
Class<?> c1=Class.forName("reflect.Person");
//2.使用.class方法
Class<?> c2=Person.class;
//3.使用类对象的getClass()方法
Person person=new Person();
Class<?> c3=person.getClass();
System.out.println(c1==c2);
System.out.println(c1==c3);
System.out.println(c2==c3);
//
true
true
true
Class<?> c=Person.class;
//1.通过Class对象的newInstance()方法
Person person1=(Person) c.newInstance();
//2.通过 Constructor 对象的 newInstance() 方法
Constructor<?> constructor= c.getConstructor();
Person person2=(Person) constructor.newInstance();
Class<?> c=Person.class;
//获取name属性
Field field=c.getDeclaredField("name");
//获取自身和父类的公有属性(不包括私有的)
Field[] fields= c.getFields();
//获取自身的公有和私有属性
Field[] declaredFields = c.getDeclaredFields();
//获取function方法,参数为String
Method method= c.getMethod("function", String.class);
//获取自身和父类的公有方法(不包括私有的)
Method[]methods= c.getMethods();
//获取自身的公有和私有方法
Method[] declaredMethods = c.getDeclaredMethods();
//获取无参的构造方法
Constructor<?> constructor=c.getConstructor();
//获取参数为String和int的构造方法
Constructor<?> constructor1=c.getDeclaredConstructor(String.class,int.class);
//获取自身和父类的构造方法(不包括私有的)
Constructor<?>[]constructors=c.getConstructors();
public static void main(String[] args) {
try {
Class<?> c=Person.class;
//通过构造方法创建对象
Constructor<?> constructor= c.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Person person=(Person) constructor.newInstance("王五",18);
System.out.println(person);
System.out.println("=====================");
//字段属性
Field field=c.getDeclaredField("name");
field.setAccessible(true);
field.set(person,"李四");
System.out.println(person);
System.out.println("=====================");
//方法属性
Method method= c.getDeclaredMethod("function", String.class);
method.setAccessible(true);
method.invoke(person,"我是通过反射给你传参的");//执行方法
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
//
Person(String,name)
Person{name='王五', age=18}
=====================
Person{name='李四', age=18}
=====================
我是通过反射给你传参的
优点:
缺点:
反射更容易出现运行时错误。
使用显式的类和接口,编译器能帮我们做类型检查,但使用反射需要运行时才知道类型,此时编译器也爱莫能助。
反射性能不高。
反射是一种解释操作,在访问字段和调用方法前,需要查找到对应的 Field 和 Method,性能要低一些。
因此反射机制主要应用在对灵活性和拓展性要求较高的系统框架上。
反射会带来安全性问题。
反射可以随意修改私有属性和访问私有方法,破坏了类的封装,可能导致逻辑错误或者存在安全隐患。
枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来
public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;
但是这种常量举例有不好的地方,比如:碰巧有个数字1,但是它可能会误认为是RED,现在我们可以直接用枚举类型来进行组织,这样就有了区分
public enum TestEnum {
RED,BLACK,GREEN;
}
本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了
这个类。
1.switch语句
public enum TestEnum {
RED,BLACK,GREEN,WHITE;
public static void main(String[] args) {
TestEnum testEnum=TestEnum.BLACK;
switch (testEnum) {
case RED: System.out.println("red");
break;
case BLACK: System.out.println("black");
break;
case WHITE: System.out.println("WHITE");
break;
case GREEN: System.out.println("black");
break;
default:
break;
}
}
}
//
black
Process finished with exit code 0
2.常用方法
Enum
类的常用方法
方法名称 | 描述 |
---|---|
values() | 以数组形式返回枚举类型的所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举实例 |
compareTo() | 比较两个枚举成员在定义时的顺序 |
示例:
public enum TestEnum {
RED, BLACK, GREEN, WHITE;
public static void main(String[] args) {
//values()方法
TestEnum[] testEnums = TestEnum.values();
for (int i = 0; i < testEnums.length; i++) {
//ordinal()方法
System.out.println(testEnums[i] + " 序号: " + testEnums[i].ordinal());
}
System.out.println("==============");
//valueOf()方法
TestEnum testEnum = TestEnum.valueOf("GREEN");
System.out.println(testEnum);
System.out.println("==============");
TestEnum testEnum1 = TestEnum.BLACK;
TestEnum testEnum2 = TestEnum.RED;
//compareTo()方法
System.out.println(testEnum1.compareTo(testEnum2));
System.out.println(BLACK.compareTo(RED));
System.out.println(RED.compareTo(BLACK));
}
}
//
RED 序号: 0
BLACK 序号: 1
GREEN 序号: 2
WHITE 序号: 3
==============
GREEN
==============
1
1
-1
Process finished with exit code 0
事实上在Java中,枚举就是一个类,所以我们在定义枚举类时,如果枚举对象有参数,需要提供相应的构造函数。
枚举的构造方法默认是私有的
public enum TestEnum {
RED("red",2), BLACK("black",3),
GREEN("green",4), WHITE("white",5);
public String color;
public int ordinal;
TestEnum(String color, int ordinal) {
this.color = color;
this.ordinal = ordinal;
}
}
我们了解到,任何一个类,哪怕其属性或者方法是私有的,我们都可以通过反射来获取到该实例对象,枚举的构造方法也是私有的,我们可以尝试是否可以通过反射来获取到。
我们还以2.2中最后的枚举类为例:
public enum TestEnum {
RED("red", 2), BLACK("black", 3),
GREEN("green", 4), WHITE("white", 5);
public String color;
public int ordinal;
TestEnum(String color, int ordinal) {
this.color = color;
this.ordinal = ordinal;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> c = TestEnum.class;
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
TestEnum testEnum = (TestEnum) constructor.newInstance("海雾蓝", 9);
System.out.println(testEnum);
}
}
//
Exception in thread "main" java.lang.NoSuchMethodException: TestEnum.<init>(java.lang.String, int)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at TestEnum.main(TestEnum.java:19)
Process finished with exit code 1
运行后报错 java.lang.NoSuchMethodException: TestEnum.
,意思是没有对应的构造方法,但我们所提供的枚举类的构造方法就是两个参数String
和int
。
这是因为所有的枚举类都默认继承于 java.lang.Enum
,所以在构造子类时,要先帮助父类进行构造,Enum的源码:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
所以我们需要提供四个参数来完成构造
public enum TestEnum {
RED("red", 2), BLACK("black", 3),
GREEN("green", 4), WHITE("white", 5);
public String color;
public int ordinal;
TestEnum(String color, int ordinal) {
this.color = color;
this.ordinal = ordinal;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> c = TestEnum.class;
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class,String.class, int.class);
constructor.setAccessible(true);
TestEnum testEnum = (TestEnum) constructor.newInstance("海雾蓝", 9);
System.out.println(testEnum);
}
}
//
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at TestEnum.main(TestEnum.java:21)
Process finished with exit code 1
此时仍然报错,但报错原因变为了java.lang.IllegalArgumentException: Cannot reflectively create enum objects
,我们从源码中查找原因
也就是说,枚举在这里被过滤了,不能通过反射获取枚举类的实例。
⭐️最后的话⭐️
总结不易,希望uu们不要吝啬你们的哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正