引言

今天是10月24日,行业内的大牛小白都知道, 这是一个中国广大程序员的共同节日,1024是2的十次方,二进制计数的基本单位之一。程序员就像是一个个1024,以最低调、踏实、核心的功能模块搭建起这个科技世界,在此也向IT行业的前辈们致敬。
这也是我第二次过程序员节,但是学习习惯不能丢,今天起了个早,写一篇博客开启这美好的一天吧。

反射概述

Java反射机制指的是在Java程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法;对于给定的一个对象,都能够调用它的任意一个属性和方法。这种动态获取类的内容以及动态调用对象的方法称为反射机制。
Java的反射机制允许编程人员在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制。
反射能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,在很多框架中被大量使用,所以可以说框架的灵魂即是:反射技术。

这些都是很官方的一些解释,通过概述能够知道反射技术的强大,所以接下来,我们细细品味一下反射的用法。

类的加载过程

当程序要使用某个类的时候,如果该类还没有被加载到内存,则系统会通过加载、连接、初始化三个步骤来实现对这个类的初始化。

  • 加载:指将class文件读入内存,并为之创建一个Class对象,任何类被使用时系统都会为其创建Class对象
  • 连接:连接又分为三个步骤(验证、准备、解析)
               验证:验证是否有正确的内部结构,并和其它类协调一致
               准备:负责为类的静态成员分配内存,并设置默认初始化值
               解析:将类的二进制数据中的符号引用替换为直接引用
  • 初始化:初始化会为所有的静态变量赋予正确的值。注意初始化操作和准备操作的区别,准备操作为静态成员分配内存,设置默认初始化值,而初始化操作是设置正确的值。举个例子:static int num = 1024,这样的一个成员变量在准备阶段会为其赋值为0,只有到初始化阶段才会赋值为1024。

类加载器

了解完类的加载过程之后,我们来看看到底是谁完成了类的加载操作,它就是:类加载器。
类加载器负责将.class文件加载到内存中,并为之生成对应的Class对象,类加载器由以下三大加载器组成:

  1. 根类加载器(Bootstrap ClassLoader):根类加载器也被称为引导类加载器,负责Java核心类的加载,例如System、String类等
  2. 扩展类加载器(Extension ClassLoader):扩展类加载器负责JRE的扩展目录中jar包的加载
  3. 系统类加载器(System ClassLoader):系统类加载器负责在Java虚拟机启动时加载来自Java命令的class文件以及classpath变量所指定的jar包和类路径

获取Class对象

有了理论的知识之后,我们就可以开始实践了,先来看看如何获取类的Class对象(有三种方式)。
首先创建一个基本类用于测试:

package com.wwj.reflect;public class Programmer {private String name;public int age;private String address;public Programmer() {}private Programmer(String name, int age) {this.name = name;this.age = age;}public Programmer(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public void test() {System.out.println("test---无参无返回值方法");}public void test2(String str) {System.out.println("test2---带参无返回值方法");}public String test3(String str, int num) {System.out.println("test3--带参带返回值方法");return str + "--" + num;}private void test4() {System.out.println("test4---私有方法");}@Overridepublic String toString() {return "Programmer{" +"name='" + name + '\'' +", age=" + age +", address='" + address + '\'' +'}';}
}

那么第一种获取Class对象的方式就是通过Object类的getClass()方法:

Programmer programmer = new Programmer();
Class pClass = programmer.getClass();

第二种方式就是通过静态属性class:

Class pClass = Programmer.class;

第三种方式通过Class类中的静态方法forName():

Class pClass = Class.forName("com.wwj.reflect.Programmer");

获取构造方法

拿到了Class对象后,我们就可以通过该对象获取类的成员并使用,先来看看如何获取类的构造方法。

 public static void main(String[] args) throws ClassNotFoundException {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor[] constructors = pClass.getConstructors();for (Constructor constructor : constructors) {System.out.println(constructor);}}

运行结果:

public com.wwj.reflect.Programmer(java.lang.String,int,java.lang.String)
public com.wwj.reflect.Programmer()

控制台只打印了两个构造方法,但很显然,Programmer类中有三个构造方法,其中的私有构造方法获取不到。所以Class类中的getConstructors()只能获取到公共的构造方法,要想获取到所有的构造方法,可以使用getDeclaredConstructors()方法:

 public static void main(String[] args) throws ClassNotFoundException {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor[] constructors = pClass.getDeclaredConstructors();for (Constructor constructor : constructors) {System.out.println(constructor);}}

运行结果:

public com.wwj.reflect.Programmer(java.lang.String,int,java.lang.String)
private com.wwj.reflect.Programmer(java.lang.String,int)
public com.wwj.reflect.Programmer()

通常情况下,我们并不需要这么多的构造方法,往往我们只需要一个构造方法就行了。

1.获取无参构造方法

先说说如何获取类的无参构造方法。

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();System.out.println(object);}

