Java 反射相关知识

2022/3/26 11:22:35

本文主要是介绍Java 反射相关知识,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

我们都知道一个普通的类,有一个唯一的全限定类名用以标识这个类,还可以有代码块、构造方法、成员变量和成员方法,其中代码块、成员变量和成员方法又可以被关键字<b>static</b>修饰,同时一个类还可以继承自一个父类,以及多个接口,甚至还有内部类和注解。不仅如此,一个类中可以添加注解的地方还有很多,比如构造方法、成员变量、成员方法、成员方法中的参数,甚至连局部变量都有可能被添加上注解。你以为这就完了?还有内部类没有考虑呢,内部类又可以分布在多个地方,比如声明在一个类内部,甚至声明在一个成员方法内!简直不要太复杂,妈妈听了都直言太难了。

何为反射

讲了那么多,反射是什么啊?为什么要有反射啊?知乎上有人说反射是一种能够在程序运行时动态访问、修改某个类中任意属性(状态)和方法(行为)的机制(包括private实例和方法);廖雪峰老师说反射就是 Reflection ,Java的反射是指程序在运行期可以拿到一个对象的所有信息,是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法;接下来该我了? ... 反射是在程序运行时,能够通过全限定类名动态获取指定类的类型信息,进而可以实例化它、执行它的任意方法、直接修改它的任意属性以及获取其他与此类相关的信息,诸如它的父类、它继承的所有接口、它的任意位置的注解信息以及其所声明的内部类等等上面所提及到的与类相关的信息。
那这有什么用啊?用处可大了,能看到这篇文章的人,总是会接触到 Spring 的。我们知道在没有使用任何框架的情况下,要想实例化一个对象,最常用的就是在你的源码里面使用 <b>new</b> 关键字,但这样就必须知道自己实例化的对象属于哪个类,而且只能是在编辑源码的时候作出指示,简单点来说就是只能在程序运行前在编辑源代码时在合适的位置使用 <b>new</b> 关键字将其实例化,这个要求什么呢,那就是源码能够修改。
Spring 作为一种几乎可以应用到所有 Java 应用的框架,它难道会提前预知任何开发者要写的业务类吗?连是什么类都不知道,如何使用 <b>new</b> 关键字将其实例化出来呢?何况 Spring 作为一个已成型的框架,其所有程序文件均已是字节码文件,我们并不能直接修改它的源码 。但是用过 Spring 的小伙伴们都知道,Spring 可以接管几乎所有类的实例化,你只需要指定一个全限定类型名,它马上就可以返回一个对应实例对象给你,那既然源码无法修改,也即意味着无法在源代码中手动 <b>new</b> 出相应的实例对象,那么问题来了,Spring 如何在不修改源代码和不知道具体的业务类时,是如何实例化出你所给定的任意类的实例对象的呢?聪明人都看到了,通过反射可以在程序运行时,通过全限定类名动态获取指定类的类型信息,进而可以实例化它,亦即获得它的实例对象。、

如何反射

获取类型信息

反射第一步,都是获取指定类的类型信息,也就是获取某个类的 Class 对象,主要有三种方法,直接示例:

package cn;

public class TestClass {

    public static void main(String[] args) throws Exception {
        
        // 通过全限定类型名获取
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 直接获取
        Class<?> classInfo_2 = TestClass.class;

        // 先实例化一个对象,在利用 Object 类里的 getClass 方法获取
        TestClass testClass = new TestClass();
        Class<?> classInfo_3 = testClass.getClass();
    }
}

只要有了 Class 对象,我们就可以对这个类为所欲为了,这词怪怪的... 接下来将分别开始分类介绍类的构造器、成员变量、成员方法、内部类和注解信息的获取。

获取构造器

想想构造器有什么用呢?构造器对应的就是一个类的构造方法,什么意思呢?意思就是我可以利用构造器实例化一个对象!来呀,直接看实例,分情况讨论,在正式开始之前,先来认识 Class 对象里的几个有关构造器的方法:

