java反射机制原理详解_java反射的原理是什么

目录

何为反射?

反射(Reflection),是指Java程序具有 在运行期分析类以及修改其本身状态或行为的能力 。

通俗点说 就是 通过反射我们可以 动态地获取一个类的所有属性和方法,还可以操作这些方法和属性。

实例的创建

一般我们创建一个对象实例 Person zhang = new Person();

虽然是简简单单一句,但JVM内部的实现过程是复杂的:

  1. 将硬盘上指定位置的Person.class文件加载进内存
  2. 执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量zhang。
  3. 执行new,在堆内存中开辟一个 实体类的 空间,分配了一个内存首地址值
  4. 调用该实体类对应的构造函数,进行初始化(如果没有构造函数,Java会补上一个默认构造函数)。
  5. 将实体类的 首地址赋值给zhang,变量zhang就引用了该实体。(指向了该对象)

java反射机制原理详解_java反射的原理是什么

其中上图步骤1 Classloader(类加载器) 将class文件加载到内存中具体分为3个阶段:加载、连接、初始化

java反射机制原理详解_java反射的原理是什么

而又在 加载阶段,类加载器会 将类对应的.class文件中的二进制字节流读入到内存中,将这个字节流转化为方法区的运行时数据结构,然后在堆区创建一个**java.lang.Class 对象**(类相关的信息),作为对方法区中这些数据的访问入口

然后再通过 类的实例来执操作 类的方法和属性,比如 zhang.eat(), zhang.getHeight() 等等

如果我们使用反射的话,我们需要拿到该类Person的Class对象,再通过Class对象来操作 类的方法和属性或者创建类的实例

Class personClass = Person.class;//这边只是举一个例子,获取class对象的多种方式,本文后面再慢慢道来 Object person = personClass.newInstance();

我们可以发现 通过new创建类的实例和反射创建类的实例,都绕不开.class文件 和 Class类的。

.class文件

首先我们得先了解一下 什么是.class文件

举个简单的例子,创建一个Person类:

