Java基础 反射详解

news2024/11/20 18:24:15

目录

简介     

反射的基本使用

获取 Class 对象的四种方式

基本使用示例

常用方法

生产中的常用方式

获取注解

SpringIoc容器的制作

反射 + 抽象工厂模式

双亲委派

反射缺点


前言-与正文无关

        生活远不止眼前的苦劳与奔波,它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中,我们往往容易陷入工作的漩涡,忘记了停下脚步,感受周围的世界。让我们一起提醒自己,要适时放慢脚步,欣赏生活中的每一道风景,享受与家人朋友的温馨时光,发现那些平凡日子里隐藏的幸福时刻。因为,这些点点滴滴汇聚起来的,才是构成我们丰富多彩生活的本质。希望每个人都能在繁忙的生活中找到自己的快乐之源,不仅仅为了生存而工作,更为了更好的生活而生活。

        送你张美图!希望你开心!

简介     

        像咱们平时大部分时候都是在写业务代码(码农),很少会接触到直接使用反射机制的场景,起码我是很少接触。但是呢首先这个是个面试重点,其实这是个很厉害的东西,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。   

        反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。反射能够根据类的字节码文件, 反射类的基本信息,类的成员(字段,方法,构造方法)等内容, 动态的创建对象,调用方法的技术 。

反射的基本使用

Java 反射的主要组成部分有4个:

  • Class:任何运行在内存中的所有类都是该 Class 类的实例对象,每个 Class 类对象内部都包含了本来的所有信息
  • Field:描述一个类的属性,内部包含了该属性的所有信息,例如数据类型,属性名,访问修饰符······
  • Constructor:描述一个类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符······
  • Method:描述一个类的所有方法(包括抽象方法),内部包含了该方法的所有信息,与Constructor类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的。

      

获取 Class 对象的四种方式

如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:

1. 知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

2. 通过 Class.forName()传入类的全路径获取:

Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");

3. 通过对象实例instance.getClass()获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

4. 通过类加载器xxxClassLoader.loadClass()传入类路径获取:

通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行

ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");

基本使用示例

创建一个我们要使用反射操作的类 TargetObject

package cn.javaguide;

public class TargetObject {
    private String value;

    public TargetObject() {
        value = "JavaGuide";
    }

    public void publicMethod(String s) {
        System.out.println("I love " + s);
    }

    private void privateMethod() {
        System.out.println("value is " + value);
    }
}

使用反射操作这个类的方法以及参数 

package cn.javaguide;

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

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
        /**
         * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
         */
        Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");
        TargetObject targetObject = (TargetObject) targetClass.newInstance();
        /**
         * 获取 TargetObject 类中定义的所有方法
         */
        Method[] methods = targetClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }

        /**
         * 获取指定方法并调用
         */
        Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
                String.class);

        publicMethod.invoke(targetObject, "JavaGuide");

        /**
         * 获取指定参数并对参数进行修改
         */
        Field field = targetClass.getDeclaredField("value");
        //为了对类中的参数进行修改我们取消安全检查
        field.setAccessible(true);
        field.set(targetObject, "JavaGuide");

        /**
         * 调用 private 方法
         */
        Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
        //为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);
        privateMethod.invoke(targetObject);
    }
}

输出内容: 

publicMethod
privateMethod
I love JavaGuide
value is JavaGuide

常用方法

Declared修饰的方法:可以获取该类内部包含的所有变量、方法和构造器,但是无法获取继承下来的信息

Declared修饰的方法:可以获取该类中public修饰的变量、方法和构造器,可获取继承下来的信息

获取类中的变量(Field)

  • Field[] getFields():获取类中所有被public修饰的所有变量
  • Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
  • Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
  • Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量