public Constructor<?>[] getConstructors();
public Constructor<T> getConstructor(Class<?>... parameterTypes);

public Constructor<?>[] getDeclaredConstructors();
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);

记住这个 Declared 的字面意思,因为后面常出现,它的意思的(本类)已声明的,来咯,上菜!

package cn;

import java.lang.reflect.Constructor;

public class TestClass {

    private int a;

    private int b;

    public TestClass() {
    }

    public TestClass(int a) {
        this.a = a;
    }

    private TestClass(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public String toString() {
        return "a = " + a + "; b = " + b;
    }

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

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 获取类的所有公开的构造方法
        for (Constructor<?> constructor : classInfo_1.getConstructors()) {
            System.out.println(constructor);
        }
        System.out.println();

        // 获取指定参数类型的公开构造方法
        System.out.println(classInfo_1.getConstructor(int.class));

        try {
            // 试图使用 getConstructor 方法获取私有的构造方法:
            System.out.println(classInfo_1.getConstructor(int.class, int.class));
        } catch (NoSuchMethodException e) {
            System.out.println("试图使用 getConstructor 方法获取私有的构造方法失败:" + e.getMessage());
        } catch (SecurityException e) {
            System.out.println("试图使用 getConstructor 方法获取私有的构造方法失败:" + e.getMessage());
        }

        // 获取类的所有构造方法,不管是公开的还是私有的
        for (Constructor<?> constructor : classInfo_1.getDeclaredConstructors()) {
            System.out.println(constructor);
        }
        System.out.println();

        // 获取指定参数类型的任意构造方法
        System.out.println(classInfo_1.getDeclaredConstructor());
        System.out.println(classInfo_1.getDeclaredConstructor(int.class));

        // 试图使用 getDeclaredConstructor 方法获取私有的构造方法
        System.out.println(classInfo_1.getDeclaredConstructor(int.class, int.class));
    }
}

运行结果:
public cn.TestClass(int)
public cn.TestClass()

public cn.TestClass(int)
试图使用 getConstructor 方法获取私有的构造方法失败:cn.TestClass.<init>(int, int)
private cn.TestClass(int,int)
public cn.TestClass(int)
public cn.TestClass()

public cn.TestClass()
public cn.TestClass(int)
private cn.TestClass(int,int)

总结一下:
// 获取指定类的所有 public 修饰(即公开)的构造方法
public Constructor<?>[] getConstructors();

// 获取指定类的指定 public 修饰(即公开)的构造方法,以参数辨别
public Constructor<T> getConstructor(Class<?>... parameterTypes);

// 获取指定类所声明的所有任意修饰符修饰(即无论 public / private / protected / default)的构造方法
public Constructor<?>[] getDeclaredConstructors();

// 获取指定类所声明的指定任意修饰符修饰(即无论 public / private / protected / default)的构造方法,以参数辨别
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);

实例化对象

获取了构造器对象后,我们就可以进行对象的实例化,示例,来个有难度的,以私有构造方法实例化一个对象(在上述代码不变的情况下只修改 main 函数如下所示):

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

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 获取该类的私有构造器
        Constructor<?> constructor = classInfo_1.getDeclaredConstructor(int.class, int.class);

        // 开始实例化
        // 由于其修饰符并不是公开的,所以我们在实例化之前,必须先设置其可访问,否则实例化失败
        constructor.setAccessible(true);
        // 传入相应的参数开始实例化
        Object obj = constructor.newInstance(1, 2);
        // 打印辨别
        System.out.println(obj.toString());
        System.out.println(obj instanceof TestClass);
    }

    运行结果:
    a = 1; b = 2
    true

除此之外,如果某个类有公开的无参构造函数,则有一个更简洁的实例化方法,如下所示:

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

        // 通过全限定类型名获取类信息
        Class<?> classInfo_1 = Class.forName("cn.TestClass");

        // 直接通过类对象实例化,前提是该类有公开的无参构造函数
        Object obj = classInfo_1.newInstance();

        // 打印辨别
        System.out.println(obj.toString());
        System.out.println(obj instanceof TestClass);
    }

    运行结果:
    a = 1; b = 2
    true

