Java中的反射

≯℡__Kan透↙ 提交于 2020-01-06 22:03:56

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

类加载器

类的加载:当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

获取Class对象的三种方式:
1.通过类名获取 类名.class
2.通过对象获取 对象名.getClass()
3.通过全类名获取 Class.forName(全类名)

一个类在加载过程中的三部曲:
1.加载
就是指将class文件读入内存,并为之创建一个Class对象.
任何类被使用时系统都会建立一个Class对象。
2.连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
3.初始化 系统给出的默认值

类的加载时机(重点)
创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类

类加载起的作用:负责将.class文件加载到内在中,并为之生成对应的Class对象。

类加载器的分类
Bootstrap ClassLoader 根类加载器:也被称为引导类加载器,负责Java核心类的加载,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中。

Extension ClassLoader 扩展类加载器:负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录。

sysetm ClassLoader 系统类加载器:负责在JVM启动时加载来自java命令的class文件。

反射

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。相当于照镜子。

对象为什么需要照镜子呢?
1.有可能这个对象是别人传过来的
2.有可能没有对象,只有一个全类名
通过反射,可以得到这个类里面的信息

Student.java–Student.class(字节码文件)–看成一个对象,这个对象就叫字节码文件对象–对应的类Class
什么是反射?
答:每一个Java文件都会对应生成一个class字节码文件,每一个class字节码文件都会对应一个字节码对象,在每一个字节码文件对象中,也是会有成员方法,成员变量,和构造方法的,反射就是:利用字节码文件对象去使用该类的成员变量,成员方法,构造方法。
(1)获取字节码文件对象的三种方式:
A:Object类的getClass()方法
B:数据类型的静态class属性
C:Class类的静态方法forName()
注意:
在平常写案例的时候,我们直接使用第二种最方便。
但是实际开发中,我们一般用的都是第三种。是因为第三种接收的是一个字符串类型的参数,
我们可以把这个参数作为配置文件的内容进行配置,这样就实现了一个变化的内容。
案例:使用上述三种方式获取类的Class对象

先创建一个标准的Person类
package com.edu_01;
public class Person {
    //创建三个成员变量
    String name;
    public int age;
    private String address;
    //创建几个构造方法
    public Person(){}
    Person(String name){
        this.name = name;
    }
    protected Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    private Person(String name,int age,String address){
        this.name = name;
        this.age = age;
        this.address = address;
    }
    //创建一个成员方法
    public void method(){
        System.out.println("method");
    }
    void function(int price){
        System.out.println(price);
    }
    private void show(String husband,String wife){
        System.out.println(husband+"--"+wife);
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", address=" + address
                + "]";
    }
}
创建一个测试类调用方法
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取Person类对应的Class字节码文件对象
        //A:Object类的getClass()方法
        Person p1 = new Person();
        Person p2 = new Person();
        Class c1 = p1.getClass();
        Class c2 = p2.getClass();
        System.out.println(p1==p2);//false
        System.out.println(c1==c2);//true
        //每一个类会对应一个字节码文件对象,而这个字节码文件对象就是这个类的原型,每一个类有且仅有一个字节码文件对象
        System.out.println("-------------");
        //B:数据类型的静态class属性
        Class c3 = Person.class;
        System.out.println(c2==c3);
        System.out.println("---------------");
        //C:Class类的静态方法forName()
        //public static Class<?> forName(String className),在这里所说的类名是全类名(带包名的类名)
        //Class c4 = Class.forName("Person");//java.lang.ClassNotFoundException: Person
        Class c4 = Class.forName("com.edu_01.Person");
        System.out.println(c3==c4);
    }
}

反射的使用步骤:
我们将成员变量封装在了 Field中
将构造方法封装在了 Constructor中
将成员方法封装在了 Method中
反射:class字节码文件对象 – 去得到对应的成员对象 – 通过该成员的对象调用方法使用,通过反射获取构造方法并使用

案例:1.通过反射获取构造方法
public Constructor[] getConstructors() 获取公共的构造方法
public Constructor[] getDeclaredConstructors() 获取所有的构造方法(包括私有)
public Constructor getConstructor(Class… parameterTypes) 根据构造参数获取公共的指定构造
public Constructor getDeclaredConstructor(Class)
Class类的getConstructor()方法,无论是否设置setAccessible(),都不可获取到类的私有构造器.
Class类的getDeclaredConstructor()方法,只有设置setAccessible()为true时,才可获取到类的私有构造器(包括带有其他修饰符的构造器).
在用反射创建一个私有化构造器类的对象时,务必要用getDeclaredConstructor()方法并设置构造器方可访问setAccessible(true)

