反射复习(java)


前言

反射非常重要,一定要掌握,我个人觉得不是很难,就是要注意各种方法里的参数是什么,然后还是那句话:最快的入门方式是看视频,建议先看完视频,再来看博客,这里我推荐 b 站韩顺平老师的视频

反射机制的作用

作用看不懂的话可以最后看

反射可以在不修改源码的情况下来控制程序符合 ocp 原则 :不修改源码,扩容功能


举个例子

现在有一个 main 类 和 Person 类,还有一个配置文件my.Properties,可以利用反射通过中间的 my.Properties 访问类中的东西

my.Properties

Classfullpath = com.mangfu.Person
field = name

Person

public class Person {
    public String name = "jack";
    public int age = 13;

    public Person() {};
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

}

Main

public class Main {
    public static void main(String[] args) throws Exception{

        //导入配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/com/mangfu/my.Properties"));

        //通过配置文件获取 类的完整路径, 和类的属性名
        String classfullpath = properties.getProperty("Classfullpath");
        String field = properties.getProperty("field");

        //通过反射获取到 Person类的字节码对象,我们可以操作里面所有的东西
        Person person = new Person();
        Class clazz = Class.forName(classfullpath);

        //这里直接操作类里面的属性
        //本来是要 person.name 改成 person.age
        //现在直接在配置文件中的 name 改成 age就行了
        Field field1 = clazz.getField(field);
        System.out.println(field1.get(person));

    }
}

可以看见我们可以通过中间的 配置文件 无需修改源码,就能操作类中的信息




反射机制的原理

在这里插入图片描述

编译的时候会产生字节码文件,然后通过类加载器,进入加载阶段,会在堆区创建一个 Class 类的对象,当然这个Class对象里面的成员变量,构造器,构造方法等东西都是对象,然后 到 new Cat(),在 堆区创建 Cat 对象,该对象知道他属于哪个 Class 对象,我们可以通过反射得到这个 Class 对象,然后操作类中的所有东西

注意一个类只有一个 Class 对象



加载机制详细解释

类加载过程图
在这里插入图片描述


Loading(加载)

JVM 将 字节码文件 从 不同 的 数据源,转化为 二进制字节流,加载到内存中,生成一个代表该类的 Class 对象,也就是这个类的对象


verification(验证)

为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,,不会危害虚拟机自身的安全,这个阶段会 进行文件格式验证 **(是否以魔数 oxcafebabe开头),元数据验证,字节码验证,和符号引用验证

如果想要关闭大部分类验证措施,加快加载速度可以这样做

javac -Xverify:none HelloWorld.java

helloworld 是类名


Preparation(准备)

JVM 会在该阶段 对 静态变量!!!,分配内存并初始化(对应数据类型的默认初始值)。这些变量的 内存分配在方法区

注意这里是静态变量,不是静态的,加载阶段不会分配内存


Resolution (解析)

JVM 将常量池的 == 符号引用== 替换为 直接引用的过程

符号引用

将 Java 类文件中,以一组符号来描述类名,方法名等东西,这些符号与实际的内存地址没有关系

直接引用

将符号引用,直接替换为直接指向这些数据的指针或地址

还没分配内存的时候用符号引用类似于打个标记,这个时候分配内存在变成直接引用


initialization(初始化)

这个阶段才真正执行类中定义的 Java 程序代码,这个阶段也是执行 clinit() 方法的过程,这个方法会根据顺序,把静态变量的赋值动作,和静态代码块中的语句合并

clinit 方法在多线程的环境中会正确加锁,也就是线程是阻塞的,等一个线程 clinit 方法执行完,才放锁,让下一个线程用




获取 Class 对象

  • Class.forName(“全类名”):编译阶段创建
    全类名:包名 + 类名

多用于配置文件,最常用

//com.mangfu.test 包下的 Student 类
Class test = Class.forName("com.mangfu.test.Student" );

  • 类名.class:加载阶段创建,类加载器得到 Class 对象

多用于参数传递,比如通过反射得到对应构造器对象,这种方式 最安全,性能最高

Class clazz = Student.class

  • 对象.getClass():运行阶段创建

通过创建好的对象,获取 Class 对象

Student s = new Student();
Class clazz = s.getClass();

  • 基本数据类型.class

基本数据类型,创建 Class 对象的方法

Class clazz = int.class

  • 包装类.TYPE

基本数据类型对应的包装类,创建 Class 对象的放啊

Class clazz = Integer.class



反射获取构造方法:获取 Class 对象里面 Constructor 对象

Class类 中用于获取构造方法的方法

  • Constructor<?>[] getConstructors(): 返回所有公有构造方法对象的数组

  • Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组

  • Constructor getConstructor(Class<?>… parameteTypes):返回单个公有构造方法对象
    参数为 基本数据结构或者包装类的字节码,对象.class,这样可以获取有参构造

  • Constructor getDeclaredConstructor(Class<?>…parmeterTypes):返回单个构造方法对象
    参数为 基本数据结构或者包装类的字节码,对象.class,这样可以获取有参构造

Constructor:用构造方法对象创建对象的方法
Student类

  • T newinstance(Object…initargs):根据指定的构造方法创建对象
    参数(可变):是构造函数里面需要的参数

  • setAccessible(boolean flag):设置为 true, 表示取消访问检查

