Java 反射详解
创始人
2024-05-25 12:49:10
0

一、反射

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 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

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...