获取类中的方法(Method)

  • Method[] getMethods():获取类中被public修饰的所有方法

  • Method getMethod(String name, Class...<?> paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰

  • Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法

  • Method getDeclaredMethod(String name, Class...<?> paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法

获取类的构造器(Constructor)

  • Constuctor[] getConstructors():获取类中所有被public修饰的构造器
  • Constructor getConstructor(Class...<?> paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
  • Constructor[] getDeclaredConstructors():获取类中所有构造器
  • Constructor getDeclaredConstructor(class...<?> paramTypes):根据参数类型获取对应的构造器

new 和newInstance() 区别

newInstance():弱类型,效率低,只能调用无参构造

new():强类型,高效率,能调用任何public构造器

生产中的常用方式

举几个我们都知道例子说明他的使用

获取注解

一般你看生产项目内你自定义个注解这个可能用的比较多!

获取注解单独拧了出来,因为它并不是专属于 Class 对象的一种信息,每个变量,方法和构造器都可以被注解修饰,所以在反射中,Field,Constructor 和 Method 类对象都可以调用下面这些方法获取标注在它们之上的注解。

  • Annotation[] getAnnotations():获取该对象上的所有注解
  • Annotation getAnnotation(Class annotaionClass):传入注解类型,获取该对象上的特定一个注解
  • Annotation[] getDeclaredAnnotations():获取该对象上的显式标注的所有注解,无法获取继承下来的注解
  • Annotation getDeclaredAnnotation(Class annotationClass):根据注解类型,获取该对象上的特定一个注解,无法获取继承下来的注解

当然也是有条件的,只有注解的@Retension标注为RUNTIME时,才能够通过反射获取到该注解,@Retension 有3种保存策略:

  • SOURCE:只在**源文件(.java)**中保存,即该注解只会保留在源文件中,编译时编译器会忽略该注解,例如 @Override 注解
  • CLASS:保存在字节码文件(.class)中,注解会随着编译跟随字节码文件中,但是运行时不会对该注解进行解析
  • RUNTIME:一直保存到运行时用得最多的一种保存策略,在运行时可以获取到该注解的所有信息

SpringIoc容器的制作

在 Spring 中,经常会编写一个上下文配置文件applicationContext.xml,里面就是关于bean的配置,程序启动时会读取该 xml 文件,解析出所有的 <bean>标签,并实例化对象放入IOC容器中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="smallpineapple" class="com.bean.SmallPineapple">
        <constructor-arg type="java.lang.String" value="小菠萝"/>
        <constructor-arg type="int" value="21"/>
    </bean>
</beans>

当然springboot也是一样的原理,只不过他不用xml而是yuml文件罢了。

在定义好上面的文件后,通过ClassPathXmlApplicationContext加载该配置文件,程序启动时,Spring 会将该配置文件中的所有bean都实例化,放入 IOC 容器中,IOC 容器本质上就是一个工厂,通过该工厂传入 <bean> 标签的id属性获取到对应的实例。

public class Main {
    public static void main(String[] args) {
        ApplicationContext ac =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple");
        smallPineapple.getInfo(); // [小菠萝的年龄是:21]
    }
}

Spring 在实例化对象的过程经过简化之后,可以理解为反射实例化对象的步骤:

  • 获取Class对象的构造器
  • 通过构造器调用 newInstance() 实例化对象

当然 Spring 在实例化对象时,做了非常多额外的操作,才能够让现在的开发足够的便捷且稳定

反射 + 抽象工厂模式

传统的工厂模式,如果需要生产新的子类,需要修改工厂类,在工厂类中增加新的分支

public class MapFactory {
    public Map<Object, object> produceMap(String name) {
        if ("HashMap".equals(name)) {
            return new HashMap<>();
        } else if ("TreeMap".equals(name)) {
            return new TreeMap<>();
        } // ···
    }
}

反射 + 抽象工厂的核心思想是:

  • 在运行时通过参数传入不同子类的全限定名获取到不同的 Class 对象,调用 newInstance() 方法返回不同的子类。细心的读者会发现提到了子类这个概念,所以反射 + 抽象工厂模式,一般会用于有继承或者接口实现关系。

        例如,在运行时才确定使用哪一种 Map 结构,我们可以利用反射传入某个具体 Map 的全限定名,实例化一个特定的子类。

className 可以指定为 java.util.HashMap,或者 java.util.TreeMap 等等,根据业务场景来定。

public class MapFactory {
    /**
     * @param className 类的全限定名
     */
    public Map<Object, Object> produceMap(String className) {
        Class clazz = Class.forName(className);
        Map<Object, Object> map = clazz.newInstance();
        return map;
    }
}

双亲委派

反射中,还有一个非常重要的类就是 ClassLoader 类,类装载是Class类装载进JVM的,ClassLoader 使用的是双亲委托模型来搜索加载类的,这个模型也就是双亲委派模型。

反射缺点

  • 破坏类的封装性:可以强制访问 private 修饰的信息,所谓的权限在它面前啥也不是一览无余。
  • 性能损耗:反射相比直接实例化对象、调用方法、访问变量,中间需要非常多的检查步骤和解析步骤,JVM无法对它们优化。

        虽然反射具有性能损耗的特点,但是我们不能一概而论,产生了使用反射就会性能下降的思想,反射的慢,需要同时调用上100W次才可能体现出来,在几次、几十次的调用,并不能体现反射的性能低下。所以不要一味地戴有色眼镜看反射,在单次调用反射的过程中,性能损耗可以忽略不计。如果程序的性能要求很高,那么尽量不要使用反射。

------------------------------------------与正文内容无关------------------------------------
 如果觉的文章写对各位读者老爷们有帮助的话,麻烦点赞加关注呗!作者在这拜谢了!

混口饭吃了!如果你需要Java 、Python毕设、商务合作、技术交流、就业指导、技术支持度过试用期。请在关注私信我,本人看到一定马上回复!

这是我全部文章所在目录,看看是否有你需要的,如果遇到觉得不对地方请留言,看到后我会查阅进行改正。

A乐神-CSDN博客

关注在文章左上角,作者信息处。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1427844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

认识文件和目录

单用户&多用户操作系统(科普) 单用户操作系统:指一台计算机在同一时间 只能由一个用户 使用,一个用户独自享用系统的全部硬件和软件资源Windows XP 之前的版本都是单用户操作系统 多用户操作系统:指一台计算机在同一时间可以由 多个用户 使用,多个用户共同享用系统的全…

syntax error: expected ‘:‘ | ‘IDENT‘ | ‘INT‘, got ‘(‘

问题 使用 go-zero时&#xff0c;执行api命令报错user.api 55:9 syntax error: expected : | IDENT | INT, got (。报错信息提醒文件55行有错&#xff0c;但是源文件55行没有问题。 解决 这里我的goctl版本是1.6.1 执行如下命令&#xff0c;将版本改为1.6.0&#xff0c;即可解…

Maven的安装以及配置(超级详细版)

前言 至于什么是Maven&#xff0c;大家可以理解为之前的Vue一样&#xff0c;也是通过操控对象映射来使用的 他内部还有很多的插件用于实现对应的功能&#xff0c;例如打包插件&#xff0c;或是测试 maven下载 Maven – Download Apache Maven apache下的开源项目&#xff0…

基于C/C++的MFC的IDC_MFCEDITBROWSE2控件不显示ico问题记录

打开资源文件 *.rc文件 &#xff0c;在最上方添加 #if !defined(_AFXDLL) #include "afxribbon.rc" // MFC ribbon and control bar resources #endif 如下图所示&#xff1a;

SpringBoot security 安全认证(二)——登录拦截器

本节内容&#xff1a;实现登录拦截器&#xff0c;除了登录接口之外所有接口访问都要携带Token&#xff0c;并且对Token合法性进行验证&#xff0c;实现登录状态的保持。 核心内容&#xff1a; 1、要实现登录拦截器&#xff0c;从Request请求中获取token&#xff0c;从缓存中获…

liunx服务器下vim无法:wq解决方案

当你vim某一个文件是&#xff0c;由于其他人在操作&#xff0c;或者上次操作中断&#xff0c;造成的 解决方案&#xff0c;不要&#xff0c;vim -r /etc/****&#xff0c;不然你会得到 正确方案 然后在&#xff0c;直接sudo vim /ect/xxx&#xff0c;还出现第一幅图就直接选…

QML自定义ComboBox组件,支持动态筛选

QtQuick.Controls提供了ComboBox组件&#xff0c;该组件能够满足日常的下拉选择框的需求&#xff0c;但当需要用户在ComboBox中通过输入关键字进行自动匹配时&#xff0c;原生的组件虽然提供了editable属性用于输入关键字&#xff0c;但是匹配内容不弹出下拉框&#xff0c;无法…

winform冒泡排序和快速排序实现

winform冒泡排序和快速排序实现 冒泡排序和快速排序是两种常用的排序算法&#xff0c;可以在Winform程序中实现以提高排序效率。冒泡排序是一种简单但效率较低的排序算法&#xff0c;它通过相邻元素间的比较和交换来实现排序。它的基本思想是每一轮比较相邻两个元素的大小&…

浅析Redis③:命令处理之数据返回Client(下)

写在前面 Redis作为我们日常工作中最常使用的缓存数据库&#xff0c;其重要性不言而喻&#xff0c;作为普通开发者&#xff0c;我们在日常开发中使用Redis&#xff0c;主要聚焦于Redis的基层数据结构的命令使用&#xff0c;很少会有人对Redis的内部实现机制进行了解&#xff0c…

YOLOv8-Segment C++

YOLOv8-Segment C https://github.com/triple-Mu/YOLOv8-TensorRT 这张图像是运行yolov8-seg程序得到的结果图&#xff0c;首先是检测到了person、bus及skateboard(这个是检测错误&#xff0c;将鞋及其影子检测成了滑板&#xff0c;偶尔存在错误也属正常)&#xff0c;然后用方…

4D毫米波雷达——ADCNet 原始雷达数据 目标检测与可行驶区域分割

前言 本文介绍使用4D毫米波雷达&#xff0c;基于原始雷达数据&#xff0c;实现目标检测与可行驶区域分割&#xff0c;它是来自2023-12的论文。 会讲解论文整体思路、输入分析、模型框架、设计理念、损失函数等&#xff0c;还有结合代码进行分析。 论文地址&#xff1a;ADCNe…

Flink实时数仓同步:快照表实战详解

一、背景 在大数据领域&#xff0c;初始阶段业务数据通常被存储于关系型数据库&#xff0c;如MySQL。然而&#xff0c;为满足日常分析和报表等需求&#xff0c;大数据平台采用多种同步方式&#xff0c;以适应这些业务数据的不同存储需求。这些同步存储方式包括离线仓库和实时仓…

nvm 工具使用介绍

目录 1.背景2.nvm介绍3.下载和安装4.配置环境变量5.配置淘宝镜像5.1 方式一&#xff1a;直接执行命令5.2 方式二&#xff1a;修改配置文件 6.常用命令7.总结 下载地址&#xff1a; https://github.com/coreybutler/nvm-windows/releases 1.背景 在工作中&#xff0c;我们可能…

总分类账户和明细分类账户

目录 一. 设置二. 联系与区别三. 平行记账规则 \quad 一. 设置 \quad 根据总分类科目设置总分类账户 根据明细分类科目设置明细分类账户 \quad 二. 联系与区别 \quad \quad 三. 平行记账规则 \quad

【C++11(一)】列表初始化and右值引用

一、 统一的列表初始化 1.1 &#xff5b;&#xff5d;初始化 在C98中&#xff0c;标准允许 使用花括号{}对数组或者结构体元素 进行统一的列表初始值设定 C11扩大了用大括号 括起的列表(初始化列表)的使用范围 使其可用于所有的内置类型和 用户自定义的类型 使用初始化列表时…

bash脚本学习笔记

一、扫盲 脚本文件是一种文本文件&#xff0c;其中包含了一系列的命令和指令&#xff0c;可以被操作系统解释器直接解释执行。脚本文件通常被用来完成特定的任务或执行重复性的操作。 脚本文件通常以某种编程语言的语法编写&#xff0c;例如 Bash、Python、Perl、Ruby 等等。…

IDEA JDBC配置

一、在pom中添加依赖 <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency></dependencies> 然后同步一下 二、编写代码…

Linux 系统服务

一、CentOS 6 与 CentOS 7开机流程 1.CentOS 6 1.1.打开电源首先通过内核引导开机。 1.2.开机自检&#xff0c;加载 BIOS 硬件信息。 1.3.MBR 记录一般是在磁盘0磁道0扇区&#xff0c;共512个字节。前446个字节是 BootLoder。计算机不知道我们的系统在哪里&#xff0c;所以需…

【Java数据结构】模拟实现ArrayList

import java.util.Arrays;/*** ArrayList的模拟实现*/ class SeqList{private int[] elem;private int usedSize0;//记录当前顺序表有多少个有效数字public static final int DEFAULT_CAPACITY10;//默认大小是10public SeqList(){this.elemnew int[DEFAULT_CAPACITY];}//新增元…

基于FFT + CNN -Transformer时域、频域特征融合的电能质量扰动识别模型

目录 往期精彩内容&#xff1a; ​模型整体结构 1 快速傅里叶变换FFT原理介绍 第一步&#xff0c;导入部分数据&#xff0c;扰动信号可视化 第二步&#xff0c;扰动信号经过FFT可视化 2 电能质量扰动数据的预处理 2.1 导入数据 2.2 制作数据集 3 基于FFTCNN-Transform…