我们可以通过Class类的getConstructor()方法获得单个的构造方法,不传参则代表获取无参构造方法,然后通过返回的构造方法对象调用newInstance()方法即可创建Programmer对象,所以运行结果应为Programmer类的信息。

Programmer{name='null', age=0, address='null'}
2.获取带参构造方法

获取带参构造方法的方式同样是通过getConstructor()方法,只不过需要传入参数,返回的是对应参数的构造方法对象,直接看例子:

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor(String.class,int.class,String.class);Object object = constructor.newInstance("张三",18,"杭州");System.out.println(object);}

运行结果:

Programmer{name='张三', age=18, address='杭州'}
3.获取私有构造方法

前面已经说到,getConstructor()方法无法获取到私有构造方法,所以我们改用getDeclaredConstructors()方法即可,用法和getConstructor()相同。

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor declaredConstructor = pClass.getDeclaredConstructor(String.class, int.class);declaredConstructor.setAccessible(true);//取消访问检查Object object = declaredConstructor.newInstance("李四", 20);System.out.println(object);}

运行结果:

Programmer{name='李四', age=20, address='null'}

需要注意的是,虽然getDeclaredConstructor()方法能够获取到私有构造方法,但由于Java语言的访问检查机制,在创建对象的时候会抛出非法访问异常,所以我们需通过setAccessible()方法取消访问检查,参数为true则为取消,取消了访问检查后才能正常创建对象。

获取成员变量

我们再来看看如何通过Class对象获得类的成员变量。

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Field[] fields = pClass.getFields();for (Field field : fields) {System.out.println(field);}}

运行结果:

public int com.wwj.reflect.Programmer.age

输出结果不难理解,和获取构造方法一样,getFields()方法无法获取类的私有成员变量,可以通过getDeclaredFields()方法获取:

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Field[] fields = pClass.getDeclaredFields();for (Field field : fields) {System.out.println(field);}}

运行结果:

private java.lang.String com.wwj.reflect.Programmer.name
public int com.wwj.reflect.Programmer.age
private java.lang.String com.wwj.reflect.Programmer.address
1.获取公共成员变量
 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();Field ageField = pClass.getField("age");ageField.set(object, 20);System.out.println(object);}

运行结果:

Programmer{name='null', age=20, address='null'}

通过Class对象的getField()方法能够获取指定属性名的成员变量,但若想对属性进行赋值,则首先需要创建出Programmer对象,然后调用成员变量对象的set()方法,传入要赋值的对象和属性值。这个逻辑其实和正常创建对象赋值是刚好相反的,反射是通过成员变量对象调用方法并将类对象和参数值传入。

2.获取私有成员变量

获取私有成员变量的方式和获取私有构造方法相同,通过getDeclaredField()方法获得成员变量对象,并且在赋值之前需要先取消访问检查,直接看示例:

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();Field nameField = pClass.getDeclaredField("name");nameField.setAccessible(true);//取消访问检查nameField.set(object,"李四");System.out.println(object);}

运行结果:

Programmer{name='李四', age=0, address='null'}

获取成员方法

获取成员方法的方式和前面相同,通过getMethods()方法可以获取到公共的成员方法,通过getDeclaredMethods()方法可以获取到包括私有的所有成员方法,在此不做重复讲解,接下来说一说如何获取单个成员方法。

1.获取无参无返回值成员方法
 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();Method method = pClass.getMethod("test");method.invoke(object);}

运行结果:

test---无参无返回值方法

同样地,通过getMethod()方法可以获取到对应参数名的成员方法,该方法需要传入两个参数:第一个参数为方法名;第二个参数为方法的参数类型。
这里因为是无参方法,所以无需传入第二个参数,获取到成员方法的对象之后,同样是调用该对象的invoke()方法并将需要执行方法的对象传入才能成功执行方法。

2.获取带参无返回值成员方法

获取带参成员方法就很简单了,在getMethod()方法中传入参数类型即可,直接看示例:

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();Method method = pClass.getMethod("test2", String.class);method.invoke(object, "王五");}

运行结果:

test2---带参无返回值方法
3.获取带参带返回值成员方法

