反射和枚举
一:反射
1 概念
Java的反射机制是在运行时,对于任意一个类都能知道这个类的所有属性和方法;对于任意一个对象都能够调用它的任意方法和属性;特点是可以动态获取信息及动态调用对象。
2 在什么时候会用到反射机制?
2.1 我们在编写Java程序时经常会遇到两种类型:一种是运行时类型,一种是编译时类型,例如Chinese c = new Book();c在编译时类型为Chinese,而运行时的类型则是Book,因此程序在运行过程中需要发现对象和类的真实关系,而通过反射机制就能够判断出该对象及类到底属于哪些类。
2.2 在应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或者是只对系统应用开放,那么如果想要访问其私有的方法或者成员,则可以利用反射机制来获取所需信息;
2.3 亦例如在Spring中,将所有类Bean交给spring容器管理,无论是XML配置Bean还是注解配置,当从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,当需要创建哪些Bean,spring就会根据这些信息来创建哪些类。
3 反射相关类
3.1 Class类及获得Class对象的三种方式
Class类代表类的实体,在运行的java应用程序中表示类和接口;
三种方式通过一下示例来进行说明:
class Student {
private String name = "Lee";
public int age = 18;
public Student() {
System.out.println("Student()");
}
private Student(String name,int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,int)");
}
private void eat() {
System.out.println("eat");
}
private void sleep() {
System.out.println("sleep");
}
private void function(String str){
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
Student s1 = new Student();
//1、通过getClass获取Class对象;
Class c1 = s1.getClass();
//方式1:直接通过.class的方式得到,该方法安全可靠,程序性能高,
//说明任何一个类都有一个隐含的静态成员变量class
Class c2 = Student.class;
//方式2:通过Class对象的forName()静态方法来获取,用的最多,但可以抛出
//ClassNotFoundException异常
Class c3 = null;
try {
//此处是类的全路径
c3 = Class.forName("TestDemo.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
//输出结果:都是true;
//因为一个类在JVM中只会有一个Class实例,因此比较后都是true。
}
}
使用的最多的是方式2:通过Class对象的forName()静态方法来获取,用的最多,但可以抛出ClassNotFoundException异常。
3.2 Field类、Constructor类、Method类及反射的使用
依旧反射上面的Student类,将反射的逻辑写到另外的类当中进行理解。代码如下:
package Reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created with IntelliJ IDEA
* Description:
*
* @author :Lee
* @description:TODO
* @date :Created in 2022/1/10 21:13
*/
public class ReflectClassDemo {
//创建对象
public static void reflectNewInstance() {
try {
Class<?> c3 = Class.forName("Reflect.Student");
Object o = c3.newInstance();
Student student = (Student) o;
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void reflecctPrivateConstructor() {
try {
Class<?> c3 = Class.forName("Reflect.Student");
Constructor<?> constructor = c3.getDeclaredConstructor(String.class,int.class);
//私有属性或者方法 需要设置
constructor.setAccessible(true);
Object stu = constructor.newInstance("lee",25);
Student student = (Student) stu;
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
}
//反射私有属性
public static void reflecctPrivateField() {
try {
Class<?> c3 = Class.forName("Reflect.Student");
Field field = c3.getDeclaredField("Leeeeee");
field.setAccessible(true);
Student student = (Student) c3.newInstance();
//设置student这个字段值为bit
field.set(student,"Lee123");
System.out.println(student);
} catch (Exception e) {
}
}
//反射私有方法
public static void reflecctPrivateMethod() {
try {
Class<?> classStudent = Class.forName("Reflect.Student");
Method methodStudent = classStudent.getDeclaredMethod("function",String.class);
System.out.println("私有方法的方法名为: "+methodStudent.getName());
//私有的一般都要加
methodStudent.setAccessible(true);
Object objectStudent = classStudent.newInstance();
Student student = (Student) objectStudent;
//反射调用方法 是使用 invoke代表:调用student这个对象methodStudent表示的方法,并且传参
methodStudent.invoke(student,"我是给私有的function函数传的参数");
} catch (Exception e) {
}
}
}
4 反射的优缺点
4.1 优点
- 对于任意的类或者对象,都能知道这个类的所有属性或者方法或者都能够调用它的任意一个方法;
- 增加程序的灵活性和扩展性,降低耦合性,提高程序的自适应能力。
4.2 缺点
- 使用反射会导致程序效率运行低下;
- 反射技术绕过了源代码的技术,因此会带来维护问题,总的来说反射代码比相应的直接代码更复杂。
二:枚举
1 背景意义
枚举的主要功能是将一组常量组织起来,以前我们定义常量通常是以下方式:
public static int final GREEN = 1;
public static int final BLACK = 2;
public static int final RED = 3;
但是以上写法错误率比较高,例如在写代码过程中碰巧有数字1、2、3出现时,可能会误认为是GREEN、BLACK、RED,因此出错的可能性比较大,而用枚举类型则避免了这样的错误,如下:
public enum TestEnum {
GREEN,BLACK,RED;
}
常用的场景有消息类型、颜色的划分、状态机等。
2 使用
2.1 switch语句
示例如下:
public enum TestEnum {
GREEN,BLACK,RED;
public static void main(String[] args) {
TestEnum testEnum = TestEnum.BLACK;
switch (testEnum) {
case GREEN:
System.out.println("绿色");
break;
case BLACK:
System.out.println("黑色");
break;
case RED:
System.out.println("红色");
break;
default:
break;
}
}
}
switch语句比较简单,不做过多的阐述。
3 常用方法
我们还是用举例的方式来主要讲解四种常用方法。代码如下:
public enum TestEnum {
GREEN,BLACK,RED;
public static void main(String[] args) {
TestEnum[] testEnums = TestEnum.values();
//以数组形式返回枚举类型的所有成员
for (int i = 0; i< testEnums.length;i++) {
System.out.println(testEnums[i] + " " + testEnums[i].ordinal());//获取枚举成员的索引位置
}
System.out.println("====================");
System.out.println(TestEnum.valueOf("BLACK"));//将普通字符串转换为枚举实例
System.out.println("====================");
//compareTo()主要用来比较两个枚举成员在定义时的顺序
TestEnum testEnum1 = TestEnum.RED;
TestEnum testEnum2 = TestEnum.GREEN;
System.out.println(testEnum1.compareTo(testEnum2));
System.out.println(RED.compareTo(BLACK));
System.out.println(GREEN.compareTo(BLACK));
System.out.println(GREEN.compareTo(RED));
}
public static void main1(String[] args) {
TestEnum testEnum = TestEnum.BLACK;
switch (testEnum) {
case GREEN:
System.out.println("绿色");
break;
case BLACK:
System.out.println("黑色");
break;
case RED:
System.out.println("红色");
break;
default:
break;
}
}
}
输出结果:
注意点:
- 需要注意的是compareTo()比较的是两个枚举成员的顺序或者说是这两之间的偏移位置;
- 当枚举对象有参数时,需要提供相应的构造函数;
- 枚举的构造函数默认是私有的。
总结:
4 枚举的优缺点
三:枚举和反射的关系(重点)
在此之前,先抛一个疑问,我们能不能通过反射来获取枚举类的实例对象呢?
下面我将举一个例子来看一下会出现什么问题。
public enum TestEnum {
// GREEN,BLACK,RED;
RED("red", 1), BLACK("black", 2), GREEN("greeb", 3);
private String name;
private int key;
//枚举的构造方法,默认是私有的
TestEnum(String name, int key) {
this.name = name;
this.key = key;
}
public static TestEnum getEnumKey(int key) {
for (TestEnum t : TestEnum.values()) {
if (t.key == key) {
return t;
}
}
return null;
}
public static void reflectPrivateConstructor() {
try {
//1.获取Class对象
Class<?> c = Class.forName("Enum.TestEnum");
//2.获取构造方法
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
//3.获取对象的实例
TestEnum testEnum = (TestEnum) constructor.newInstance("Lee", 18, "12", 456);
System.out.println(testEnum);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
reflectPrivateConstructor();
}
}
运行完可以发现抛出了异常:
异常解读: 抛出的异常是说我们没有对应的构造方法!
我们可能比较惊讶,明明我们已经写了构造方法啊,并且两个参数一个是String和int,问题到底出在了哪里?
需要注意的是: 所有的枚举类,都是默认继承java.lang.Enum,继承了父类除构造函数外的所有东西,并且子类要帮助父类进行构造,而上面我们所写的类并没有帮助父类构造,枚举比较特殊,虽然我们写了两个参数,但默认还添加了两个参数,Enum类的源码如下:
也就是说我们自己构造了两个参数,但是默认后面还会给两个参数,一共有四个参数,因此以上异常的原因就找出来了。
接下来我们对代码稍作修改:
截止目前我们再次运行代码,发现依旧有异常抛出!!!
我们来看一下什么异常:
异常解读:
此次异常表示newInstance()方法阿伯错了,我们再次看一下源码为何会抛出java.lang.IllegalArgumentException这个异常?
问题显示枚举在这个地方被过滤掉了,也就是说我们并不能通过反射来获取枚举类的实例!!!
四:总结
- 枚举本身就是一个类,构造方法是私有的,且默认继承java.lang.Enum;
- 枚举可以避免反射和序列化问题。