🍁1. 反射
在程序运行时,可以动态地创建对象、调用方法、访问和修改字段,以及获取类的各种属性信息(如成员变量、方法、构造函数等),这种机制就称为反射
类名 |
用途 |
Class类 |
代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 |
代表类的成员变量 / 类的属性 |
Method类 |
代表类的方法 |
Constructor类 |
代表类的构造方法 |
🍁1.1 反射获取Class对象
获取字节码文件对象
1. Class.forName(全类名) 包名 + 类名
2. 类名.class
3. 对象名.getClass()
public class Demo1 { public static void main(String[] args) throws ClassNotFoundException { //获取字节码文件对象的三种方式 // Class.forName(全类名)包名 + 类名(常用) Class clazz1 = Class.forName("reflect.Student"); //类名.class Class clazz2 = Student.class; //对象名.getClass() Student student = new Student(); Class clazz3 = student.getClass(); System.out.println(clazz1 == clazz2); System.out.println(clazz2 == clazz3); } }
🍁1.2 反射获取构造方法
🍁1.2.1 获取构造方法的方式
方法 |
用途 |
getConstructor(Class...<?> parameterTypes) |
获得该类中与参数类型匹配的公有构造方法 |
getConstructors() |
获得该类的所有公有构造方法对象的数组 |
getDeclaredConstructor(Class...<?> parameterTypes) |
获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() |
获得该类所有构造方法对象的数组 |
使用 getConstructors()时,只会获取类中的公共的构造方法
可以看出,使用getDeclaredConstructors()时,不论构造方法的权限修饰符是什么,都可以获取到
来看获取单个构造方法的例子,在调用方法的时候,传入方法里面的参数要和需要获取的构造方法的参数一致
getConstructor 只能获取 public 修饰的构造方法,getDeclaredConstructor可以获取任意修饰符修饰的构造方法,所以如果要获取的构造方法如果不是 public 修饰的,但是使用了getConstructor获取,就会报错
public class Demo2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //获取class字节码文件对象 Class clazz = Class.forName("com.li.reflect.Student"); //获取构造方法 Constructor[] cons1 = clazz.getConstructors(); for (Constructor con : cons1) { System.out.println(con); } Constructor[] cons2 = clazz.getDeclaredConstructors(); for (Constructor con2 : cons2) { System.out.println(con2); } //获取单个的构造方法 Constructor con1 = clazz.getDeclaredConstructor(); System.out.println(con1); Constructor con2 = clazz.getDeclaredConstructor(String.class); System.out.println(con2); Constructor con3 = clazz.getDeclaredConstructor(String.class,int.class); System.out.println(con3); Constructor con4 = clazz.getDeclaredConstructor(int.class); System.out.println(con2); } }
student类:
public class Student { private String name; private int age; public Student() { } private Student(int age){ this.age = age; } protected Student(String name){ this.name = name; } public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Student(String name, int age) { this.name = name; this.age = age; } }
🍁1.2.2 获取构造方法的用途
通过反射,可以获取到构造方法的所有内容
首先第一个,就是可以获取到构造方法的权限修饰符
//获取权限修饰符,这里获取的是其对应的常量值 int modifiers = con1.getModifiers(); System.out.println(modifiers); //public 对应的常量是1
idea中的提示功能就是使用了反射的机制,获取到了构造方法的权限修饰符,这里只提示了除空参构造之外,可以使用的两种构造方法,private由于在其他类中不能调用,这里就没有显示
之后,还可以获取到构造方法的参数
//获取参数 Parameter[] parameters = con3.getParameters(); System.out.println(Arrays.toString(parameters));//[java.lang.String arg0, int arg1]
应用场景还是上面的代码提示功能
既然可以获取构造方法的所有内容,那以此来创建一个对象也是可以的:
//创建对象 Student student1 = (Student) con3.newInstance("张三", 18); System.out.println(student1); //获取到private修饰的构造方法时 con4.setAccessible(true); //表示临时取消权限校验 Student student2 = (Student) con4.newInstance(18); System.out.println(student2);
用这种方式创建对象需要注意:
传进去的参数要和获取到的构造方法的参数一致
如果获取到 private修饰的构造方法时,需要临时取消权限校验
🍁1.3 反射获取成员变量
🍁1.3.1 获取成员变量的方式
方法 |
用途 |
getField(String name) |
获得某个公有的属性对象 |
getFields() |
获得所有公有的属性对象的数组 |
getDeclaredField(String name) |
获得某个属性对象 |
getDeclaredFields() |
获得所有属性对象的数组 |
public class Demo3 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //获取字节码文件的对象 Class clazz = Class.forName("com.li.reflect.Student"); //获取所有的成员变量 Field[] fields1 = clazz.getFields(); System.out.println(Arrays.toString(fields1)); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { System.out.println(field); } //获取单个成员变量 Field res1 = clazz.getField("gender"); System.out.println(res1); Field res2 = clazz.getDeclaredField("name"); System.out.println(res2); } }
这里的方法的区别对比着获取构造方法的区别是一样的
在获取单个的成员变量时,传入的参数是要获取的变量名
🍁1.3.2 获取成员变量的用途
可以获取到成员变量的权限修饰符,名称,数据类型
//获取成员变量的权限修饰符 int modifiers = res1.getModifiers(); System.out.println(modifiers); //获取成员变量的名称 String name = res1.getName(); System.out.println(name); //获取成员变量的数据类型 Class<?> type = res1.getType(); System.out.println(type);
此外,还可以获取到当前成员变量记录的值
//获取成员变量记录的值 Student student = new Student("张三",18,"男"); //临时取消权限校验 res1.setAccessible(true); Object value = res1.get(student); System.out.println(value);
这里需要注意的是,如果获取的值是private修饰的,还是需要设置临时取消权限校验
获取到值之后,还可以进行修改
//修改 res1.set(student,"女"); System.out.println(student);
🍁1.4 反射获取成员方法
🍁1.4.1 获取成员方法的方式
方法 |
用途 |
getMethod(String name, Class...<?> parameterTypes) |
获得该类某个公有的方法 |
getMethods() |
获得该类所有公有的方法的数组,包括继承的 |
getDeclaredMethod(String name, Class...<?> parameterTypes) |
获得该类某个方法 |
getDeclaredMethods() |
获得该类所有方法的数组,不包括继承的 |
getMethods() 获取的是该类所有公有的方法的数组,包括继承的,所以显示出了很多Person类本身没有定义的方法
getDeclaredMethods()获取的是该类所有方法的数组,不包括继承的,
获取指定的方法:
在使用getDeclaredMethod获取指定方法时,第一个参数需要指定方法的名称,第二个参数需要指定方法的形参,如果是空参,那么第二个参数就空着不写
//获取指定方法 Method m = clazz.getDeclaredMethod("eat",String.class); System.out.println(m);
🍁1.4.2 获取成员方法的用途
可以获取方法的修饰符,名字,方法的形参,以及方法抛出的异常
//获取方法修饰符 int modifiers = m.getModifiers(); System.out.println(modifiers); //获取方法的名字 String name = m.getName(); System.out.println(name); //获取方法的形参 Parameter[] parameters = m.getParameters(); for (Parameter parameter : parameters) { System.out.println(parameter); } //获取方法抛出的异常 Class[] exceptionTypes = m.getExceptionTypes(); for (Class exceptionType : exceptionTypes) { System.out.println(exceptionType); }
获取到的方法也可以通过调用 invoke 方法运行
/*Object invoke(Object obj,Object...args):运行方法 参数一:用obj对象调用该方法 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值(如果没有就不写) * */ Person person = new Person(); m.setAccessible(true); //如果有返回值可以接收 Object res = m.invoke(person, "饭"); //打印返回值 System.out.println(res);
🍁2. 枚举
枚举是一种特殊的类,枚举中的每个元素都是该类的一个唯一实例,主要用途是把一组常量组织起来
在之前我们定义常量的时候是这样定义的:
public static final int RED = 1;
但是如果恰好此时有一个数字1,就可能被误认为是RED,这时就可以通过枚举来组织常量
在创建类时选择Enum类进行创建
🍁2.1 枚举的使用
Enum类中的常用方法
方法名称 |
描述 |
values() |
以数组形式返回枚举类型的所有成员 |
ordinal() |
获取枚举成员的索引位置 |
valueOf() |
将普通字符串转换为枚举实例 |
compareTo() |
比较两个枚举成员在定义时的顺序 |
public enum EnumDemo1 { RED, BLACK, YELLO, BLUE; public static void main(String[] args) { EnumDemo1[] enumDemo1s = EnumDemo1.values(); for (int i = 0; i < enumDemo1s.length; i++) { System.out.println(enumDemo1s[i] + " " + enumDemo1s[i].ordinal()); } //根据字符串返回类中的实例 EnumDemo1 res = EnumDemo1.valueOf("RED"); System.out.println(res); //比较定义顺序 System.out.println(RED.compareTo(BLUE)); } }
可以和switch语句一起使用:
public enum EnumDemo1 { RED, BLACK, YELLO, BLUE; public static void main(String[] args) { EnumDemo1 e1 = RED; switch (e1) { case RED: System.out.println("RED"); break; case BLACK: System.out.println("BLACK"); default: System.out.println("无法匹配"); } } }
🍁2.2 枚举类的构造方法
在之前提到过,枚举本身是一个类,如果想要类里面的实例对象有参数的话,需要提供相应的构造方法,并且构造方法默认是私有的
public enum EnumDemo2 { RED(0, "red"), BLACK(1, "black"), YELLO(2, "yello"), BLUE(3, "blue"); private int ordinal; private String color; EnumDemo2() { } private EnumDemo2(int ordinal, String color) { this.ordinal = ordinal; this.color = color; } }
如果将构造方法设置为除private外的其他类型的话就会报错
🍁2.3 枚举在反射中的特殊情况
还按照之前反射的方法获取构造方法并创建对象时,会发现报错了,并且给出的异常是没有找到这个构造方法,但是我们的EnumDemo2类中是存在这个方法的
自己定义的枚举类是默认继承Enum类的,会优先代用父类的构造方法,所以就需要加上父类的参数,此时就解决了第一个异常
此时通过查看newInstance的源码发现,只要是枚举类,就会报错,所以说枚举类是比较安全的