反射概述

什么是反射

将类的各个组成部分封装为其余对象的过程就叫做 反射,其中 组成部分 指的是咱们类的 成员变量(Field)构造方法(Constructor)成员办法(Method)

应用反射的优缺点

  • 长处

    1. 在程序运行过程中能够操作类对象,减少了程序的灵活性;
    2. 解耦,从而进步程序的可扩展性,进步代码的复用率,不便内部调用;
    3. 对于任何一个类,当晓得它的类名后,就可能晓得这个类的所有属性和办法;而对于任何一个对象,都可能调用它的一个任意办法。
  • 毛病

    1. 性能问题:Java 反射中蕴含了一些动静类型,JVM 无奈对这些动静代码进行优化,因而通过反射来操作的形式要比失常操作效率更低。
    2. 平安问题:应用反射时要求程序必须在一个没有平安限度的环境中运行,如果程序有平安限度,就不能应用反射。
    3. 程序健壮性:反射容许代码执行一些平时不被容许的操作,毁坏了程序结构的抽象性,导致平台发生变化时形象的逻辑构造无奈被辨认。

Class 对象的获取及应用

获取 Class 对象的形式

  1. Class.forName("全类名")

源代码阶段,它能将字节码文件加载进内存中,而后返回 Class 对象,多用于配置文件中,将类名定义在配置文件中,通过读取配置文件来加载类。

  1. 类名.class

类对象阶段,通过类名的 class 属性来获取,多用于参数的传递。

  1. 对象.getClass()

运行时阶段,getClass() 定义在 Object 类中,表明所有类都能应用该办法,多用于对象的获取字节码的形式。

咱们首先定义一个 Person 类,用于后续反射性能的测试;