获取带参带返回值成员方法同样十分简单,只不过多了一个返回值处理罢了:

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();Method method = pClass.getMethod("test3", String.class, int.class);Object obj = method.invoke(object, "赵六", 20);System.out.println(obj);}

运行结果:

test3--带参带返回值方法
赵六--20
4.获取私有成员方法

获取私有成员方法,即通过getDeclaredMethod()方法获取成员方法对象,并取消访问检查,然后执行方法即可:

 public static void main(String[] args) throws Exception {Class pClass = Class.forName("com.wwj.reflect.Programmer");Constructor constructor = pClass.getConstructor();Object object = constructor.newInstance();Method method = pClass.getDeclaredMethod("test4");method.setAccessible(true);method.invoke(object);}

运行结果:

test4---私有方法

利用反射无视泛型检查

到这里关于反射的基本知识就介绍完了,接下来我们用泛型来解决一个问题:无视掉Java的泛型检查。
我们看这样的一段代码:

 public static void main(String[] args) throws Exception {List<String> list = new ArrayList<>();list.add("hello");list.add("world");list.add(1024);}

因为list集合的泛型被指定为String类型,所以该集合将只能存储字符串,所以我们在放入1024的时候编译器会报错,那有没有可能通过一些手段将其它类型也能够放入该集合呢?办法是有的,那就是通过反射。
因为Java泛型机制其实只在编译阶段有效,在真正运行的时候是不带泛型的,这种现象叫泛型擦除。这是因为这一特点,我们就能通过反射越过编译期的泛型检查,实现将其它类型的数据存放到指定类型的集合中。

 public static void main(String[] args) throws Exception {List<String> list = new ArrayList<>();list.add("hello");list.add("world");Class listClass = list.getClass();Method addMethod = listClass.getMethod("add", Object.class);addMethod.invoke(list, 1024);System.out.println(list);}

运行结果:

[hello, world, 1024]

这样,int类型数据就成功被存放到了集合中。

动态代理

动态代理是反射技术的高级应用,其目的就是为其它对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
在Java中,JDK为我们提供了Proxy类和InvocationHandler接口,通过这个类和接口就可以生成动态代理对象,但需要注意,JDK提供的代理只能针对接口做代理,如果需要对普通类做代理,我们可以使用cglib。
由于篇幅有限,这里不对动态代理做详细介绍,就通过一个案例让大家先了解一下动态代理。
新建一个接口ICat:

interface ICat{public void run();
}

新建一个类继承该接口:

public class Cat implements ICat{@Overridepublic void run(){System.out.println("喵喵~一只猫在奔跑");}
}