package com.edu_02;
import java.lang.reflect.Constructor;
public class ConstructorDemo {
    public static void main(String[] args) throws Exception {
        //public Constructor[] getConstructors() 获取公共的构造方法
        //获取Peroson类对应的字节码文件对象
        Class c = Class.forName("com.edu_01.Person");
        //获取Perosn类中对应的构造方法
        Constructor[] cons = c.getConstructors();
        //遍历所有获取到的构造方法
        for (Constructor con : cons) {
            System.out.println(con);//public com.edu_01.Person()
        }
        System.out.println("----------------");
        //public Constructor[] getDeclaredConstructors() 获取所有的构造方法(包括私有)
        Constructor[] cons2 = c.getDeclaredConstructors();
        for (Constructor con : cons2) {
            System.out.println(con);
        }
        System.out.println("---------------");
        //public Constructor getConstructor(Class... parameterTypes) 根据构造参数获取公共的指定构造
        //获取Person类中的公共的无参数的构造方法
        Constructor con  = c.getConstructor();
        System.out.println(con);
        //怎么通过我们刚才获取的无参构造创建对象
        //public T newInstance(Object... initargs)
        Object obj = con.newInstance();
        System.out.println(obj);
        System.out.println("-------------");
        //获取Person类中的非公共的构造方法
        //public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        Constructor con2 = c.getDeclaredConstructor(String.class,int.class,String.class);
        //获取构造器的时候,传入的什么参数,在调用获取到的这个构造方法对象的时候也就需要传入什么类型的参数
        //取消这个构造器对象所对应的访问权限检测
        con2.setAccessible(true);
        Object obj2 = con2.newInstance("陈奕迅",45,"香港");
        System.out.println(obj2);
        System.out.println("-----------");
        //获取被Protected修饰的构造方法
        Constructor con3 = c.getDeclaredConstructor(String.class,int.class);
        //取消访问权限的检测
        con3.setAccessible(true);
        Object obj3 = con3.newInstance("张学友",50);
        System.out.println(obj3);
    }
}

通过反射获取成员变量并使用
public Field[] getFields() 获取公有的成员变量
public Field[] getDeclaredFields() 获取全部的成员变量,包括私有
public Field getDeclaredField(String name) 传入变量名称返回指定的成员变量对象,包括私有
public Field getField(String name) 传入变量名称返回指定的成员变量对象,仅可获取共有的
public void set(Object obj,Object value) 给一个对象的一个字段设置一个值

package com.edu_03;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class FieldDemo {
    public static void main(String[] args) throws Exception {
        //获取Perosn类对应的字节码文件对象
        Class c = Class.forName("com.edu_01.Person");
        //利用反射获取一个[Perosn对象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        //获取所有公共的字段对象
        //public Field[] getFields()获取公有的成员变量
        Field[] fields = c.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("--------------");
        //获取所有的字段对象,包括私有
        //public Field[] getDeclaredFields()获取全部的成员变量,包括私有
        Field[] fields2 = c.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println(field);
        }
        System.out.println("--------------");
        //public Field getDeclaredField(String name) 传入变量名称返回指定的成员变量对象,包括私有
        //获取String name;字段
        Field f = c.getDeclaredField("name");
        //使用f这个对象给一个Perosn对象设置姓名
        //public void set(Object obj, Object value)
        /* 参数1:需要设置的对象
         * 参数2:需要给这个对象设置什么值       */
        //取消访问修饰符的限制
        f.setAccessible(true);
        f.set(obj, "谢娜");
        System.out.println("--------------");
        //public Field getField(String name)传入变量名称返回指定的成员变量对象,仅可获取公有的
        //获取public int age;
        Field f2 = c.getField("age");
        f2.set(obj, 30);
        System.out.println("---------------");
        Field f3 = c.getDeclaredField("address");
        //取消权限检测
        f3.setAccessible(true);
        f3.set(obj, "湖南");
        System.out.println(obj);
    }
}

通过反射获取成员方法并使用
public Method[] getMethods()获取所有公共成员方法
public Method[] getDeclaredMethods()获取所有成员方法,包括私有
public Method getMethod(String name, Class)

