一、反射
1、什么是反射
反射允许对成员变量、成员方法和构造器的信息进行编程访问。
补充:暴力反射,非public修饰需要打开权限
setAccessible(boolean)
2、反射的作用
- 利用反射创建的对象可以无视修饰符调用类里面的内容
- 可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。
读取到什么类,就创建什么类的对象
读取到什么方法,就调用什么方法
此时当需求变更的时候不需要修改代码,只要修改配置文件即可。
简单记忆:1、获取任意一个类中的所有信息、2、结合配置文件动态创建对象。
3、学习反射到底学习什么
反射都是从class字节码文件中获取的内容。
- 如何获取class字节码文件的对象
- 利用反射如何获取构造方法(创建对象)
- 利用反射如何获取成员变量(赋值,获取值)
- 利用反射如何获取成员方法(运行)
4、获取字节码文件对象的三种方式
- Class这个类里面的静态方法forName(“全类名”)(最常用)
- 通过class属性获取 (一般更多的是当做参数进行传递)
- 通过对象获取字节码文件对象 (当我们已经有了这个类的对象时,才可以使用)
代码演示:
package com.liming.myreflect;
public class ReflectDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
/*
* 获取class对象的三种方式
* 1、Class.forName("全类名");
* 2、类名.class
* 3、对象.getClass();
* */
//1、方式一
//全类名:包名+类名
//最为常用的
Class clazz1 = Class.forName("com.liming.myreflect.Student01");
//2、方式二
//一般更多的是当做参数进行传递
Class clazz2 = Student01.class;
//3、方式三
//当我们已经有了这个类的对象时,才可以使用
Student01 stu = new Student01();
Class clazz3 = stu.getClass();
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
}
}
package com.liming.myreflect;
public class Student01 {
private String name;
private int age;
public String gender;
public Student01() {
}
public Student01(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student01{name = " + name + ", age = " + age + "}";
}
/**
* 获取
* @return gender
*/
public String getGender() {
return gender;
}
/**
* 设置
* @param gender
*/
public void setGender(String gender) {
this.gender = gender;
}
}
5、获取构造方法
规则:
1、get表示获取
2、Declared表示私有
3、最后的s表示所有,复数形式
4、如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
代码演示:
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获得整体(class字节码文件对象)
Class clazz = Class.forName("com.liming.myreflect.Student01");
//2.获取构造方法对象
//获取所有构造方法(public)
Constructor[] constructors1 = clazz.getConstructors();
for (Constructor constructor : constructors1) {
System.out.println(constructor);
}
System.out.println("=======================");
//获取所有构造(带私有的)
Constructor[] constructors2 = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors2) {
System.out.println(constructor);
}
System.out.println("=======================");
//获取指定的空参构造
Constructor con1 = clazz.getConstructor();
System.out.println(con1);
Constructor con2 = clazz.getConstructor(String.class,int.class);
System.out.println(con2);
System.out.println("=======================");
//获取指定的构造(所有构造都可以获取到,包括public包括private)
Constructor con3 = clazz.getDeclaredConstructor();
System.out.println(con3);
//了解 System.out.println(con3 == con1);
//每一次获取构造方法对象的时候,都会新new一个。
Constructor con4 = clazz.getDeclaredConstructor(String.class);
System.out.println(con4);
}
}
5.1、获取构造方法并创建对象
涉及到的方法:newInstance
代码演示:
//首先要有一个javabean类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name) {
this.name = name;
}
private Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
//测试类中的代码:
//需求1:
//获取空参,并创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.liming.myreflect.Student");
//2.获取空参的构造方法
Constructor con = clazz.getConstructor();
//3.利用空参构造方法创建对象
Student stu = (Student) con.newInstance();
System.out.println(stu);
System.out.println("=============================================");
//测试类中的代码:
//需求2:
//获取带参构造,并创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.liming.myreflect.Student");
//2.获取有参构造方法
Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
//3.临时修改构造方法的访问权限(暴力反射)
con.setAccessible(true);
//4.直接创建对象
Student stu = (Student) con.newInstance("zhangsan", 23);
System.out.println(stu);
6、获取成员变量
规则:
1、get表示获取
2、Declared表示私有
3、最后的s表示所有,复数形式
4、如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
获取成员变量并获取值和修改值
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值 |
package com.liming.myreflect;
import java.lang.reflect.Field;
public class ReflectDemo03 {
public static void main(String[] args) throws Exception {
//1、获取类的字节码文件
Class<?> clazz = Class.forName("com.liming.myreflect.Student01");
//2、获取所有公共的成员变量
Field[] fields1 = clazz.getFields();
for (Field field : fields1) {
System.out.println(field);
}
//获取所有的成员变量
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
}
//获取单个公共成员变量
Field gender = clazz.getField("gender");
System.out.println(gender);
//获取单个成员变量(所有权限)
Field name = clazz.getDeclaredField("name");
System.out.println(name);
//获取权限修饰符
int modifiers = name.getModifiers();
System.out.println(modifiers);
//获取成员变量的名字
String n = name.getName();
System.out.println(n);
//获取成员变量的数据类型
Class<?> type = name.getType();
System.out.println(type);
//获取成员变量记录的值
Student01 stu = new Student01("zhangsan", 23, "男");
name.setAccessible(true);
String value = (String) name.get(stu);
System.out.println(value);
//修改对象里面记录的值
name.set(stu, "lisi");
System.out.println(stu);
}
}
7、获取成员方法
规则:
1、get表示获取
2、Declared表示私有
3、最后的s表示所有,复数形式
4、如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
获取成员方法并运行
Object invoke(Object obj, Object… args) :运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
代码演示:
package com.liming.myreflect;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ReflectDemo03 {
public static void main(String[] args) throws Exception {
//1、获取class字节码文件对象
Class clazz = Class.forName("com.liming.myreflect.Student02");
//2、获取所有公共的成员方法(包含父类中所有的公共方法)
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("======================================");
//获取所有的成员方法(不包含父类)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
System.out.println("======================================");
//获取单个公共的成员方法
Method sleep = clazz.getMethod("sleep");
System.out.println(sleep);
System.out.println("======================================");
//获取单个成员方法
Method eat = clazz.getDeclaredMethod("eat", String.class);
System.out.println(eat);
System.out.println("======================================");
//获取方法的修饰符
int modifiers = eat.getModifiers();
System.out.println(modifiers);
//获取方法的名字
String name = eat.getName();
System.out.println(name);
//获取方法的形参
Parameter[] parameters = eat.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
//获取方法抛出的异常
Class[] exceptionTypes = eat.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
//方法运行
//参数一:用obj对象调用该方法
//参数二:调用方法传递的参数(没有就不写)
//返回值:方法的返回值(没有就不写)
Student02 stu = new Student02();
eat.setAccessible(true);
eat.invoke(stu,"鸡腿");
}
}
package com.liming.myreflect;
public class Student02 {
private String name;
private int age;
public Student02() {
}
public Student02(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public void sleep(){
System.out.println("睡觉");
}
private void eat(String something){
System.out.println("在吃"+something);
}
public String toString() {
return "Student02{name = " + name + ", age = " + age + "}";
}
}
8、四道面试题
你觉得反射好不好?好,有两个方向
第一个方向:无视修饰符访问类中的内容。但是这种操作在开发中一般不用,都是框架底层来用的。
第二个方向:反射可以跟配置文件结合起来使用,动态的创建对象,动态的调用方法。
8.1、泛型擦除
集合中的泛型只在java文件中存在,当编译成class文件之后,就没有泛型了。
代码演示:
public class ReflectDemo03 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.创建集合对象
ArrayList<Integer> list = new ArrayList<>();
list.add(123);
//list.add("asd");
//2.利用反射运行add方法去添加字符串
//因为反射使用的是class字节码文件
//获取class对象
Class clazz = list.getClass();
//获取add方法对象
Method add = clazz.getMethod("add", Object.class);
//运行方法
add.invoke(list,"asd");
//打印集合
System.out.println(list);
}
}
8.2、修改字符串的内容
字符串不可以修改的原因?
字符串,在底层是一个byte类型的字节数组,名字叫做value
private final byte[] value;
真正不能被修改的原因:final和private
final修饰value表示value记录的地址值不能修改。
private修饰value而且没有对外提供getvalue和setvalue的方法。所以,在外界不能获取或修改value记录的地址值。
如果要强行修改可以用反射:
代码演示:
public class ReflectDemo04 {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
String s = "abc";
String ss = "abc";
// private final byte[] value= {97,98,99};
// 没有对外提供getvalue和setvalue的方法,不能修改value记录的地址值
// 如果我们利用反射获取了value的地址值,也是可以修改的
// final修饰的value真正不可变的value数组的地址值,里面的内容利用反射还是可以修改的,比较危险
//1.获取class对象
Class clazz = s.getClass();
//2.获取value成员变量(private)
Field field = clazz.getDeclaredField("value");
//JDK高版本已经屏蔽了这种操作,低版本还是可以的
field.setAccessible(true);//临时修改权限
//3.获取value记录的地址值
byte[] value = (byte[]) field.get(s);
value[0] = 100;
System.out.println(s);
System.out.println(ss);
}
}
8.3、反射和配置文件结合动态获取的练习(重点)
需求: 利用反射根据文件中的不同类名和方法名,创建不同的对象并调用方法。
分析:
①通过Properties加载配置文件
②得到类名和方法名
③通过类名反射得到Class对象
④通过Class对象创建一个对象
⑤通过Class对象得到方法
⑥调用方法
代码演示:
properties配置文件:
classname=com.liming.mytest.Student
methodname=sleep
public class ReflectDemo01 {
public static void main(String[] args) throws Exception{
//1.读取配置文件的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("day15\\prop.properties");
prop.load(fis);
fis.close();
System.out.println(prop);
String classname = prop.get("classname") + "";
String methodname = prop.get("methodname") + "";
//2.获取字节码文件对象
Class clazz = Class.forName(classname);
//3.获取构造器创建这个类的对象
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object o = constructor.newInstance();
System.out.println(o);
//4.获取方法的对象
Method sleep = clazz.getDeclaredMethod(methodname);
sleep.setAccessible(true);
//5.运行方法
sleep.invoke(o);
}
}
8.4、利用发射保存对象中的信息(重点)
需求:对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
代码演示:
public class ReflectDemo02 {
public static void main(String[] args) throws IOException, IllegalAccessException {
/* 对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去 */
Student01 stu = new Student01("张三", 23, '男', 182.3, "学习");
Teacher teacher = new Teacher("赖桑", 20000.0);
saveObject(stu);
saveObject(teacher);
}
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class clazz = obj.getClass();
//2. 创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("day15" + File.separator + "a.txt", true));
//3. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//获取成员变量的名字
String name = field.getName();
//获取成员变量的值
Object value = field.get(obj);
//写出数据
bw.write(name + "=" + value);
bw.newLine();
}
bw.close();
}
}
运行结果: 在a.txt文件中
name=赖桑
salary=20000.0
name=张三
age=23
gender=男
height=182.3
hobby=学习
name=赖桑
salary=20000.0