这是一只会奔跑的猫,通常我们会使用动态代理来增强某个类的方法,例如该类中,我们可以增强run()方法,使其还会抓老鼠:

 public static void main(String[] args) throws Exception {final ICat cat = new Cat();//原对象ICat catProxy = (ICat) Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] objs)throws Throwable {//增强run方法if (method.getName().equals("run")) {method.invoke(cat, objs);//调用原对象的方法,保留原方法的功能//新增功能System.out.println("抓住一只老鼠");}return null;}});catProxy.run();}

运行结果:

喵喵~一只猫在奔跑
抓住一只老鼠

主要说一说invoke()方法,该方法有三个参数:

  • proxy:在其调用方法的代理实例
  • method:对应于在代理实例上调用的接口方法的Method实例。Method对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口
  • objs:包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为null。基本类型的参数被包装在适当基本包装器类的实例中

返回值即是代理方法的返回值,因为这里run()方法没有返回值,所以返回null即可,然后调用method对象的invoke()方法,并将需要执行方法的对象和参数值objs传入即可执行原方法的逻辑,这在如何获取成员方法中已经说过,然后我们就可以在下面写上需要添加的功能,这样该方法就比原先的方法功能更加丰富了。

最后

本篇文章总体是偏简单的,适合刚入门的学习者,虽然简单,但也写了挺久,从8点多一直写到11点,目的也是希望大家能够快速掌握反射技术,反射技术在后期的框架学习中是至关重要的,理解反射,对于框架的底层实现你就能够更加了解。

最后祝大家节日快乐!

【1024特辑】带你掌握框架的灵魂——反射技术相关推荐

  1. 框架的灵魂------反射

    反射之所以被称为框架的灵魂,是因为它赋予了我们在运行时分析类以及执行类中方法的能力. 通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性. 一.反射的应用场景? 像 Spring ...

  2. android视频压缩框架,GitHub - tangpeng/VideoCompressor: Android 使用自带的MediaCodec 框架进行本地视频压缩,速度嗖嗖的,亲测有效!!!...

    如果您觉得本项目对你有用,请随手star,谢谢 Android 视频压缩常见3种方案:(1)FFmpeg,(2)mp4praser,(3)MediaCodec. 本demo是用android 自带的M ...

  3. 不会框架不要紧,我带你自定义框架

    不会框架不要紧,我带你自定义框架 前言:这标题说的有点大了,当一回标题党,之前在学JSP的时候提到了JSTL和EL表达式,由于一直钟情于Servlet,迟迟没有更新别的,这回算是跳出来了.这回放个大招 ...

  4. ThinkPHP5.1.x 框架源码分析之框架的灵魂

    一.类的自动加载初始 框架的灵魂,类的自动加载 为什么说是框架灵魂呢,一般框架都会有类的自动加载,当引入文件很多的时候,就会需要用到.这一个也是很多人想去阅读源码时卡住的点 源码阅读 打开到入口文件 ...

  5. spring+websocket综合(springMVC+spring+MyBatis这是SSM框架和websocket集成技术)

    java-websocket该建筑是easy.儿童无用的框架可以在这里下载主线和个人教学好java-websocket计划: Apach Tomcat 8.0.3+MyEclipse+maven+JD ...

  6. Java中的灵魂-反射机制

    全文都是转载的嗑嗑磕嗑瓜子的猫这位大神的文章,写的非常好,转载过来主要是想将好的知识保存下来方便以后再查阅.原文请点这 思考:在讲反射之前,先思考一个问题,java中如何创建一个对象,有哪几种方式? ...

  7. C#温故而知新学习系列之.NET框架高级特性—概述.NET框架中的反射(一)

    阅读目录 一:什么是元数据? 二:概述.NET框架中的反射 一:什么是元数据? 元数据,就是描述数据的数据,它存储在PE文件中,PE文件由IL Code和元数据组成,元数据为.NET提供了丰富的自我描 ...

  8. 应广单片机adc_台湾应广单片机 单片机PMC131 带12位ADC、采用FPPATM技术

    PMC131 台湾应广 一级代理 现货批发 长期供应 带12位ADC.采用FPPATM技术.单核心8位单片机 PMC131替代松翰SN8P2711芯片 PIN对PIN,引脚功能及脚位完全兼容, 仅需更 ...

  9. 新技术新框架层出不穷,作为技术人员应该怎么办?

    新技术新框架层出不穷,作为技术人员应该怎么办? 随着Python的大量使用,很多人在想要不要恶补下Python,已保证生存力: 又或者谷歌推出了新语言以代替Android: 又比如Twitter 宣布 ...

最新文章

  1. 本硕非科班,单模型获得亚军!
  2. hdu6165 缩点,dfs
  3. 【Android UI设计与开发】第06期:底部菜单栏(一)使用TabActivity实现底部菜单栏
  4. Windows——完全控制面板(上帝模式)
  5. 去除aspx生成的页面最开始的空行
  6. Kafka开发指南之 如何Kafka 事务型生产者,保证生产者exactly once
  7. php最简单漂亮的excel导出,php把数据表导出为Excel表的最简单、最快的方法(不用插件)...
  8. python atm取款系统_基于python的ATM(自动取款机)项目
  9. IP记录Linux所有用户操作日志的方法(附脚本)
  10. [整理] TPM 2.0 设备串口通讯协议中文文档
  11. mysql backup user_mysql备份常见命令
  12. smarty 模板 php,PHP smarty模板
  13. 空间两直线间最短距离计算公式
  14. 数据中台稳定性的“四高” | StartDT Tech Lab 18
  15. 【ByteCTF 2022】Crypto Writeup
  16. opencv-python 小白笔记(3)
  17. Xshell配色美化
  18. windows10启动/关闭MySQL服务的两种方法
  19. web前端开发这门技术的由来,入门必读
  20. 一种简单的直观的高效的权限设计

热门文章

  1. 怎么从饭局看自己在领导心中地位?别傻傻喝酒,高人看这6个举动
  2. 在将要离开公司的日子
  3. Axure原型设计工具介绍
  4. Java job interview:公司项目Java开发Backbone.js为复杂WEB应用程序提供模型
  5. Resource temporarily unavailable解决办法
  6. 9种炫酷loading加载cssjs特效
  7. 骁龙870和麒麟980哪个好 骁龙870和麒麟980对比哪个更强
  8. 元宇宙,现状,路径与未来
  9. 【Ubuntu】22.04安装搜狗输入法
  10. FlashBuilder4.6创建手机小程序