package com.edu_04;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class MethodDemo {
    public static void main(String[] args) throws Exception {
        //1.获取Person类中所有的公有的成员方法
        //创建Pwrson类对应的字节码文件对象
        Class c = Class.forName("com.edu_01.Person");
        //利用反射的方式创建一个Person对象
        Constructor con  = c.getConstructor();
        Object obj = con.newInstance();
        //public Method[] getMethods()获取所有公共成员方法,包括父类的公共的成员方法
        Method[] methods = c.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("---------------");
        //获取Person类中的所有的成员方法
        //public Method[] getDeclaredMethods()获取所有成员方法,包括私有,只能获取本类的所有的成员方法,不能获取父类的
        Method[] methods2 = c.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }
        System.out.println("---------------");
        // public Method getMethod(String name, Class<?>... parameterTypes)
        //参数一:方法名 参数二:方法参数类型.class 获取指定的公共方法
        //获取method()这个公有的成员方法
        Method m  = c.getMethod("method");
        System.out.println(m);
        System.out.println("---------------");
        //public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
        //参数一:方法名 参数二:方法参数类型.class 获取指定的方法,包括私有
        //需求获取method这个个方法
        Method m2 = c.getDeclaredMethod("method");
        System.out.println(m2);
        //使用m2这个成员方法的对象
        //public Object invoke(Object obj,Object... args)
        /* 参数1:执行m2这个方法的对象
         * 参数2:执行m2这个方法的时候,需要传入的参数         */
        m2.invoke(obj);
        System.out.println("-----------------");
        //获取Person类中function方法
        Method m3 = c.getDeclaredMethod("function", int.class);
        //取消权限检测
        m3.setAccessible(true);
        m3.invoke(obj, 10);
        System.out.println("-------------------");
        Method m4 = c.getDeclaredMethod("show", String.class,String.class);
        m4.setAccessible(true);
        m4.invoke(obj, "张杰","谢娜");
    }
}

案例:
需求:通过反射运行配置文件的内容。

创建一个人类
package com.edu_05;
public class Person {
    public void eat(){
        System.out.println("民以食为天");
    }
}
创建一个老师类
public class Teacher {
    public void teach(){
        System.out.println("老师会讲课");
    }
}
创建一个学生类
public class Student {
    public void study(){
        System.out.println("好好学习,天天向上");
        }
}
利用测试类测试
package com.edu_05;
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
    public static void main(String[] args) throws Exception {
        //1.创建学生对象调用学习的方法
//      Student s = new Student();
//      s.study();
        //2.需要创建老师的对象,调用讲课的方法
//      Teacher t = new Teacher();
//      t.teach();
        //3.需要调用人类的吃饭方法
//      Person p = new Person();
//      p.eat();
        /* 以上的代码的写法,俗称硬编码(将代码写的太死了)
         * 尝试去将代码中需要访问到的内容,这个内容是有可能需要不断
         * 去动态修改的一个内容,我们可不可以将这些内容存储到一个配置文件中,
         * 当我们的程序运行起来之后,直接去配置文件中读取信息就可以了,
         * 以后如果需要更改的话,我们只需要更改配置文件中的内容即可,
         * 而我们自己的代码不需要改动。。。。
         * 对上面的代码进行改动:
         * 使用反射的方式创建对象,调用方法。。
         * 1.创建某一个类对应的字节码文件对象  -- 全类名(className=com.edu_01.Person)
         * 2.将对象需要调用的方法,依然利用反射的方式获取该方法的对应的对象 -- 方法名(classMethod=study)     */
        //创建Properties集合
        Properties prop = new Properties();
        prop.load(new FileReader("prop.txt"));
        //从集合中获取对应的全类名和对应的需要执行的方法名
        String className = prop.getProperty("className");
        String classMethod = prop.getProperty("classMethod");
        //创键配置文件中类对应的对象
        Class c = Class.forName(className);
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        //通过反射获取需要执行的方法对象
        Method m = c.getMethod(classMethod);
        //执行获取到的方法
        m.invoke(obj);
    }
}

可以创建一个配置文件用来存储数据,然后创建出一个类的字节码文件对象,利用字节码文件对象来调用方法,这样可以使我们的代码避免修改,只需要配置文件就好,更加方便。
需求:给ArrayList的一个对象,想在这个集合中添加一个字符串数据,如何实现呢?
分析:先创建一个ArrayList集合,然后获取他的字节码文件对象,再利用对象调用方法将字符串类型添加进去。

  1.创建ArrayList类对应的字节码文件对象
  2.通过字节码文件对象获取到add方法对象
  3.调用add方法的对象,给集合中添加字符串
package com.edu_06;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ArrTest {
    public static void main(String[] args) throws Exception {
        //1.创建ArrayList类对应的字节码文件对象
        ArrayList<Integer> arr = new ArrayList<>();
        Class c = arr.getClass();
        //2.通过字节码文件对象获取到add方法对象
        Method addM = c.getDeclaredMethod("add", Object.class);
        //传入参数的时候必须传入与方法上的参数一致的参数
        //3.调用add方法的对象,给集合中添加字符串
        addM.invoke(arr, "java");
        addM.invoke(arr, "world");
        System.out.println(arr);
    }
}

为什么Integer 类型的能添加String类型的?
其实泛型是给jvm虚拟机看的,当程序一旦执行起来后,泛型就会自动去掉,这也称之为泛型擦除!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!