执行指定方法

首先,通过 Class 对象获取对应的方法实例对象(我们能简称其为方法器吧),同样的,获取方法器有几个类似获取构造器的方法:

public Method[] getMethods();
public Method getMethod(String name, Class<?>... parameterTypes);

public Method[] getDeclaredMethods();
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);

为了更全面地演示,我们给上面的 TestClass 增加了父类,并添加了各种修饰符修饰的方法,我们实践出真知:

    package cn;

    import java.lang.reflect.Method;

    class Super {
        public void super_public(){
            System.out.println("super_public");
        }

        private void super_private(){
            System.out.println("super_private");
        }

        protected void super_protected(){
            System.out.println("super_protected");
        }

        void super_default(){
            System.out.println("super_default");
        }
    }

    public class TestClass extends Super {

        private int a;

        private int b;

        public TestClass() {
        }

        public TestClass(int a) {
            this.a = a;
        }

        private TestClass(int a, int b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public String toString() {
            return "a = " + a + "; b = " + b;
        }

        public void a_public(){
            System.out.println("a_public");
        }

        private void a_private(){
            System.out.println("a_private");
        }

        protected void a_protected(){
            System.out.println("a_protected");
        }

        void a_default(){
            System.out.println("a_default");
        }

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

            // 通过全限定类型名获取类信息
            Class<?> classInfo_1 = Class.forName("cn.TestClass");

            // 直接通过类对象实例化,前提是该类有公开的无参构造函数
            Object obj = classInfo_1.newInstance();

            for (Method method : classInfo_1.getMethods()) {
                System.out.println(method);
            }
            System.out.println();

            for (Method method : classInfo_1.getDeclaredMethods()) {
                System.out.println(method);
            }
            System.out.println();
        }
    }

    运行结果:
    public static void cn.TestClass.main(java.lang.String[]) throws java.lang.Exception
    public java.lang.String cn.TestClass.toString()
    public void cn.TestClass.a_public()
    public void cn.TestClass.super_public()
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()

    public static void cn.TestClass.main(java.lang.String[]) throws java.lang.Exception
    public java.lang.String cn.TestClass.toString()
    public void cn.TestClass.a_public()
    void cn.TestClass.a_default()
    public void cn.TestClass.super_public()
    private void cn.TestClass.a_private()
    protected void cn.TestClass.a_protected()

太多了,估计也看不下去,直接解释一波,这里获取方法器的方法作用可以说是和获取构造器一摸一样,总结如下:

// 获取所有公开方法,包括父类的
public Method[] getMethods();

// 获取指定的公开方法
public Method getMethod(String name, Class<?>... parameterTypes);

// 获取本类中所声明的所有方法,无论是什么修饰符修饰的
public Method[] getDeclaredMethods();

// 获取本类中所声明的任意指定方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);

同理,成功获取到方法器后就可以执行方法了,同样,我们执行一个被 private 修饰符修饰的私有方法,示例如下:

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

    // 通过全限定类型名获取类信息
    Class<?> classInfo_1 = Class.forName("cn.TestClass");

    // 直接通过类对象实例化,前提是该类有公开的无参构造函数
    Object obj = classInfo_1.newInstance();

    // 获取私有方法的方法器,第一个参数是方法名,第二个可变参数是方法参数类型,有多少写多少,没有不写
    Method method = classInfo_1.getDeclaredMethod("a_private");

    // 开始执行方法
    // 同样要设置任何非公开方法为可访问,否则出错
    method.setAccessible(true);
    // 效果等同于 obj.a_private() ,第一个参数是该类的一个实例对象,第二个可变参数是实参
    method.invoke(obj);
}

运行结果:a_private


这篇关于Java 反射相关知识的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程