setAccessible:**使用反射可以访问 private 构造器 [称为爆破]


举个例子
主要看Main类

Student类

public class Student {
    private int age = 18;
    public String name = "张三";

    private Student() {}

    private Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

Main 类

public class Main {
    public static void main(String[] args)throws Exception {
        Class clazz = Class.forName("com.mangfu.test.Student");

        //因为 构造方法是私有的 所以我们要用 getDeclaredConstructor
        //里面的参数是 形参的字节码
        Constructor cs = clazz.getDeclaredConstructor(int.class, String.class);

        //因为这个是私有对象,我们要通过爆破创建才能构造一个对象
        cs.setAccessible(true);

        //通过 newInstance 构造一个对象
        Student s = (Student) cs.newInstance(18, "张三");
        System.out.println(s);

    }
}


反射获取成员变量:获取Class 对象里面的 Field 对象

Class 类中用于获取成员变量的方法

  • Field[] getFields():返回所有公共成员变量对象的数组

  • Field[] getDeclaredFields():返回所有成员变量对象的数组

  • Field[] getField(String name):返回单个公有成员变量对象
    参数是 对象(不是Class对象,下面是Student 对象) 的属性名

  • Field getDeclaredField(String name):返回单个成员变量对象
    参数是 对象(不是Class对象,下面是Student 对象) 的属性名

Field 类中用于创建对象的方法

  • void set(Object obj, Object value):赋值
    参数1:field 对象,参数2:需要赋的值

  • Object get(Object obj):获取值
    参数:对象(不是Class 对象,下面是 Student 对象)的名字

  • setAccessible(boolean flag):设置为 true, 表示取消访问检查

举例
Student 类

public class Student {
    private int age = 18;
    private String name = "张三";

    private Student() {}

    private Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

Main类

public class Main {
    public static void main(String[] args)throws Exception {

        //得到 Student 类的 Class 对象
        Class clazz = Class.forName("com.mangfu.test.Student");

        //获取 Class 对象里面的 Field 对象----属性 name
        Field field = clazz.getDeclaredField("name");

        //通过 反射利用构造函数构造对象
        Constructor cs = clazz.getDeclaredConstructor(int.class, String.class);
        cs.setAccessible(true);
        Student s = (Student) cs.newInstance(18, "张三");

        //因为这个属性是私有的 所以要用 setAccessible 爆破一下,才能修改
        field.setAccessible(true);

        //修改 属性 name 的 值,因为上面我们获取的 是属性 name 对象
        field.set(s, "李四");
        System.out.println(field.get(s));//输出李四

    }
}


反射获取成员方法:获取 Class 对象里的 Method 对象

Class类中用于获取成员方法的方法

  • Method[] getMethods():返回公有成员方法对象的数组,包括继承的

  • Method[] getDeclaredMethods:返回所有成员方法对象的数组,不包括继承的

  • Method getMethod(String name, Class<?>…parameterTypes):返回单个公有成员方法对象
    参数1:方法的名字,参数2:方法的形参(可变)

  • Method getDeclaredMethod(String name, Class<?>…parameterTypes):返回单个成员方法对象
    参数1:方法的名字,参数2:方法的形参(可变)

Method类中用于创建对象的方法

  • Object invoke(Object obj, Object…args):允许方法
    参数1: 对象(不是Class对象,下面是Student 对象),参数2:调用方法要传递的参数(没有就不写)。返回值:调用的方法的返回值的对象

  • setAccessible(boolean flag):设置为 true, 表示取消访问检查

举例
Student 类

public class Student {
    private int age = 18;
    private String name = "张三";

    public Student() {};

    private Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    private int hi() {
        System.out.println("调用hi函数");
        return 1;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

Main 方法

public class Main {
    public static void main(String[] args)throws Exception {

        //得到 Student 类的 Class 对象
        Class clazz = Class.forName("com.mangfu.test.Student");

        //得到 Student 中的 hi 方法的对象
        Method method = clazz.getDeclaredMethod("hi");

        //这里 通过反射 构造 Student 类对象
        Constructor constructor = clazz.getConstructor();
        Student s = (Student) constructor.newInstance();

        //通过反射调用 Student 类中的 hi 方法,因为我们上面的得到的 Method 对象是 hi 方法
        //而且这个方法是私有的所以要爆破一手
        method.setAccessible(true);
        method.invoke(s);

    }
}



其他常用 API

Class
在这里插入图片描述


Constructor

Parmeter[] getrParmameters():获取构造方法的形参

int getmodifiers():获取成员变量的数据类型


Field

int getmodifiers():获取成员变量的数据类型
Class<?> getType:获取成员变量的数据类型
String getName:获取成员变量的名字


Method

int getmodifiers():获取成员变量的数据类型
Class[] getExceptionTypes:获取方法抛出的异常
String getName:获取成员变量的名字
Class[] getParameter():获取方法的形参,以 Class[] 形式返回
Class getReturnType:以Class 形式获取返回类型


getmodifiers() 方法返回值

默认修饰符号:0,public:1,private:2,protected:3,static:8,final:16




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值