Java中的反射机制

 🤨🤨🤨终于终于,把反射这一篇博客补过来了,初学时有点晕,现在,重新认真整理整理

1.反射的概念

1.1 定义:

Java程序运行期间,对于任意一个类都能得到该类的所有属性和方法,既然能得到,当然也可以修改部分信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。

说点人话,反射反射就是倒着反过来,正常来说,对于一个类,我们是定义者,定义类的属性和方法,而反射就是倒过来,在运行期间直接拿到该类的所有属性和方法。

再底层一点来说,反射机制的核心类就是Class类,我们知道, 我们每编写一个类,就会有一个.java文件,使用javac编译后就得到了被编译后的.class文件,而每一个.class都是Class类的对象

1.2 用途

首先, 反射机制是所有第三方框架的基础,反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理;

其次,在实际开发中,经常会遇到某个属性或方法是私有的,这时候,就可以利用反射机制通过反射来获取私有变量和方法

还有,我们知道 对象类型有运行时类型和编译时类型,我们常常要在运行时才能知道其真实类型,比如,Person p = new Student() ,这句代码中p在编译时类型为Person,运行时类型为Student,通过反射我们可以清楚的知道运行时到底是哪个类

编译时类型&运行时类型:

//        编译时类型&运行时类型
        Person p1 = new Person();
        System.out.println(p1.getClass());
        Person p2 = new Student();
        System.out.println(p2.getClass());

由结果就可以看到p1,p2的运行时类型到底是什么:

  

2.反射的四个核心类(Class,Constructor,Method, Field)

  • Class类——反射操作的核心,代表类的实体,应用反射的第一步就是获取Class类的对象 
  • Constructor类——与构造方法有关
  • Method类——与类中的方法有关
  • Field类——与类中属性相关

其中,Class类是核心第一步,Method类最为常用

额外说一句:所有和反射相关的包都在import java.lang.reflect 包下面

在介绍这四个类之前,我们先来做一些准备工作,创建两个类,Person类和Student类,其中,Student类是Person类的子类

Person类:

/**Person父类
 * @author sunny
 * @date 2022/04/14 17:45
 **/
public class Person {
//    三个成员变量,分别不同的权限
    public String name;
    int phone;
    private int age;

//    无参构造
    public Person() {
        name = "无参";
        phone = 0;
        age = 0;
        System.out.println("调用了父类无参构造方法");
    }

//    有参构造
    public Person(String name, int phone, int age) {
        this.name = name;
        this.phone = phone;
        this.age = age;
        System.out.println("调用了父类有参构造方法");
    }

    private void test() {
        System.out.println("调用了父类的test方法,private权限");
    }

    public void testPerson() {
        System.out.println("调用了父类的testPerson方法,public权限");
    }
}

Student类:

/**Student类
 * @author sunny
 * @date 2022/04/14 17:54
 **/
public class Student extends Person {
    public String studentName;
    int studentPhone;
    private int studentAge;

    public Student() {
        System.out.println("调用了子类的无参构造");
    }

    public Student(String studentName, int studentPhone, int studentAge) {
        this.studentName = studentName;
        this.studentPhone = studentPhone;
        this.studentAge = studentAge;
        System.out.println("调用了子类的有参构造方法");
    }

    private void fun() {
        System.out.println("调用了子类的fun方法,private权限");
    }

    public int getAge(int num) {
        System.out.println("调用了子类的getAge方法,public权限");
        return this.studentAge + num;
    }

    @Override
    public String toString() {
        return "Student{" +
                "studentName='" + studentName + '\'' +
                ", studentPhone=" + studentPhone +
                ", studentAge=" + studentAge +
                '}';
    }
}

2.1 Class类

正如上文写道,每一个类都对应一个Class类的对象(对象就是编译后的.class文件),所以,要应用反射机制获取一个类的所有属性和方法第一步就是获取其对应得Class类对象

🤨获取方式有三种!!!

  • 直接使用 类名称.class 来获取,注意只适合在编译期就明确要操作的Class,用法如下:
    //        方法一:直接类名获取
            Class cls1 = Student.class;
  • 通过该类的任意实例化对象调用 getClass()方法 ,用法如下:
    //        方法二:类对象的 getClass() 方法
            Class cls2 = new Student().getClass();
  •  使用 Class类提供的:Class.forName("类的全路径名"),全路径名就是 包名.类名
  •  注意此方法是静态方法,用的最多, 但可能抛出 ClassNotFoundException 异常
    //        方法三:Class.forName("类的全路径名")
            try {
                Class cls3 = Class.forName("practice_class.reflect.Student");
            }catch(ClassNotFoundException e){
                e.printStackTrace();
            }

