关于java反射及相关使用的部分理解

一、类的加载时机

当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三步来实现对这个类进行初始化:
(1)加载:
就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
(2)连接:
验证是否有正确的内部结构,并和其他类协调一致,准备 负责为类的静态成员分配内存,并设置默认初始化值
(3)初始化:
初始化成员变量等等

类的加载时机分为以下几种:
(1)创建类的实例
(2)访问类的静态变量,或者为静态变量赋值
(3)调用类的静态方法
(4)初始化某个类的子类
(5)使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

二、什么是反射

众所周知,创建一个对象分为三个阶段:
1.源文件阶段 .java的文件
2.字节码阶段 .class
3.创建对象阶段 new 对象名称

java的反射机制就是指:
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。想要使用反射,就必须得要获取字节码文件

用途
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。

接下来通过代码来进行演示,首先我们创建一个Person类:

package com.zhangyi.demo;

/**
 * @author zhangyi
 */
public class Person {
    public String name;
    public Integer age;
    private String food;

    //无参的构造方法
    public Person() {

    }

    //有参的构造方法
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }


    public void show() {
        System.out.println("我是" + this.name + ",今年" + this.age + "岁,我今天吃了"+this.food);
    }
    private void eat(String food){
		System.out.println("我今天吃了"+ food);
    }
}

之前提到,想要使用反射,就必须得要获取字节码文件,获取类的字节码文件有以下三种方式,以Person类为例,通过Junit4单元测试来进行演示:

public class PersonTest {

    @Test
    public void test1() throws ClassNotFoundException {
        /*获取类的字节码的三种方式*/
        
       /* 第一种  Class类中静态方法forName()  一般用于读取配置文件*/
        Class<?> clazz1 = Class.forName("com.zhangyi.demo.Person");

        /*  第二种静态属性class   一般用于当作静态方法的锁对象*/
        Class<?> clazz2 = Person.class;

        /*第三种 Object类的getClass()方法  一般用于判断两个对象是否是同一个字节码文件*/
        Person person = new Person();
        Class<?> clazz3 = person.getClass();

        /*判断三者是否相等 */
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz2 == clazz3);
        System.out.println(clazz1 == clazz3);
    }
}

输出结果:


true
true
true

因此,我们可以看到,通过这三种方式创建Person类的字节码文件都是一样的。

三、反射的部分用法

(一)通过字节码创建对象

1.通过无参的构造器创建对象:

    @Test
    public void test2() throws Exception {
        /* 1.获取字节码*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        /*2.调用字节码的newInstance()方法*/
        Person person = (Person) clazz.newInstance();
        person.name = "张仪";
        person.age = 21;
        //food为私有的变量,没提供get、set方法,不可直接调用,后续会通过反射机制进行暴力赋值
        person.show();
    }

输出结果:

我是张仪,今年21,今天吃了null

** 2.通过有参的构造器创建对象**

 @Test
    public void test3() throws Exception {
        /* 1.获取字节码的构造器 clazz.getConstructor(type.class) 因为在反射阶段操作的都是字节码,不知道具体的类型,只有在创建对象的时候才去给实际参数*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Constructor constructor = clazz.getConstructor(String.class, Integer.class);
        /* 2.通过构造器创建对象 调用构造器的newInstance方法并传入参数*/
        Person person = (Person) constructor.newInstance("张仪",21);
        person.show();
    }

输出结果:

我是张仪,今年21,今天吃了null

(二)获取字段

1.获取公共的字段:

    @Test
    public void test4() throws Exception{
        /*1.根据反射创建对象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.获取公共的字段*/
        Field name = clazz.getField("name");
        Field age = clazz.getField("age");
        /*3.设置person对象的属性*/
        name.set(person,"张仪");
        age.set(person,21);
        person.show();
    }

输出结果:

我是张仪,今年21,今天吃了null

2.获取私有的字段

    @Test
    public void test5() throws Exception{
        /*1.根据反射创建对象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.获取公共的字段 并且设置person对象的相应属性*/
        Field name = clazz.getField("name");
        Field age = clazz.getField("age");
        name.set(person,"张仪");
        age.set(person,21);
        /*3.通过暴力反射获取私有的字段 并且通过setAccessible方法去除私有权限 */
        Field food = clazz.getDeclaredField("food");
        food.setAccessible(true);
        //赋值
        food.set(person,"面条");
        person.show();
    }

输出结果:

我是张仪,今年21,今天吃了面条

(三)获取方法

1.获取公共的方法:

    @Test
    public void test6() throws Exception{
        /*1.根据反射创建对象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.获取公共的方法*/
        Method show = clazz.getMethod("show");

        /*3.调用invoke方法 若无参数则可以不用传值*/
        show.invoke(person);
    }

输出结果:

我是null,今年null岁,今天吃了null

2.获取私有的方法:

  @Test
    public void test7() throws Exception{
        /*1.根据反射创建对象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.通过暴力反射获取私有的方法 并且通过setAccessible方法去除私有权限  若有参数记得传参*/
        Method eat = clazz.getDeclaredMethod("eat",String.class);
        eat.setAccessible(true);
        /*3.调用invoke方法 若无参数则可以不用传值,有则记得传值*/
        eat.invoke(person,"面条");
    }

输出结果:

我今天吃了面条

(四)经典小案例

需求:如何绕过数组泛型检测

科普一个小知识点:java的泛型又被称之为假泛型,java的泛型仅在编译期有效,在运行期则会被擦除,即编译的.class文件中并没有泛型,也就是说所有的泛型参数类型在编译后都会被清除掉。这就是所谓的类型擦除。感兴趣可以自行编译一下查看class文件。

需求实现代码如下:

@Test
    public void  test8() throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
       // list.add("添加字符串会报错");

        /*获取ArrayList的字节码*/
        Class<?> clazz = Class.forName("java.util.ArrayList");
        /*获取add方法*/
        Method add = clazz.getMethod("add", Object.class);
        /*此时调用方法add 给list添加字符串*/
        add.invoke(list,"此时添加字符串不会报错");
        System.out.println(list);
    }

输出结果:

[1, 此时添加字符串不会报错]

以上就是我的部分见解。

发布了8 篇原创文章 · 获赞 0 · 访问量 649

猜你喜欢

转载自blog.csdn.net/weixin_42979871/article/details/102552345
今日推荐