Java反射实践:从反射中理解class

简介: 写在前面今天在需求评审的时候,遇到了挺有意思的要求。需求是什么样子就不说了。总之完成这个需求需要一个调用系统api的操作。然而这个api因为并不稳定的原因。

写在前面

今天在需求评审的时候,遇到了挺有意思的要求。需求是什么样子就不说了。总之完成这个需求需要一个调用系统api的操作。然而这个api因为并不稳定的原因。被谷歌hide掉了。
这个时候我们最直接的方式就是去通过反射去调用这个系统api。(当然这种方式治标不治本,因为既然被hide,就说明这个api很不稳定。所以这个版本可以用,有可能下个版本就没了)
不过这里我们不考虑这个问题,因为如题所说,这次主角是反射。
之前在用反射的时候,其实并没有去思考反射所带来的东西。而这次在用反射的时候,开始去思考为什么要这么用,它和Java的运行机制有什么关系。
我们在看JVM的时候:多少都能了解到Java的类加载机制,那就是我们在new一个对象的时候,JVM会先使用双亲委派机制去加载这个class文件,class文件的代码结构会被封装成Class对象唯一的出现在方法区中。然后才是我们的实例对象创建到堆中。
之前我去上述的理解没有任何概念,仅仅是当做文字给记了下来。直到我今天在使用反射的时候,突然对上述的概念有了实质性的认识。
不过不着急,让我们一点点的深入....

进入正文

声明一个类

首先我们先声明一个class,内部包含了另一个对象,并且写了一些私有/公有/静态的变量及方法,一会将针对这个class进行反射的操作:

这里的日志使用了Log是Android里的打印输出,而非Java中的System.out.print。

public class ReflectModel {
    private ReflectBean mReflectBean;
    private String mContent = "A";
    public int mNum = 1;
    public static int sNum = 666;

    public ReflectModel() {
        LogUtils.d(TAG, "ReflectModel(),无参构造方法执行");
    }

    public ReflectModel(String content) {
        LogUtils.d(TAG, "ReflectModel(String content),一个String参数的构造方法执行");
        mContent = content;
    }

    private ReflectModel(String content, int num) {
        LogUtils.d(TAG, "ReflectModel(String content,int num),俩个参数的私有构造方法执行");
        mContent = content;
        mNum = num;
    }

    public void fun() {
        LogUtils.d(TAG, "我就是一个方法,在本例子中。我是被反射生成的对象调用的->mContent:" + mContent + "-mNum:" + mNum);
    }

    private void printContent() {
        LogUtils.d(TAG, "我就是私有的打印方法->mContent:" + mContent + "-mNum:" + mNum);
    }

    private void setContent(String content) {
        LogUtils.d(TAG, "我就是一个带有一个String参数的私有方法->setContent:" + content);
        mContent = content;
    }

    public void printBean() {
        LogUtils.d(TAG, "我是ReflectBean的打印方法->mName:" + mReflectBean.getName());
    }

    public static void staticFun() {
        LogUtils.d(TAG, "我是静态方法staticFun");
    }
}

内部的ReflectBean类

public class ReflectBean {
    private String mName = "a";

    public ReflectBean(String name) {
        mName = name;
    }

    public String getName() {
        return mName;
    }
}

获取拥有真正对象代码结构的Class:

不知道各位小伙伴们有没有思考过,为什么我们new一个类的时候这么方便,反而到了反射的时候会如此的麻烦?

