likes
comments
collection
share

JAVA反射(基础详细版)

作者站长头像
站长
· 阅读数 73

1.反射

1.1 反射的概述

我们首先要知道什么是反射:

反射就是指在运行状态中,对于任意一个类,都能够知道并调用这个类的所有属性和方法。这种动态获取对象信息和动态调用对象方法的功能叫做Java反射。

可能上面的解释各位理解不了,那么我就通俗理解一下就是:

(1).利用反射创建的对象可以无视修饰符而调用里面的类里面的内容;

(2).可以跟配置文件结合使用,把要创建的对象信息和方法写入到配置文件里面;

1.2 学习反射到底是学习什么

反射都是从class字节码文件里面获得的内容

(1).如何获取class字节码文件对象

(2).利用反射如何获取构造方法(创建对象)

(3).利用反射如何获取成员变量(赋值,获取值)

(4).利用反射如何获取成员方法(运行)

我们本次的测试类是User类,它的源码如下:
package com.example.main;

public class User{
    private String name;
    private String passWord;
    public User(){}

    private User(String name){
        this.name = name;
    }

    public User(String name, String passWord) {
        this.name = name;
        this.passWord = passWord;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
    //重写
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", passWord='" + passWord + ''' +
                '}';
    }
}

1.3 获取class字节码文件对象的三种方式

package com.example.main;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException {
        //1.通过调用Class里面静态方法forName()
        //注意里面的参数应输入为“全类名”,即全类名=包名+类名
        //里面的clazz1就是Student这个类的字节码文件对象
        Class<?> clazz1 = Class.forName("com.example.main.User");
        //这里使用了泛型是为了增加灵活性和安全性,里面的?可以是任意类型。
        //这里源代码的获取阶段就是————先将User这个类加载到内存中之后,再获取User.class产生的字节码文件对象。


        //2.通过class属性获取
        Class<?> clazz2 = User.class;
        //熟悉Java栈堆的应该明白,虽然这里的两个字节码对象在栈里面地址不同,但是由于class文件在硬盘中是唯一的,因此加载到内存里面产生的对象也是唯一的。
        System.out.println(clazz2 == clazz1);//true

        //3.可以直接通过User类来获取字节码文件对象
        User user = new User();
        Class<?> clazz3 = user.getClass();
        System.out.println(clazz1 == clazz3);//true
        System.out.println(clazz2 == clazz3);//true
    }
}

说了半天大家可能还不知道什么是字节码文件和字节码文件对象,字节码文件就是通过Java文件(我们写的Java代码)编译以后的class文件(这个文件在硬盘上是可以找到的);而字节码文件对象则是class文件(即字节码文件)在加载到内存中之后,JVM会自动创建好它的对象,而且这个对象里面至少包含了构造函数、成员变量和成员方法。

我们获取的是什么?字节码文件对象,它在内存(不是硬盘)中是唯一的。

1.4 获取构造方法

package com.example.main;

import java.lang.reflect.Constructor;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
        //第一步肯定还是要先获得字节码文件对象
        Class<?> clazz = Class.forName("com.example.main.User");

        //1.获取所有的(public)构造方法,这里的数据类型必须用Constructor。
        //这里的Constructor里面的泛型也是为了增强安全性和灵活性
        Constructor<?>[] constructors1 = clazz.getConstructors();
        for (Constructor<?> c: constructors1) {
            System.out.println(c);
        }

        System.out.println("-----------------------------------");

        //2.获取所有的构造方法(包括private方法)。
        Constructor<?>[] constructors2 = clazz.getDeclaredConstructors();
        for (Constructor<?> c: constructors2) {
            System.out.println(c);
        }

        System.out.println("------------------------------------");

        //3.获取指定的空参构造
        Constructor<?> constructor3 = clazz.getConstructor();
        System.out.println(constructor3);
        //观察控制台打印的数据,你会发现什么?
        Constructor<?> constructor4 = clazz.getDeclaredConstructor(String.class,String.class);
        System.out.println(constructor4);

        Constructor<?> constructor5 = clazz.getConstructor();
        //之所以不相等是因为每次获得构造方法对象时,都会新New一个对象。
        System.out.println(constructor5 == constructor3);//false
    }
}

控制台打印的数据如下所示:

JAVA反射(基础详细版)

1.5 获取构造方法并创建对象

涉及到的方法为:newInstance

package com.example.main;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1.获取无参构造,并创建对象
        //先获取字节码文件对象
        Class<?> clazz1 = Class.forName("com.example.main.User");
        //获取空参构造方法
        Constructor<?> constructor = clazz1.getConstructor();
        //利用空参构造方法创建对象
        User user = (User) constructor.newInstance();
        System.out.println(user.toString());

        System.out.println("--------------------------------------");

        //2.获取有参构造,并创建对象
        //获得字节码文件对象
        Class<?> clazz2 = Class.forName("com.example.main.User");
        //获取有参构造方法
        Constructor<?> constructor1 = clazz2.getDeclaredConstructor(String.class);
        //临时修改构造方法的访问权限,即暴力反射
        constructor1.setAccessible(true);
        //直接创建对象
        User user1 = (User) constructor1.newInstance("李金轩");
        System.out.println(user1);
    }
}

控制台打印的结果如图所示:

JAVA反射(基础详细版)

1.6 获取成员变量

这个和获取构造方法的相关代码非常相似,直接看代码:

package com.example.main;

import java.lang.reflect.Field;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException,NoSuchFieldException {
        //获取字节码文件对象
        Class<?> clazz1 = Class.forName("com.example.main.User");
        //1.获取所有的public成员变量
        Field[] fields = clazz1.getFields();
        for (Field f: fields) {
            System.out.println(f);//User类我上面已经给了,由于都是私有变量,故这个打印结果肯定为空。
        }
        //2.获取所有的成员变量(public+private)
        Field[] fields1 = clazz1.getDeclaredFields();
        for (Field f:fields1) {
            System.out.println(f);
        }

        System.out.println("------------------------------");

        /*3.获取指定的成员变量对象
          如果获取的变量为私有,或者该变量不存在,则会抛出NoSuchFieldException异常。
          Field field = clazz1.getField("xiyan");
          System.oyt.println(field);
         */

        //4.获取单个成员变量(私有或公共都可以)
        Field field = clazz1.getDeclaredField("name");
        System.out.println(field);
    }
}

控制台打印结果为:

JAVA反射(基础详细版)

1.7 获取成员变量并修改值和获取值

在这个例子中,我将User(String name)构造函数的修饰符由private改为了public,代码示例:

package com.example.main;

import java.lang.reflect.Field;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //先实例化两个对象。
        User user1 = new User("xiyan");
        User user2 = new User("lijinxuan","21");

        //目的:通过获取成员变量来获取值并修改值
        //1.获取class对象
        Class<?> clazz1 = Class.forName("com.example.main.User");
        //2.获取成员变量对象
        Field field = clazz1.getDeclaredField("name");
        //3.暴力反射
        field.setAccessible(true);
        //4.设置name的值,此处第一个参数为要修改值的Java对象,第二个为要修改的值
        field.set(user1,"Xiyan");
        //5.获取指定对象的name的值
        String name = (String) field.get(user1);

        //直接打印结果可以得到
        System.out.println(name);
        System.out.println(user1);
        System.out.println(user2);
    }
}

对于上面的反射修改成员变量的值大家可能有点懵,正如我开始说的,反射是可以在运行状态中来动态调用类的属性或方法的,即先实例化对象之后,我们再用了反射来获取并修改Java对象的成员变量值。

上面的运行结果如下:

JAVA反射(基础详细版)

1.8 获取成员方法

在这个例子中,我将User类改为了如下所示:

package com.example.main;

public class User{
    private String getYongJieName(String name){
        if (!name.equals("jinxuan")){
            return "情况不对,披坚执锐";
        } else {
            return "金刚伏魔";
        }
    }
    public String getLOLName(String name){
        if (!name.equals("lijinxuan")){
            return "德玛西亚";
        } else {
            return "诺克萨斯";
        }
     }
     protected void getXl(){
        System.out.println("八嘎呀路");
     }
}

接下来就是主方法执行代码,如下所示:

package com.example.main;

import java.lang.reflect.Method;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.首先创建字节码文件对象
        Class<?> clazz = Class.forName("com.example.main.User");

        //2.获取方法
        //此处的getMethod()方法不仅获得了自己类内部的public方法,还可以获得父类中的public修饰的方法
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }

        System.out.println("=================================");

        //可以获得自己类内部的所有方法(私有+公共)
        Method[] methods1 = clazz.getDeclaredMethods();
        for (Method m: methods1) {
            System.out.println(m);
        }

        System.out.println("=================================");

        //获得单个指定的方法(空参)
        Method method1 = clazz.getDeclaredMethod("getXl");
        System.out.println(method1);
        //指定方法(有参)
        Method method2 = clazz.getMethod("getLOLName", String.class);
        System.out.println(method2);
        System.out.println("-----------------------------");
        //获取指定的私有方法
        Method method = clazz.getDeclaredMethod("getYongJieName", String.class);
        System.out.println(method);
    }
}

这是运行结果:

JAVA反射(基础详细版)

1.9 获取成员方法并将其运行

在这里我们需要使用invoke方法,即Object invoke(Object object,Object...args),我们直接看代码吧:

package com.example.main;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    //注意:千万别忘了抛出异常
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
         //1.先获取字节码文件对象
         Class<?> clazz = Class.forName("com.example.main.User");
         //2.获取类的实例化对象
         User user = new User();
         //3.获取指定的成员方法
         Method method1 = clazz.getDeclaredMethod("getYongJieName", String.class);
         Method method2 = clazz.getDeclaredMethod("getXl");
         //由于getYongJieName()方法是private修饰,因此虽然获得了成员方法对象method1,但如果想访问成员方法则需要暴力反射。
         method1.setAccessible(true);
         //4.运行
        //invoke方法的第一个参数就是实例化对象,第二个是方法参数。
        //如果方法有返回值则应该接收返回值。
        //这里我需要说的就是如果method1方法本身就运行时抛出异常,那么Invoke会将其封装为InvocationTargetException异常抛出。
        String s = (String) method1.invoke(user,"lijinxuan");
        //默认返回的是Object类型
        Object user1 = method1.invoke(user,"lijinxuan");
        System.out.println(user1);
        System.out.println(s);
        //没有返回值则不用。
        method2.invoke(user);
    }
}

运行结果:

JAVA反射(基础详细版)

转载自:https://juejin.cn/post/7336482843009237007
评论
请登录