为了面试而战-反射
今天来学习新的内容,反射。
反射
什么是反射?官方的解释是反射允许对封装类的字段、方法和构造函数的信息进行编程访问。这里的字段就是成员变量,方法就是成员方法,构造函数就是构造方法。
一个类的里面,常用的就是成员变量、成员方法、构造方法,反射就可以想成是一个人,他可以把这三个东西都获取到,并对他们进行操作。那获取出来有什么用呢?很有用的,比如IDEA中的自动提示功能就是反射,我们创建了一个对象,IDEA就可以把这个对象能调用的成员方法都获取出来并进行展示。还有我们在创建对象或者调用方法时,形参给忘了,按一下ctrl+p就可以获取提示,这也是反射,获取所有的形参并展示出来。说白了,反射就是可以从类里面拿东西,拿什么,就是常用的成员方法、成员变量、构造方法。
利用反射我们可以把成员变量获取出来,就可以得到这个变量的所有信息,比如修饰符、名字、数据类型、赋值或者已经记录的值。利用反射还可以获取构造方法,也能得到所有信息,扒得干干净净, 比如名字、修饰符、形参,甚至可以用获取出来的方法创建对象。也可以获取成员方法,同样干干净净,可以获取到修饰符、名字、形参、返回值,甚至方法抛出的异常,方法上的注解,运行获取的方法。在反射面前,一切都是赤裸裸的。
以上操作可以分成两类,一是获取,而是解剖。获取的时候不是从java文件中获取,而是从字节码文件.class中获取。我们要先学习如果获取.class字节码文件对象。再去从字节码文件对象中获取字段、方法以及构造方法。
获取class对象的三种方式
- Class.forName("全类名"); Class是类名,java定义好了这个类,用来描述字节码文件。forName()是静态方法。
- 类名.class;
- 对象.getClass(); getClass()是定义在Object中,所有对象都可以调用。
那这三种方式什么时候用呢?这三种方式就对应了java里面的三种不同的阶段,或者说我想创建一个对象,是要经历三个阶段的。
第一个阶段:先编写java文件,把他编译成class文件,这个阶段没有把代码加载到内存中,是在硬盘中操作,这个阶段称为源代码阶段。在这个阶段会通过第一种方式获取字节码文件对象。
第二个阶段:运行代码,会把.class文件加载到内存中,这个阶段称为加载阶段,用第二种方式。
第三个阶段:我要创建这个类的对象,此时称为运行阶段,用第三种方式。
看一下代码实现
public class MyReflectDemo1 { public static void main(String[] args) throws ClassNotFoundException { //第一种方式 //全类名:包名+类名 //最为常用 Class clazz1 = Class.forName("com.itheima.myreflect1.Student"); //第二种 //一般更多的是当做参数,之前学多线程的时候,同步代码块synchronized(对象.class) Class clazz2 = Student.class; //第三种 //当我们已经有了这个类的对象时,才可以使用 Student s = new Student(); Class clazz3 = s.getClass(); } }
Java中万物皆对象,class对象就是Class类。构造方法也可以看成是对象,是Constructor类。成员变量是Field类对象,成员方法是Method类的对象。
利用反射获取构造方法
public class MyReflectDemo { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { /* Class类中用于获取构造方法的方法 Constructor<?>[] getConstructors() 返回所有公共构造方法对象的数组 Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组 Constructor<T> getConstructor(Class<?>... parameterTypes) 返回单个公共构造方法对象 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造方法对象 Constructor类中用于创建对象的方法 T newInstance(Object... initargs) 根据指定的构造方法创建对象 setAccessible(boolean flag) 设置为true,表示取消访问检查 */ //1.获取class字节码文件对象 Class clazz = Class.forName("com.itheima.myreflect1.Student"); //2.获取构造方法 /* 获取所有公共构造方法 Constructor[] cons = clazz.getConstructors(); for(Constructor con : cons){ System.out.println(con); } */ //获取所有构造方法 //Constructor[] cons = clazz.getDeclaredConstructors(); //获取单个 Constructor con1 = clazz.getDeclaredConstructor();//空参构造 //获取有参构造函数,形参里的参数类型要和类中的构造方法形参类型一致 Constructor con2 = clazz.getDeclaredConstructor(String.class); Constructor con3 = clazz.getDeclaredConstructor(String.class,Integer.class); //获取权限修饰符 int modifiers = con3.getModifiers();//private - 2 public - 1 在帮助文段中能查到,搜索常量字段值,就有权限修饰符所对应的数字。修饰权限符的应用场景:IDEA底层会通过反射获取到权限修饰符,如果是私有的就不会给你提示,不让调用 Parameter[] parameters = con3.getParameters(); //拿到构造方法了,可以创建对象。con3获取的有参构造,所以创建对象的时候也得带参数 //表示临时取消权限校验,不做这一步会报错。因为这个构造是private修饰的,之前con3.getDeclaredConstructor()只是能让我们看见这个构造,并不能创建对象。 //这就叫暴力反射,你已经藏起来变成私有的,但我还能给你拿出来。 con4.setAccessible(true); Student stu = (Student)con3.newInstance("张三",23);
利用反射获取成员变量
public class MyReflectDemo { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { /* Class类中用于获取成员变量的方法 Field[] getFields(): 返回所有公共成员变量对象的数组 Field[] getDeclaredFields(): 返回所有成员变量对象的数组 Field getField(String name): 返回单个公共成员变量对象 Field getDeclaredField(String name):返回单个成员变量对象 Field类中用于创建对象的方法 void set(Object obj, Object value):赋值 Object get(Object obj) 获取值 */ //1.还是要先获取字节码文件的对象,因为每一个要获取的东西都是在字节码文件里的 Class clazz = Class.forName("com.itheima.myreflect1.Student"); //2.获取成员变量 //获取公共的成员变量 Field[] fields = clazz.getFields(); //获取所有的成员变量 Field[] fieldsD = clazz.getDeclaredFields(); //获取公共单个成员变量 Field gender = clazz.getField("gender"); //获取单个成员变量 Filed name = clazz.getField("name"); //获取权限修饰符 int modifiers = name.getModifiers(); //获取成员变量名字 String n = name.getName(); //获取成员变量数据类型 Class<?> type = name.getType(); //获取成员变量记录的值 //和对象有关,要先创建一个对象 Student s = new Student("zhangsan",23,"男"); name.setAccessible(true);//临时取消访问权限 Object value = name.get(s); //修改对象里记录的值 name.set(s,"lisi"); } }
利用反射获取成员方法
public class MyReflectDemo { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { /* Class类中用于获取成员变量的方法 Field[] getFields(): 返回所有公共成员变量对象的数组 Field[] getDeclaredFields(): 返回所有成员变量对象的数组 Field getField(String name): 返回单个公共成员变量对象 Field getDeclaredField(String name):返回单个成员变量对象 Field类中用于创建对象的方法 void set(Object obj, Object value):赋值 Object get(Object obj) 获取值 */ //1.获取class字节码文件的对象 Class clazz = Class.forName("com.itheima.myreflect3.Student"); //2.获取所有方法对象 //(包括父类中所有的公共方法) Method[] methods = clazz.getMethods(); //获取不到父类中的方法 Method[] methods = clazz.getDeclaredMethod(); //获取单个方法 //一定要加参数类型,因为方法可能有重载的情况,不加参数类型不知道是哪个方法 Method m = clazz.getDeclaredMethod("eat",String.class); // 获取方法的修饰符 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(); //方法运行 /*Method类中用于创建对象的方法 Object invoke(Object obj, Object... args):运行方法 参数一:用obj对象调用该方法 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值(如果没有就不写)*/ Student s = new Student(); //参数一s:方法的调用者 //参数二“汉堡包”:调用的时候传入的实际参数 m.setAccessible(true); Onject result = m.invoke(s,"汉堡包");
反射的作用
学习完反射的API,我们来看一下反射的作用是什么
- 获取一个类里面所有的信息,获取到之后,再执行其他的业务逻辑
- 结合配置文件,动态的创建对象并调用方法
用两个练习来说明作用:
保存信息 对于任意一个对象,都可以把对象所有的字段名和值,报错到文件中去。 public class Student { private String name; private int age; private char gender; private double height; private String hobby; public Student() { } public Student(String name, int age, char gender, double height, String hobby) { this.name = name; this.age = age; this.gender = gender; this.height = height; this.hobby = hobby; } /** * 获取 * @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; } /** * 获取 * @return gender */ public char getGender() { return gender; } /** * 设置 * @param gender */ public void setGender(char gender) { this.gender = gender; } /** * 获取 * @return height */ public double getHeight() { return height; } /** * 设置 * @param height */ public void setHeight(double height) { this.height = height; } /** * 获取 * @return hobby */ public String getHobby() { return hobby; } /** * 设置 * @param hobby */ public void setHobby(String hobby) { this.hobby = hobby; } public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}"; } } public class MyReflectDemo { public static void main(String[] args) throws IllegalAccessException, IOException { /* 对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去 */ Student s = new Student("小A",23,'女',167.5,"睡觉"); Teacher t = new Teacher("播妞",10000 ); saveObject(s); } //把对象里面所有的成员变量名和值保存到本地文件中 public static void saveObject(Object obj) throws IllegalAccessException, IOException { //1.获取字节码文件的对象 Class clazz = obj.getClass(); //创建IO流 BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\a.txt")); //2.获取所有的成员变量 Field[] fields = clazz.getDeclaredField(); for(Field field : fields){ //不知道哪个是private field.setAccessible(true); //获取成员变量的名字 String name = field.getName(); //获取成员变量的值 Object value = field.get(obj); bw.write(name + "=" + value); bw.newLine(); } bw.close(); } }
练习二
跟配置文件结合动态创建
反射可以跟配置文件结合的方式,动态的创建对象,并调用方法
classname=com.itheima.myreflect6.Teacher //全类名 method=teach //方法名 //表示程序在运行的过程中我要去创建上面这个类的对象,并调用下面这个方法。在以后我们要想调用其他类里的方法,测试类里的代码是不需要改的,只需要修改配置文件信息即可。动态的获取全类名,动态的获取方法,获取到哪个类就创建哪个类的对象,获取到哪个方法就运行哪个方法
public class MyReflectDemo { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //1.读取配置文件中的信息 Properties prop = new Properties(); //要把配置文件中的信息加载到集合中 //先定义IO流 FileInputStream fis = new FileInputStream("myreflect\prop.properties"); prop.load(fis); fis.close(); //2.获取全类名和方法名 String className = (String)prop.get("classname"); String methodName = (String) prop.get("method"); //3.利用反射创建对象并运行方法 Class clazz = Class.forName(className); //获取构造方法 Constructor con = clazz.getDeclaredConstructor(); Object o = con.newInstance(); //获取成员方法并运行 Method method = clazz.getDeclaredMethod(); method.setAccessible(true); method.invoke(o); } }
总结
学完反射,我们要知道以下内
- 反射的作用
- 获取任意一个类中的所有信息
- 结合配置文件动态创建对象
- 获得class字节码文件对象的三种方式
- Class.forName("全类名");
- 类名.class
- 对象.getClass();
- 如何获取构造方法、成员方法、成员变量
- get:获取
- set:设置
- Constructor:构造方法
- Parameter:参数
- Field:成员变量
- Modifiers:修饰符
- Method:方法
- Declared:私有的
这些就是我学到的反射的基础知识,希望能够帮助大家。
#你觉得今年春招回暖了吗##牛客解忧铺##牛客在线求职答疑中心##23届找工作求助阵地#我是一个转码的小白,平时会在牛客中做选择题,在做题中遇到不会的内容就会去找视频或者文章学习,以此不断积累知识。这个专栏主要是记录一些我通过做题所学到的基础知识,希望能对大家有帮助