public void fun() {
        //第一种方式获取Class对象:产生一个Student对象的实例,以及一个唯一的Class对象。
        ReflectModel model1 = new ReflectModel();
        //获取Class对象:这种方式没什么意义,既然有了对象的实例,何必再去反射
        Class modelClass = model1.getClass();

        //第二种方式获取Class对象:需要我们导包,但是有些时候这个类是隐藏的(比如很多系统不稳定的类,@Hide)
        Class modelClass2 = ReflectModel.class;

        Class modelClass3 = null;
        try {
            //第三种方式获取Class对象:类的全路径
            modelClass3 = Class.forName("com.example.mbenben.studydemo.basenote.reflect.ReflectModel");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

将Class实例化


        //开始进行具体内部操作,此时我们仅仅是拿到了ReflectModel的class这个对象,而非是这个ReflectModel对象的实例
        if (modelClass3 != null) {
            //获取ReflectModel.class对应的所有构造方法(public的)
            Constructor[] constructors = modelClass3.getConstructors();
            for (Constructor constructor : constructors) {
                LogUtils.d(TAG, "当前获取的构造方法:" + constructor.getName());
            }
            LogUtils.d(TAG, "----以上是public的----");
            //所有构造方法(包括:私有、受保护、默认、公有)
            constructors = modelClass3.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                LogUtils.d(TAG, "当前获取的构造方法:" + constructor);
            }

            Object object = null;
            try {
                // 获取共有无参构造函数
                Constructor con = modelClass3.getConstructor(null);
                // 调用此无参构造方法,那么此时我们就会获取到Reflect的实例对象了(默认返回object)
                ReflectModel reflectModel = (ReflectModel) con.newInstance();
                // 有了ReflectModel对象实例,我们就可以正常使用了
                /**
                 * 但是此时我们知道,我们强制类型转成了我们想要的类型,但是我们上文中提到,有些类是hide的。
                 * 因此我们很多情况下,我们被限制只能得到object对象。
                 */
                reflectModel.fun();
                // 获取私有的含有String和int参数的构造方法
                con = modelClass3.getDeclaredConstructor(new Class[]{String.class, int.class});
                // 此时我们获取到了这个private的含参构造方法对象,但是因为private权限原因,我们没办法直接调用newInstance()
                // 我们需要调用下面的方法,无视private修饰符(调用后,我们就可以执行private的构造方法)
                con.setAccessible(true);
                //调用私有俩参构造方法
                object = con.newInstance("B", 2);

反射调用方法

                //上诉提到,如果我们反射的类是hide,此时我们肯定没办法把Object转成ReflectModel类型。因此,我们拿到这个实例后想要调用其方法,还需要使用反射的方式
                // private方法应该使用getDeclaredMethod()去获取
                Method printContent = modelClass3.getDeclaredMethod("printContent", new Class[]{});
                //同样因为private的原因,我们在执行方法时,要先清除权限问题
                printContent.setAccessible(true);
                //使用object实例对象,调用printContent方法,因为没有参数,所以传null
                printContent.invoke(object, null);
                //生成名为setContent的含有一个String参数的方法对象
                Method setContent = modelClass3.getDeclaredMethod("setContent", new Class[]{String.class});
                setContent.setAccessible(true);
                //调用setContent方法,去改变mContent的值
                setContent.invoke(object, "C");
                //再次执行打印操作
                printContent.invoke(object, null);

                //调用静态方法
                Method staticMethod = modelClass3.getMethod("staticFun", new Class[]{});
                //因为静态方法属于类,所以我们不需要传实例对象,因为它在class被加载的时候,就已经被创建了。
                staticMethod.invoke(null, null);

反射调用变量/static

                /**
                 * 反射调用变量
                 */
                //获取所有public变量封装的Field对象
                Field[] fields = modelClass3.getFields();
                //获取所有变量封装的Field对象
                fields = modelClass3.getDeclaredFields();

                //获取private的mContent的变量
                Field content = modelClass3.getDeclaredField("mContent");
                content.setAccessible(true);
                Field num = modelClass3.getField("mNum");
                num.set(object, 3);
                //获取static变量
                Field sNum = modelClass3.getField("sNum");
                LogUtils.d(TAG, "mNum在object实例对象中的值:" + num.get(object) + "-static变量sNum的值:" + sNum.get(null));
                //将object实例的mContent对象,设置为D
                content.set(object, "D");
                printContent.invoke(object, null);

                Field reflectBean = modelClass3.getDeclaredField("mReflectBean");
                Class reflectBeanClass = Class.forName("com.example.mbenben.studydemo.basenote.reflect.ReflectBean");
                Constructor beanCon = reflectBeanClass.getConstructor(new Class[]{String.class});
                Object beanObject = beanCon.newInstance("b");
                //给mReflectBean赋值
                reflectBean.setAccessible(true);
                reflectBean.set(object, beanObject);
                Method printBean = modelClass3.getMethod("printBean", new Class[]{});
                printBean.invoke(object, null);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

继续理解

我们知道JVM在使用一个类的时候,会先去加载这个类。也就是生成唯一的Class对象。这个Class对象拥有我们的java代码的变量,方法结构。但是它并不是一个实例。因此我们在反射的时候,要先获取构造方法对象,也就是Class返回给我们的Constructor。此时我们运行这个对象的newInstance,我们就初始化了这个Class,获取了这个Class的实例。(这也就是为什么我们反射操作会如此的麻烦)
实例化了这个对象之后,虽然只是一个Object对象,但是它和我们真正new的对象没有任何区别,那么此时我们就可以正常的调用方法了。
我们知道,static是属于类,在类被加载的时候就已经出现了。那么此时,反射也侧面证实了这个问题:
在我们反射调用static的变量和方法时,set或者invoke的参数传的是null,也就是说我们没有传递任何对象实例,所以此时我们使用的是这个在类被加载时就被创建的Class对象中的变量和方法,而非操作的实例对象。因此,对static是不是有了一个更为深刻的理解呢?

尾声

时隔很久都还没有写博客了,是因为自己的确不知道该写些写什么,唉,迷茫,好菜...
希望可以对各位看官有所帮助吧。


我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

img_89788b3a8f3f86257453cbc8264959f6.png
个人公众号:IT面试填坑小分队
目录
相关文章
|
7天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
10天前
|
监控 算法 Java
掌握Java的垃圾回收机制:从原理到实践
在Java的世界中,垃圾回收(Garbage Collection,简称GC)是一块神秘的领域,它如同一位默默无闻的清洁工,确保内存中不再使用的对象得到妥善处理。本文将带你走进垃圾回收的大门,探索它的工作原理、常见算法及其在实际应用中的调优策略。无论你是初学者还是有一定经验的开发者,这篇文章都将为你揭开垃圾回收的神秘面纱,让你的Java程序运行得更加高效和稳定。
25 5
|
13天前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
17天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
17天前
|
Java UED 开发者
Java中的异常处理:理解与实践
【9月更文挑战第3天】在Java编程中,异常处理是保持程序健壮性的关键。本文将引导你了解Java的异常机制,从基本的try-catch结构到自定义异常类的创建,以及如何优雅地处理异常情况。我们将一起探讨异常处理的最佳实践,并学习如何在代码中实现它们,以确保你的应用程序能够优雅地处理运行时错误。
13 2
|
20天前
|
Java 调度
Java中的多线程基础与实践
【8月更文挑战第31天】本文将深入浅出地讲解Java中多线程的基础知识,并通过实例展示如何在Java程序中实现多线程。我们将从多线程的基本概念出发,逐步深入到线程的创建、控制以及同步机制,最后通过一个简易版的生产者消费者模型来实践这些知识点。文章旨在帮助初学者快速掌握多线程编程的关键技能,并理解其背后的原理。
|
11天前
|
Java 数据库连接 开发者
Java中的异常处理:理解与实践
【9月更文挑战第9天】在Java编程的海洋里,异常处理是一艘不可或缺的救生艇。它不仅保护你的代码免受意外错误的侵袭,还能确保你的应用在遇到困难时能优雅地继续航行。本文将带你深入了解Java的异常处理机制,通过浅显易懂的方式,让你掌握如何捕捉和处理异常,以及如何自定义异常类型来应对特定的业务需求。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的知识和技巧,让你的代码更加健壮和可靠。
|
21天前
|
Java 程序员
Java中的异常处理:理解与实践
【8月更文挑战第31天】本文将引导你深入理解Java中的异常处理机制,并通过实例展示如何在代码中有效地应用try-catch块来捕捉和处理异常。我们将从基本概念入手,逐步深入到异常的分类、创建自定义异常以及最佳实践。通过本文,你将学会如何编写健壮的Java代码,确保程序在遇到错误时能够优雅地恢复或通知用户。让我们一起探索如何在Java世界中驾驭那些不期而遇的挑战吧!
|
21天前
|
Java 数据库连接 API
Java中的异常处理:理解、实践与最佳实践
【8月更文挑战第31天】在Java编程的世界中,异常处理是保持代码健壮性的关键。本文将通过浅显易懂的方式,带你了解Java异常处理的基本概念,并通过具体示例展示如何有效管理异常。我们将一起学习如何使用try-catch语句来捕获和处理异常,以及finally块的重要性。同时,我们还将探讨一些最佳实践,帮助你编写更加稳定和可靠的Java代码。无论你是Java新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
21天前
|
设计模式 安全 Java
Java中的单例模式:理解与实践
【8月更文挑战第31天】在软件设计中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨Java中实现单例模式的不同方法,包括懒汉式、饿汉式、双重校验锁以及静态内部类等方法。每种方法都有其适用场景和潜在问题,我们将通过代码示例来展示如何根据具体需求选择合适的实现方式。