无论哪种方式创建,指向的都是同一个对象:

//        无论哪种方式获取的,都指向同一个对象
        System.out.println(cls1 == cls2);

可以看到,输出结果为 true 

 

🤨通过反射创建类的实例:

直接使用newInstance方法

//        通过反射创建类的实例
//        先创建Class类对象cls
        Class<Student> cls = Student.class;
//        调用newInstance方法,默认调无参构造
        Student stu = cls.newInstance();

 由运行结果可以看到,默认调用的是无参构造函数

那么问题来啦,当无参构造不存在或者无参构造不可见,就会报错,或者说我们需要用该类的其他构造方法创建类的实例

那么这时候,就需要用到Constructor类了,下面我们来看Constructor类

2.2 Constructor类

先看主要方法:

具体怎么用,什么意思呢,让我们一个一个来分析一哈: 

(1)getConstructors()

        Class<Student> cls = Student.class;
        Constructor[] con = cls.getConstructors();
        System.out.println(Arrays.toString(con));

当我们修改其中一个构造方法为private时:

    public Student() {
        System.out.println("调用了子类的无参构造");
    }

    private Student(String studentName, int studentPhone, int studentAge) {
        this.studentName = studentName;
        this.studentPhone = studentPhone;
        this.studentAge = studentAge;
        System.out.println("调用了子类的有参构造方法");
    }

输出结果只剩下了共有的构造方法 

 

可以得到:getConstructors() ——只能获取当前类(不包括父类)的所有public构造方法

(2)getDeclaredConstructors()

不修改两个构造方法得访问权限,保持一个public,一个private,运行下面这段程序:

        Class<Student> cls = Student.class;
//        cls.getDeclaredConstructors()
        Constructor[] declared = cls.getDeclaredConstructors();
        System.out.println(Arrays.toString(declared));

可以得到:getDeclaredConstructors()——可以取到当前类(也不包括父类)的所有构造方法(包括私有方法)

(3)getDeclaredConstructor(参数类型.class…)

前两个都是获取构造方法的数组,当我们如果想要拿到某一个指定的构造方法时,用下面这种方法:

       Class<Student> cls = Student.class;
//        拿到指定的有参构造
        Constructor c = cls.getDeclaredConstructor(String.class,int.class,int.class);
//        用该构造方法实例化一个对象
//        如果该构造方法是私有的,我们要先用下面这句破坏封装
        c.setAccessible(true);
        Student s = (Student) c.newInstance("笨笨",123,333);
        System.out.println(s);

可以得到:要得到指定的某个构造方法,可以用getDeclaredConstructor(参数类型.class…)方法,小括号内就是指定构造方法的类型.class;

再用此构造方法创建对象时,如果此构造方法是私有的,要先破坏其封装性,怎么破坏呢,传入setAccessable方法的参数为true;

【注意】破坏封装性,仅对当前JVM进程中的这个对象生效,出了这个进程或是别的对象是不起作用的

构造方法可见,直接使用newInstance方法传参即可,注意强制类型转换

2.3 Method类

Method类对应类中方法的反射类,其实反射中的套路都差不多,这里,与上文的构造方法类似

ps:可以看到,后缀+s的就是获取到一定范围内的方法,返回Method数组,不加s的是获取某个指定的方法

(1) getMethods()方法——能拿到当前类及其父类的所有public方法

(2)getDelaredMethods()方法,仅能拿到当前类的所有方法(包含private方法)

这里不再赘述分析,直接上代码看两个方法的结果验证结论即可:

/**Method类的学习
 * @author sunny
 * @date 2022/04/15 08:22
 **/
public class Method_Test {
    public static void main(String[] args) throws Exception{
//        第一步一定是获取Class对象
        Class<Student> cls = Student.class;
//        复习一下刚学的,用指定的构造方法创建实例
        Constructor c = cls.getDeclaredConstructor(String.class,int.class,int.class);
        c.setAccessible(true);
        Student stu = (Student) c.newInstance("笨笨",6,8);
//        学习使用Method类
        Method[] m1 = cls.getMethods();
        System.out.println(Arrays.toString(m1));
        Method[] m2 = cls.getDeclaredMethods();
        System.out.println(Arrays.toString(m2));
    }
}

打印输出: 

唔…… getMethods()方法方法能打印当前类及其父类的所有Public方法,有点多,截图不完整