package com.cunyu;/** * @author : cunyu * @version : 1.0 * @className : Person * @date : 2021/4/7 22:37 * @description : Person 类 */public class Person {    private int age;    private String name;    public long id;    public long grade;    protected float score;    protected int rank;    public Person(int age, String name, long id, long grade, float score, int rank) {        this.age = age;        this.name = name;        this.id = id;        this.grade = grade;        this.score = score;        this.rank = rank;    }    public Person() {    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public long getId() {        return id;    }    public void setId(long id) {        this.id = id;    }        public long getGrade() {        return grade;    }    public void setGrade(long grade) {        this.grade = grade;    }    public float getScore() {        return score;    }    public void setScore(float score) {        this.score = score;    }    public int getRank() {        return rank;    }    public void setRank(int rank) {        this.rank = rank;    }    @Override    public String toString() {        final StringBuffer sb = new StringBuffer("Person{");        sb.append("age=").append(age);        sb.append(", name='").append(name).append('\'');        sb.append(", id=").append(id);        sb.append(", grade=").append(grade);        sb.append(", score=").append(score);        sb.append(", rank=").append(rank);        sb.append('}');        return sb.toString();    }}

定义好 Person 类之后,咱们尝试用 3 种不同的形式来获取 Class 对象,并比拟它们是否雷同。

package com.cunyu;/** * @author : cunyu * @version : 1.0 * @className : Demo1 * @date : 2021/4/7 23:29 * @description : Class 对象的获取 */public class Demo1 {    public static void main(String[] args) throws ClassNotFoundException {//        第一种形式,Class.forName("全类名")        Class class1 = Class.forName("com.cunyu.Person");        System.out.println(class1);//        第二种形式,类名.class        Class class2 = Person.class;        System.out.println(class2);//        第三种形式,对象.getName()        Person person = new Person();        Class class3 = person.getClass();        System.out.println(class3);//        比拟三个对象是否雷同        System.out.println(class1 == class2);        System.out.println(class1 == class3);    }}

上述代码中,会发现最初输入的比拟后果返回的是两个 true,阐明通过上述三种形式获取的 Class 对象都是同一个,同一个字节码文件(*.class)在一次运行过程中只会被加载一次

Class 对象的应用

获取成员变量

  • Field[] getFields()
package com.cunyu;import java.lang.reflect.Field;/** * @author : cunyu * @version : 1.0 * @className : Demo2 * @date : 2021/4/7 23:39 * @description : Class 对象的应用 */public class Demo2 {    public static void main(String[] args) throws ClassNotFoundException {        Class class1 = Class.forName("com.cunyu.Person");        Field[] fields = class1.getFields();        for (Field field : fields) {            System.out.println(field);        }    }}

回顾下咱们的 Person 类,能够发现 idgrade 成员变量都是被 public 所润饰的,阐明该办法是用于获取类中所有被 public 所润饰的成员变量(包含父类)。

  • Field getField(String name)
package com.cunyu;import java.lang.reflect.Field;/** * @author : cunyu * @version : 1.0 * @className : Demo2 * @date : 2021/4/7 23:39 * @description : Class 对象的应用 */public class Demo2 {    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {        Class class1 = Class.forName("com.cunyu.Person");        Field field1 = class1.getField("id");        System.out.println(field1);        Field field2 = class1.getField("age");        System.out.println(field2);        Field field3 = class1.getField("rank");        System.out.println(field3);    }}

从下面的后果剖析可知,该办法只能用于获取类中指定名称的 public 所润饰的成员变量,对于 protectedprivate 所润饰的成员变量,该办法是无奈获取的(包含父类)。而获取或设置成员变量值时,能够通过 get/set 办法来操作,具体操作办法如下。

// 假如咱们获取到的 Field 为下面的 id,获取和设置 id 的值就能够通过如下操作来进行// 1. 获取Field idField = personClass.getField("id");Person person = new Person();Object idValue = idField.get(person);System.out.println("id:" + idValue);// 2. 设置idField.set(person, "1312120");System.out.println("person:" + person);
  • Field[] getDeclaredFields()
package com.cunyu;import java.lang.reflect.Field;/** * @author : cunyu * @version : 1.0 * @className : Demo2 * @date : 2021/4/7 23:39 * @description : Class 对象的应用 */public class Demo2 {    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {        Class class1 = Class.forName("com.cunyu.Person");        Field[] fields = class1.getDeclaredFields();        for (Field field : fields) {            System.out.println(field);        }    }}

察看下面的后果可知,该办法可用于获取所有的成员变量,不必思考修饰符的限度(不包含父类)。

  • Field getDeclaredField(String name)
package com.cunyu;import java.lang.reflect.Field;/** * @author : cunyu * @version : 1.0 * @className : Demo2 * @date : 2021/4/7 23:39 * @description : Class 对象的应用 */public class Demo2 {    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {        Class class1 = Class.forName("com.cunyu.Person");        Field field1 = class1.getDeclaredField("id");        System.out.println(field1);        Field field3 = class1.getDeclaredField("rank");        System.out.println(field3);        Field field2 = class1.getDeclaredField("age");        System.out.println(field2);    }}

察看下面的后果可知,该办法可用于获取指定的成员变量,不必思考成员变量修饰符的限度(不包含父类)。然而在利用 setget 办法来获取和设置 privateprotected 润饰的成员变量时,须要利用 setAccessible() 来疏忽拜访全新啊修饰符的安全检查,否则程序将会报错。

获取构造方法

package com.cunyu;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;/** * @author : cunyu * @version : 1.0 * @className : Demo3 * @date : 2021/4/8 13:28 * @description : 结构对象获取 */public class Demo3 {    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {        Class personClass = Class.forName("com.cunyu.Person");//        1. 获取所有构造方法        System.out.println("所有构造方法");        Constructor[] constructors = personClass.getConstructors();        for (Constructor constructor : constructors) {            System.out.println(constructor);        }//        2. 获取指定构造方法//        空参构造方法        System.out.println("空参构造方法");        Constructor constructor1 = personClass.getConstructor();        System.out.println(constructor1);//        带参构造方法        System.out.println("带参构造方法");        Constructor constructor2 = personClass.getConstructor(int.class, String.class, long.class, long.class, float.class, int.class);        System.out.println(constructor2);//        获取构造方法后,能够利用它来创建对象        System.out.println("空参创建对象");//        第一种办法        Object person = constructor1.newInstance();        System.out.println(person);//        第二种办法        Object person1 = personClass.newInstance();        System.out.println(person1);                System.out.println("带参创建对象");        Object object = constructor2.newInstance(20, "村雨遥", 1312020, 3, 99.0F, 2);        System.out.println(object);    }}

  • Constructor<?>[] getConstructors()

    相似于通过 Class 实例来获取成员变量,该办法用于获取所有 public 所润饰的构造方法(包含父类);

  • Constructor<T> getConstructor(类<?>... parameterTypes)

该办法用于获取某一指定参数类型后的 public 所润饰的构造方法(包含父类);

  • Constructor<?>[] getDeclaredConstructors()

该办法用于获取所有 public 所润饰的构造方法(不包含父类);

  • Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)

该办法用于获取某一指定参数类型后的 public 所润饰的构造方法(不包含父类);

而获取到构造方法之后,咱们就能够利用 newInstance() 办法来创立类的实例。非凡的,如果咱们的构造方法是无参的,此时则能够间接利用 Class.newInstance() 来结构实例。

获取成员办法

package com.cunyu;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * @author : cunyu * @version : 1.0 * @className : Demo4 * @date : 2021/4/8 13:51 * @description : 成员办法获取 */public class Demo4 {    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {        Class personClass = Class.forName("com.cunyu.Person");//        获取所有 public 成员办法        System.out.println("获取所有成员办法");        Method[] methods = personClass.getMethods();        for (Method method : methods) {            System.out.println(method);        }//        获取指定名称的办法        System.out.println("获取指定名称的办法");        Method getAgeMethod = personClass.getMethod("getAge");        System.out.println(getAgeMethod);//        执行办法        Person person = new Person(20, "村雨遥", 1312020, 3, 99.0F, 2);        int age = (int) getAgeMethod.invoke(person);        System.out.println(age);    }}

  • Method[] getMethods()

用于获取以后类的所有 public 所润饰的成员办法(包含父类)。

  • Method getMethod(String name, 类<?>... parameterTypes)

用于获取以后类的某一个指定名称 public 所润饰的成员办法(包含父类)。

  • Method[] getDeclaredMethods()

用于获取以后类的所有 public 所润饰的成员办法(不包含父类)。

  • Method getDeclaredMethods(String name, 类<?>... parameterTypes)

用于获取以后类的某一个指定名称 public 所润饰的成员办法(不包含父类)。

而当咱们获取到类的成员办法后,如果要执行某一个办法,能够应用 invoke() 办法来执行该办法。

获取类名

package com.cunyu;/** * @author : cunyu * @version : 1.0 * @className : Demo5 * @date : 2021/4/8 14:06 * @description : 获取类名 */public class Demo5 {    public static void main(String[] args) throws ClassNotFoundException {        Person person = new Person();        Class personClass = person.getClass();        String className = personClass.getName();        System.out.println(className);    }}

  • String getName()

从上述程序的后果可知,当咱们获取到 Class 对象之后,如果不晓得类的全名,就能够应用 getName() 来获取该类的全名。

反射实例

假如咱们有如下需要:在不扭转类的代码的前提下,咱们可能创立任意类的对象,并执行其中的办法。

此时,咱们能够通过 配置文件 + 反射 的形式来实现这一成果,而这也就是咱们当初所用框架中的根底,当咱们应用反射后,只须要通过批改配置文件中的内容就可能不必去改代码就实现对应的性能。

假如咱们有两个类,一个 Student,一个 Teacher,两者的定义如下;

package com.cunyu;/** * @author : cunyu * @version : 1.0 * @className : Teacher * @date : 2021/4/8 15:15 * @description : 老师类 */public class Teacher {    private String name;    private int age;    public void teach() {        System.out.println("教书育人……");    }}
package com.cunyu;/** * @author : cunyu * @version : 1.0 * @className : Student * @date : 2021/4/8 15:16 * @description : 学生类 */public class Student {    private String name;    private float score;    public void study() {        System.out.println("好好学习,天天向上……");    }}

要实现咱们的需要,通常须要如下步骤:

  1. 将要创建对象的全类名和要执行的办法都配置在配置文件中;

定义的配置文件 prop.properties ,其中次要内容包含 classNamemethodName 两个属性,别离代表类的全类名和要调用办法的名字。一个具体实例如下,别离代表名为 Student 的类和名为 study 的办法。

className=com.cunyu.StudentmethodName=study
  1. 而后在主办法中加载读取配置文件;
//        创立配置文件对象Properties properties = new Properties();//        加载配置文件ClassLoader classLoader = ReflectTest.class.getClassLoader();InputStream inputStream = classLoader.getResourceAsStream("prop.properties");properties.load(inputStream);//        获取配置文件中定义的数据String className = properties.getProperty("className");String methodName = properties.getProperty("methodName");
  1. 利用反射技术将类加载到内存中;
//        加载进内存Class name = Class.forName(className);
  1. 接着利用 newInstance() 办法创建对象;
//        创立实例Object object = name.newInstance();
  1. 最初则是利用 invoke() 办法来执行办法;
//        获取并执行办法Method method = name.getMethod(methodName);method.invoke(object);

将整个流程汇总起来就是:

package com.cunyu;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Properties;/** * @author : cunyu * @version : 1.0 * @className : ReflectTest * @date : 2021/4/8 15:27 * @description : 测试 */public class ReflectTest {    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {//        创立配置文件对象        Properties properties = new Properties();//        加载配置文件        ClassLoader classLoader = ReflectTest.class.getClassLoader();        InputStream inputStream = classLoader.getResourceAsStream("prop.properties");        properties.load(inputStream);//        获取配置文件中定义的数据        String className = properties.getProperty("className");        String methodName = properties.getProperty("methodName");//        加载进内存        Class name = Class.forName(className);//        创立实例        Object object = name.newInstance();//        获取并执行办法        Method method = name.getMethod(methodName);        method.invoke(object);    }}

此时,咱们只须要改变配置文件 prop.properties 中的配置即可输入不同后果;

总结

好了,感激急躁看到这里的各位。如果您感觉本文对您有所帮忙,那就给我点个赞吧!

最初,对于文中知识点有谬误或欠缺的中央,还请大家见谅,欢送大家评论留言给我斧正~