public class Person { /** * 状态 or 属性 */ String name;//姓名 String sex;//性别 int height;//身高 int weight;//体重 /** * 行为 */ public void sleep(){ System.out.println(this.name+"--"+ "睡觉"); } public void eat(){ System.out.println("吃饭"); } public void Dance(){ System.out.println("跳舞"); } }

我们执行javac命令,编译生成Person.class文件

然后我们通过vim 16进制 打开它

在命令模式下输入.. 以16进制显示 :%!xxd #在命令模式下输入.. 切换回默认显示 :%!xxd -r

java反射机制原理详解_java反射的原理是什么

不同的操作系统,不同的 CPU 具有不同的指令集,JAVA能做到平台无关性,依靠的就是 Java 虚拟机。

.java源码是给人类读的,而 .class字节码是给JVM虚拟机读的,计算机智能识别 0 和 1组成的二进制文件,所以虚拟机就是我们编写的代码和计算机之间的桥梁。

虚拟机将我们编写的 .java 源程序文件编译为 字节码 格式的 .class 文件,字节码是各种虚拟机与所有平台统一使用的程序存储格式,class文件主要用于解决平台无关性的中间文件

java反射机制原理详解_java反射的原理是什么

Person.class文件 包含Person类的所有信息

Class类

我们来看下jdk的官方api文档对其的定义:

Class类的类表示正在运行的Java应用程序中的类和接口。 枚举是一种类,一个注释是一种界面。 每个数组也属于一个反映为类对象的类,该对象由具有相同元素类型和维数的所有数组共享。

原始Java类型( boolean , byte , char , short , int , long , float和double ),和关键字void也表示为类对象。

类没有公共构造函数。 相反, 类对象由Java虚拟机自动构建,因为加载了类,并且通过调用类加载器中的defineClass方法。。

**java 万物皆是Class类 **

【图片】

我们来看下Class类的源码,源码太多了,挑了几个重点:

public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement { private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000; private static native void registerNatives(); static { registerNatives(); } /* * Private constructor. Only the Java Virtual Machine creates Class objects. * This constructor is not used and prevents the default constructor being * generated. */ private Class(ClassLoader loader) { //私有化的 构造器 // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; } ... // reflection data that might get invalidated when JVM TI RedefineClasses() is called private static class ReflectionData<T> { volatile Field declaredFields;//字段 volatile Field publicFields; volatile Method declaredMethods;//方法 volatile Method publicMethods; volatile Constructor<T> declaredConstructors;//构造器 volatile Constructor<T> publicConstructors; // Intermediate results for getFields and getMethods volatile Field declaredPublicFields; volatile Method declaredPublicMethods; volatile Class<?> interfaces;//接口 // Value of classRedefinedCount when we created this ReflectionData instance final int redefinedCount; ReflectionData(int redefinedCount) { this.redefinedCount = redefinedCount; } } ... //注释数据 private volatile transient AnnotationData annotationData; private AnnotationData annotationData() { while (true) { // retry loop AnnotationData annotationData = this.annotationData; int classRedefinedCount = this.classRedefinedCount; if (annotationData != null && annotationData.redefinedCount == classRedefinedCount) { return annotationData; } // null or stale annotationData -> optimistically create new instance AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount); // try to install it if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) { // successfully installed new AnnotationData return newAnnotationData; } } } ...

我们可以发现Class也是类,是一种特殊的类,将我们定义普通类的共同的部分进行抽象,保存类的属性,方法,构造方法,类名、包名、父类,注解等和类相关的信息。

Class类的构造方法是private, 只有JVM能创建Class实例,我们开发人员 是无法创建Class实例的,JVM在构造Class对象时,需要传入一个 类加载器

类也是可以用来存储数据的,Class类就像 普通类的模板 一样,用来保存“类所有相关信息”的类。

java反射机制原理详解_java反射的原理是什么

我们来继续看这个利用反射的例子: Class personClass = Person.class;

由于JVM为加载的 Person.class创建了对应的Class实例,并在该实例中保存了该 Person.class的所有信息,因此,如果获取了Class实例(personClass ),我们就可以通过这个Class实例获取到该实例对应的 Person类的所有信息。

反射的使用

获取Class实例4种方式

  1. 通过对象调用 getClass() 方法来获取

Person p1 = new Person(); Class c1 = p1.getClass();

像这种已经创建了对象的,再去进行反射的话,有点多此一举。

一般是用于传过来的是Object类型的对象,不知道具体是什么类,再用这种方式比较靠谱

  1. 类名.class

Class c2 = Person.class;

这种需要提前知道导入类的包,程序性能更高,比较常用,通过此方式获取 Class 对象 ,Person类不会进行初始化

  1. 通过 Class 对象的 forName() 静态方法来获取,最常用的一种方式

Class c3 = Class.forName("com.zj.demotest.domain.Person");

这种只需传入类的全路径 Class.forName会进行初始化initialization步骤 ,即静态初始化(会初始化类变量,静态代码块)。

  1. 通过类加载器对象的 loadClass() 方法

public class TestReflection { public static void main(String args) throws ClassNotFoundException { Person p1 = new Person(); Class c1 = p1.getClass(); Class c2 = Person.class; Class c3 = Class.forName("com.zj.demotest.domain.Person"); //第4中方式,类加载器 ClassLoader classLoader = TestReflection.class.getClassLoader(); Class c4 = classLoader.loadClass("com.zj.demotest.domain.Person"); System.out.println(c1.equals(c2)); System.out.println(c2.equals(c3)); System.out.println(c3.equals(c4)); System.out.println(c1.equals(c4)); } }

loadClass的源码:

public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); }

loadClass 传入的第二个参数是"false",因此它不会对类进行连接这一步骤,根据 类的生命周期我们知道,如果一个类没有进行验证和准备的话,是无法进行初始化过程的,即 不会进行类初始化,静态代码块和静态对象也不会得到执行

我们将c1,c2,c3,c4进行 equals 比较

System.out.println(c1.equals(c2)); System.out.println(c2.equals(c3)); System.out.println(c3.equals(c4)); System.out.println(c1.equals(c4));

结果:

true true true true

因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例, 一个类在 JVM 中只会有一个 Class 实例

Class类常用的API

日常开发的时候,我们一般使用反射是为了 创建类实例(对象)、反射获取类的属性和调用类的方法

getName()

获得类的完整名字

getFields()

获得类的public类型的属性

getDeclaredFields()

获得类的所有属性。包括 private 声明的和继承类

getMethods()

获得类的public类型的方法

getDeclaredMethods()

获得类的所有方法。包括 private 声明的和继承类

getMethod(String name, Class parameterTypes)

获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。

getConstructors()

获得类的public类型的构造方法

getConstructor(Class parameterTypes)

获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型

newInstance()

通过类的不带参数的构造方法创建这个类的一个对象

getSuperClass()

用于返回表示该 Class 表示的任何类、接口、原始类型或任何 void 类型的 超类的Class(即父类)

我们这边就不全部展开讲了,挑几个重点讲解一下

创建对象

  1. 调用class对象的 newInstance() 方法

Class c1 = Class.forName("com.zj.demotest.domain.Person"); Person p1 = (Person) c1.newInstance(); p1.eat();

结果:

吃饭

注意:Person类必须有一个 无参的构造器类的构造器的访问权限不能是private

  1. 使用指定构造方法 Constructor 来创建对象

如果我们非得让Person类的无参构造器设为private呢,我们可以获取对应的Constructor来创建对象

Class c1 = Class.forName("com.zj.demotest.domain.Person"); Constructor<Person> con = c1.getDeclaredConstructor(); con.setAccessible(true);//允许访问 Person p1 = con.newInstance(); p1.eat();

结果:

吃饭

注意:setAccessible()方法能在运行时 压制Java语言访问控制检查(Java language access control checks),从而能任意调用 被私有化保护的方法、域和构造方法。

由此我们可以发现** 单例模式不再安全,反射可破之!**

访问属性

Field getField(name)

根据字段名获取某个public的field(包括父类)

Field getDeclaredField(name)

根据字段名获取当前类的某个field(不包括父类)

Field getFields()

获取所有public的field(包括父类)

Field getDeclaredFields()

获取当前类的所有field(不包括父类)

我们来看一个例子:

public class TestReflection3 { public static void main(String args) throws Exception { Object p = new Student("li hua"); Class c = p.getClass(); Field f = c.getDeclaredField("name");//获取属性 f.setAccessible(true);//允许访问 Object val= f.get(p); System.out.println(val); } static class Student { private String name; public Student(String name) { this.name = name; } } }

结果:

li hua

我们可以发现 反射可以破坏类的封装

调用方法

Method getMethod(name, Class…)

获取某个public的Method(包括父类)

Method getDeclaredMethod(name, Class…)

获取当前类的某个Method(不包括父类)

Method getMethods()

获取所有public的Method(包括父类)

Method getDeclaredMethods()

获取当前类的所有Method(不包括父类)

我们来看一个例子:

public class TestReflection4 { public static void main(String args) throws Exception { //获取私有方法,需要传参:方法名和参数 Method h = Student.class.getDeclaredMethod("setName",String.class); h.setAccessible(true); Student s1 =new Student(); System.out.println(s1.name); //传入目标对象,调用对应的方法 h.invoke(s1,"xiao ming"); System.out.println(s1.name); } static class Student { private String name; private void setName(String name) { this.name = name; } } }

结果:

null xiao ming

我们发现获取方法getMethod()时,需要传参 方法名和参数

这是因为.class文件中通常有 不止一个方法,获取方法getMethod()时,会去调用searchMethods方法循环遍历所有Method,然后根据 方法名和参数类型找到唯一符合的Method返回。

java反射机制原理详解_java反射的原理是什么

我们知道类的方法是在JVM的方法区中 ,当我们new 多个对象时,属性会另外开辟堆空间存放,而方法只有一份,不会额外消耗内存,方法就像一套指令模板,谁都可以传入数据交给它执行,然后得到对应执行结果。

method.invoke(obj, args) 时传入目标对象,即可调用对应对象的方法

如果获取到的Method表示一个静态方法,调用静态方法时, 无需指定实例对象,所以invoke方法传入的第一个参数永远为null, method.invoke(null, args)

那如果 方法重写了呢, 反射依旧遵循 多态 的原则

反射的应用场景

如果平时我们只是写业务代码,很少会接触到直接使用反射机制的场景,毕竟我们可以直接new一个对象,性能比还反射要高。

但如果我们是工具框架的开发者,那一定非常熟悉,像 Spring/Spring Boot、MyBatis 等等框架中都大量使用反射机制, 反射被称为框架的灵魂

比如:

  1. Mybatis Plus可以让我们只写接口,不写实现类,就可以执行SQL
  2. 开发项目时,切换不同的数据库只需更改配置文件即可
  3. 类上加上@Component注解,Spring就帮我们创建对象
  4. 在Spring我们只需 @Value注解就读取到配置文件中的值
  5. 等等

扩展:反射配置文件

我们来模拟一个配置高于编码的例子

新建my.properties,将其放在resources的目录下

#Person类的包路径 className=com.zj.demotest.domain.Person methodName=eat

Person类 还是本文 一直用的,在文章的开头有

最后我们来编写一个测试类

public class TestProp { public static void main(String args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Properties properties = new Properties(); ClassLoader classLoader = TestProp.class.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream("my.properties");// 加载配置文件 properties.load(inputStream); String className = properties.getProperty("className"); System.out.println("配置文件中的内容:className="+className); String methodName = properties.getProperty("methodName"); System.out.println("配置文件中的内容:methodName="+methodName); Class name = Class.forName(className); Object object = name.newInstance(); Method method = name.getMethod(methodName); method.invoke(object); } }

结果:

配置文件中的内容:className=com.zj.demotest.domain.Person

配置文件中的内容:methodName=eat

吃饭

紧接着,我们修改配置文件:

className=com.zj.demotest.domain.Person methodName=eat

结果变为:

配置文件中的内容:className=com.zj.demotest.domain.Person

配置文件中的内容:methodName=Dance

跳舞

是不是很方便?

java反射机制原理详解_java反射的原理是什么

尾语

反射机制是一种功能强大的机制,让Java程序具有在 运行期分析类以及修改其本身状态或行为的能力 。

对于特定的复杂系统编程任务,它是非常必要的,为各种框架提供开箱即用的功能提供了便利,为解耦合提供了保障机制。

但是世事无绝对,反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接访问对象要差点(JIT优化后,对于框架来说实际是影响不大的),还会增加程序的复杂性等(明明直接new一下就能解决的事情,非要写一大段代码)。

本文【java反射机制原理详解_java反射的原理是什么】由作者: 递归 提供,本站不拥有所有权,只提供储存服务,如有侵权,联系删除!
本文链接:https://www.cuoshuo.com/blog/4168.html

(0)
上一篇 2023-03-09 09:03:51
下一篇 2023-03-10 08:02:02

相关推荐

  • 软件编程入门自学教程

    编程的范围实在很大,学什么就是小白的首要问题。如果是学编程希望能够将重复工作自动化这类朋友,直接学Python即可。下文会说怎么学习,本文主要针对的是想要通过编程高薪转行的这类人。 一、编程方向 编程可就业的技术岗位非常多,我们看下前端、后端、移动端以及人工智能: 另外还有测试、运维、数据相关等: 按照上图的大分类介绍一下各个方向重点掌握技能。 后端开发:建…

    2023-03-11
    200
  • c语言基础知识入门书籍推荐

    全篇干货,我敢保证对你绝对有用。C语言是一种通用的,面向过程的高级编程语言,他以易于理解,简洁灵活,语法易读,成为了目前世界上最流行和最具影响力的语言之一。 那么新手在学习c语言的入门阶段,该怎么快速巩固基础? 我把自己压箱底的宝贝书单总结了一下,我真的不知道当初自己为什么要和C语言这么较真,明明现在用Java比较多。 希望能减少大家四处搜索的时间吧。 1、…

    2023-03-08
    1000
  • ftp教程ubuntu ubuntu开启ftp

    FTP(文件传输协议)是一种标准网络协议,用于在远程网络之间传输文件。 有许多可用于 Linux 的开源 FTP 服务器。最流行和使用最广泛的是PureFTPd 、ProFTPD 和vsftpd 。在本教程中,我们将安装 vsftpd(非常安全的 Ftp 守护程序)。它是一个稳定、安全、快速的 FTP 服务器。我们还将向您展示如何配置 vsftpd 以将用户…

    2023-03-16
    200
  • js进度条怎么命令进度

    一、前言 我们经常在网页上 ,游戏界面加载时会看到加载进度条的效果,我们往往会以为这些加载进度条的效果,很难实现。 今天教大家JS+CSS结合做简单一个加载进度条的效果。 二、项目准备 软件:HBuilderX。 三、项目实现 1. body 创建2个div,外部div添加id"progress"属性, 添加 id属性 。 <div…

    2023-03-13
    300
  • mysql安装教程环境配置_完整版MySQL安装配置

    简介:Mysql是最流行的关系型数据库管理系统,Mysql是一款优秀的web管理方面的 RDBMS(Relational Database Management System:关系数据库管理系统)应用工具。现在大多数软件系统使用关系型数据库管理系统(RDBMS)来存储和管理大数据量。所谓的关系型数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念…

    2023-03-10
    600
  • mysql更改密码后提示过期

    Your password has expired. To log in you must change it using a client that supports expired passwords 该问题提示很明显,说密码已过期。 按照网上的说法,操作了一遍。步骤如下: 1、在my.cnf 的 部分加入 skip-grant-tables 参数。 s…

    2023-03-08
    700
  • 卡雷尔机器人作业答案(机器人学建模控制与视觉课后答案)

    卡雷尔CEO 尹利 双目视觉即深度视觉,以目前的技术搭配雷达可以更好的应用在服务机器人身上。 机器视觉也分种类,像工业机器手采用机器视觉可以定位抓取,工业上用的机器视觉大部分依赖外企产品。 但在双目视觉上,国内与国外企业在技术上差距不大,准确的说是各有差异。 双目视觉可以做到三维空间建模、物体的识别与扫描重建,配合机器手臂还可以做抓取物体。 北京卡雷尔机器人…

    2023-03-16
    100
  • 解析xml时,需要校验节点是否闭合

    XML的简介 定义 HTML:超文本标记语言 XML:eXtensible Markup Language 可扩展标记语言 version=”1.0″ 可扩展:所有的标签都是自定义的。 功能:数据存储 配置文件(最主要的使用场景) 数据传输(一般不使用xml做数据传输,而使用json代替) html与xml区别: html语法松散,x…

    2023-03-21
    000
  • 源文件是什么类型的文件_什么是源文件

    1.3 文件参考(本知识点高级内容及应用后续将作专题介绍) SolidWorks创建的文件有时候是创建在其他文件的基础上的。通过这种参考链接关系所创建的文件更胜于在多个文件之间复制信息。 被参考的文件不一定要存放在参考文件的文件夹中。在实际应用中,参考文件通常被存放在不同的位置(本地电脑上或网络中)。SolidWorks提供了一些专门的工具来检测这些参考文件…

    2023-03-09
    900
  • qq安全检查未通过怎么办(QQ安全检查未通过)

    你的QQ安全吗? 大多数人应该会思考一下,回答“应该安全吧!”。讲真,你并没有100%的确认QQ的安全系数,毕竟没有人告诉你如何有效提高安全度,除了无限加长的复杂密码。 8.99亿用户不离不弃的“老司机”QQ, 18年来一直在提高“行驶”的安全性,早已针对陷阱重重的网络环境修炼好防御的秘籍。让群众们更加安心吃瓜,不怕“中毒”。 事实上,每一个手Q党都“背靠”…

    2023-03-13
    300

发表回复

登录后才能评论
返回顶部
错说博客上线啦!