什么是反射?
反射允许对成员变量,成员方法和构造方法的信息进行编程访问。
说简单点就是反射能将类里面的构造方法,成员变量,修饰符,返回值,注解,类型,甚至异常等类里面的所有东西都能够获取出来。
关于Class的介绍:Class类是用来描述类的类,它是一个十分特殊的类,没有构造方法。Class对象的加载过程如下:当程序运行时,我们编写的每一个类都会被编译生成 类名.class 文件,当我们我们new对象或者类加载器加载的时候,JVM就会加载我们的 类名.class 文件并且加载到内存中,即当一个类加载完成之后,在堆内存的方法区中就生成了一个该类唯一的Class对象(一个类只会对应一个Class对象,绝对不会产生第二个),这个Class对象就包含了完整的类的结构信息,用于表示该类的所有信息。
但是我们获取不是从java文件中获取的,而是从class字节码文件中获取的,所以我们先来讲一讲如何获取字节码文件对象。
一:反射获取类对象
java代码运行可以分为以下几个阶段:
1.源代码阶段:编写java文件,然后把他编译成字节码文件。没有把代码加载到内存当中,只是在硬盘进行操作。
2.加载阶段:把字节码文件加载到内存当中
3.运行阶段:在内存当中获取对象
以下三种获取对象的方式分别对应着上面三个阶段获取:
1.Class.forNmae("全类名"); 2.类名.class; 3.对象.getClass;
这里所有举例都用student类来作为演示,所以先创建一个student类,后面会一直用这个。
package H; //创建学生类 public class student { private String name; private int age; public student() { } public student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
获取类对象的三种方法
public static void main(String[] args) throws ClassNotFoundException { Class aClass = Class.forName("H.student"); Class bClass =student.class;//最为常用 student s=new student();//当作参数进行传递 Class cclass=s.getClass();//已经有了这个类的对象才可以使用 } }
二:反射获取构造方法
Constructor就是java中构造方法的类
演示以下:
public class test { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class aClass = Class.forName("H.student");//最为常用 Class bClass =student.class;//当作参数进行传递 student s=new student(); Class cclass=s.getClass();//已经有了这个类的对象才可以使用 Constructor[] constructors1 = aClass.getConstructors();//获取全部公共构造方法 Constructor[] constructors2 = aClass.getDeclaredConstructors();//获取全部构造方法 Constructor constructor1 = aClass.getConstructor(String.class,int.class);//获取参数传递的类型的构造方法 Constructor constructor2 = aClass.getDeclaredConstructor();// } }
获取到Constructor对象可以调用Constructor类中的getModifiers方法获取他的权限修饰符。
打印出来的结果是整形形式,代表不同的权限修饰符。
int modifiers=constructor1.getModifiers();
用获取到的构造方法创建对象:
1.形参需要跟构造对象一致。
2.当构造是私有方法时,不能直接创建对象,需要使用setAccssible方法临时取消权限校验(暴力反射)。
sonstructor1.setAccessible(ture); student stu=(student) constructor1.newInstance("张三",23);
三:反射获取成员变量
field就是java中成员变量的类
Field field = aClass.getField("name");
获取成员变量的权限修饰符:打印出来的结果是整形形式,代表不同的权限修饰符。
int modifiers=field.getModifiers();
获取成员的成员变量名:
String n=field.getType();
获取成员的数据类型:
Class<?> type=name.getTypt();
获取成员变量记录的值:
当成员是私有时,需要使用setAccssible方法临时取消权限校验(暴力反射),来查看对象的值
student s=new student("张三","23"); name.setAccessible(true); Object values=name.get(s);
修改对象里记录的成员变量的值:
当成员是私有时,需要使用setAccssible方法临时取消权限校验(暴力反射),来修改对象的值
name.set(a,"诩子");
四:反射获取成员方法:
Method类就是java类中的成员方法的类
1.获取成员方法
Method[] methods = aClass.getMethods(); for (Method method : methods) { System.out.println(method); } method m=class.getMethod("eat",String.class);
注意:getMethod方法包括从父类继承的方法。
获取方法的名字:
String name=m.getName();
获取方法的形参:
int num =m.getParameterCount();//获取形参个数 Class<>[] p=m.getParameterTypes();//获取形参类型 Paramemter[] parameters=m.getParameters();//获取方法的形参
获取方法抛出的异常:
Class<?>[] exceptionTypes=m.getExceptionTypes();
运行方法:当成员是私有时,需要使用setAccssible方法临时取消权限校验(暴力反射),来修改对象的值
object invoke(Object obj,object... args) //形参一:要使用的方法调用者,用obj对象调用该方法 //形参二:要使用的方法的形参 //返回值:要使用的方法的返回值,当没有返回值就不写
students s=new students(); m.invoke(s,"汉堡包");
细节:我们发现当操作到私有化的参数且是跟实际对象有关的操作(查看对象数值,修改对象数值),需要使用暴力反射。
反射的作用:
1.获取一个类里面的所有的信息,获取到了之后,再执行其他的业务逻辑
2.结合配置文件,动态的创建对象并调用方法。
我们通过两个小练习来体现:
test1:保存对象所有的字段名和值到文件中
public class test { public static void main(String[] args) throws IllegalAccessException, IOException { student s=new student("诩",18,'女',168,"熬夜"); teacher t=new teacher("俊",10000); saveObject(s); } public static void saveObject(Object obj) throws IllegalAccessException, IOException { BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\code\\javaee\\myFrect\\src\\test")); Class aClass = obj.getClass(); Field[] fidles = aClass.getDeclaredFields(); for (Field fidle : fidles) { fidle.setAccessible(true); String name=fidle.getName(); Object value = fidle.get(obj); bw.write(name+"="+value); bw.newLine(); } bw.close(); } }
test2:通过配置文件动态创建对象
public static void main(String[] args) throws IOException { Properties prop =new Properties(); prop.put("className","D:\\code\\javaee\\myFrect\\src\\H2\\student.java"); prop.put("methodName","study"); FileOutputStream fos = new FileOutputStream("D:\\code\\javaee\\myFrect\\src\\prop.properties"); prop.store(fos,"无"); fos.close(); Properties prop1 =new Properties(); FileInputStream fis=new FileInputStream("D:\\code\\javaee\\myFrect\\src\\prop.properties"); prop1.load(fis); String className=(String)prop1.get("className"); String methodName=(String)prop1.get("methodName"); System.out.println(className); System.out.println(methodName); fis.close(); }
上面的方法获取到了文件的类名和方法名,接着便可以使用反射创建对象,直接修改文件的数据可以实现动态的创建对象。
注意:配置文件是一个特殊的双列集合,里面有方法能跟IO结合。