getDelaredMethods()方法能拿到当前类的所有方法,包含private方法,fun方法是私有的也能拿到

来个容易混淆的点:

如果我现在想拿到父类的一个private方法怎么办呢?

只能先拿到父类的Class对象,再调用getDeclaredMethods方法

【ps:】因为 getMethods()方法仅能拿到当前类及其父类的所有public方法,也不存在什么去破坏封装性拿到父类私有方法的可能,所以只能用父类的Class对象去解决

(3)重点来看下拿到指定的方法:

🤔🤔🤔首先来看成员方法的使用:

  • 得到Class对象
    Class<Student> cls = Student.class;
  • 得到指定的Method方法
    Method method = cls.getDeclaredMethod("方法名称",方法参数类型.class);
  • 如果此方法为private权限,仍然要先破坏封装
    method.setAccessible(true);
  • 创建类的实例对象
    //        复习一下刚学的,用指定的构造方法创建实例
            Constructor c = cls.getDeclaredConstructor(String.class,int.class,int.class);
            c.setAccessible(true);
            Student stu = (Student) c.newInstance("笨笨",6,8);
  • 执行该方法

    method.invoke(对象名,方法参数);

代码示例如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

/**Method类的学习
 * @author sunny
 * @date 2022/04/15 08:22
 **/
public class Method_Test {
    public static void main(String[] args) throws Exception{
//        第一步一定是获取Class对象
        Class<Student> cls = Student.class;
//        复习一下刚学的,用指定的构造方法创建实例
        Constructor c = cls.getDeclaredConstructor(String.class,int.class,int.class);
        c.setAccessible(true);
        Student stu = (Student) c.newInstance("笨笨",6,8);

//        学习使用Method类
//        因为fun方法是无参的,所以这里只需传入方法名称即可
        Method method = cls.getDeclaredMethod("fun");
//        fun方法是私有的,先破坏封装
        method.setAccessible(true);
//        执行该方法
        method.invoke(stu);

//        再看一个有参的方法调用,传入方法名称和参数类型.class
        Method m = cls.getDeclaredMethod("getAge",int.class);
        m.invoke(stu,100);
    }
}

看输出结果:


  🤔🤔🤔然后就是静态方法

静态方法与成员方法只两点不同

因为是静态所以无需创建对象实例;

因为无对象实例,所以invoke方法执行时,第一个参数为null,invole(null,方法参数)

import java.lang.reflect.Method;

/**
 * @author sunny
 * @date 2022/04/15 09:08
 **/
public class StaticMethod_Test {
    public static void main(String[] args) throws Exception{
//        获得Class对象
        Class<Student> cls = Student.class;
//        得到指定方法
        Method m = cls.getDeclaredMethod("staticTest",int.class);
//        执行该方法,第一个参数为null
        m.invoke(null,111);
    }
}

打印结果:

 最后来个小结:

(1)获取Class对象

(2)

(3)如果是成员方法,先产生类实例,再invoke(对象名,传入的方法参数)

         如果是静态方法,直接invoke(null,传入的方法参数)

(4)当然如果方法权限是不可见的,先破坏封装

2.4 Field类 

Field类是描述类的属性的反射类

跟Method方法大差不差,一个获取方法,一个获取属性而已

该表描述不大准确,Field同Method一样: 

(1) getFields()方法——能拿到当前类及其父类的所有public属性

 (2)getDelaredFields()方法,仅能拿到当前类的所有属性(包含private属性)

(3)getField("属性名称")——能拿到当前类及其父类的所有public属性中指定的属性

 (4)getDelaredField("属性名称"),能拿到当前类的所有属性中的指定属性

🤔🤔🤔既然能拿到属性,那必然也是能修改属性值得呀~

import java.lang.reflect.Field;

/**
 * @author sunny
 * @date 2022/04/15 09:20
 **/
public class Field_Test {
    public static void main(String[] args) throws Exception{
        Class cls = Class.forName("practice_class.reflect.Student");
        Field field = cls.getDeclaredField("studentAge");
        field.setAccessible(true);
//        创建对象实例
        Student stu = (Student) cls.newInstance();
//       设置属性值为99
        field.set(stu,99);
        System.out.println(stu);
    }
}

 看结果:

yes,修改成功啦!

终于反射总结完了,套路真真都一样,基本反射原理及用法就是这样得啦!

具体反射在第三方框架里得应用我们框架学习中再来品味😊😊😊😊😊 

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨笨在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值