反射

反射作为RTTI语言(比如Java)的基础之一被很多人所熟知,但是有些同学对反射本身还是懵懵懂懂的,不是很清楚它到底有什么用。今天这节课我们就对反射本身来一个通体的认知。

定义

反射所在的包为:java.lang.reflect

它的英文版定义是:Reflection allows programmatic access to information about the fields, methods and constructors of loaded classes。the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

By default, a reflected object is not accessible.

Setting the accessible flag in a reflected object permits sophisticated applications with sufficient privilege, such as Java Object Serialization or other persistence mechanisms, to manipulate objects in a manner that would normally be prohibited.

Java反射主要是指程序可以访问或者修改它本身状态或行为的一种能力,是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时通过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。

PS: 因为反射机制与Class类联系紧密,所以在学习反射之前需要先了解Class类是什么。

Android文档中的反射定义:https://developer.android.google.cn/reference/java/lang/reflect/package-summary.html

作用

动态的访问、修改类的成员,可以达到使用常规手段做不到的目的。

最常见的例子:一个类有一个私有的成员属性,无法通过正常的手段(比如Get方法)获取这个属性的值,那么就需要通过反射来获得它的值,

反射多用于框架和组件,通过反射可以写出复用性高的通用程序。

比如我们所熟知的Android中的Activity就是通过反射实例化生成的。

    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {return (Activity)cl.loadClass(className).newInstance();}

最后这行代码通过字符串形式的类路径加载指定的Activity类到内存中,然后通过反射的newInstance()实例化Activity对象,最后通过向下转型返回给调用者。

上面这段代码位于android.app.Instrumentation内。同理,我们所熟知的Application,Service,ContentProvider,BroadcatReceiver也是通过这种方式生成的。

Java多态的伟大之处就从这里开始!

你可能会有疑惑,为什么不直接new呢?

如果是new方法,那么new只能实例化指定的类,也就是说,如果使用new,Android系统框架只能实例化某个Activity了。而如果通过反射,那么只要是Activity的子类,都可以通过此方法实例化,这也就是多态的精髓。

优点

反射涉及到了动态与静态的概念:

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,用以降低类之间的藕合性。

反射机制的优点是可以实现动态创建对象以及修改对象的结构,体现出很大的灵活性。

缺点

它的缺点是对性能有影响。使用反射基本上是一种解释操作。由于用于字段和方法接入时反射要远慢于直接代码,反射在性能上会有所影响,但性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。所以,合理的使用反射将大大提高我们程序的通用性和复用性。

技术解析铺垫

运行时类型识别(Run-time Type Identification, RTTI)主要有两种方式,一种是我们在编译时和运行时已经知道了所有的类型,另外一种是功能强大的”反射”机制。

要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由”Class对象”完成的,它包含了与类有关的信息。类是程序的重要组成部分,每个类都有一个Class对象,每当编写并编译了一个新类就会产生一个Class对象,它被保存在一个同名的.class文件中。在运行时,当我们想生成这个类的对象时,运行这个程序的Java虚拟机(JVM)会确认这个类的Class对象是否已经加载,如果尚未加载,JVM就会根据类名查找.class文件,并将其载入,一旦这个类的Class对象被载入内存,它就被用来创建这个类的所有对象。一般的RTTI形式包括三种:

1.传统的类型转换。如”(Apple)Fruit”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。

2.通过Class对象来获取对象的类型。如

    Class c = Class.forName("Apple");Object o = c.newInstance();

3.通过关键字instanceof或Class.isInstance()方法来确定对象是否属于某个特定类型的实例,准确的说,应该是instanceof / Class.isInstance()可以用来确定对象是否属于某个特定类及其所有基类的实例,这和equals() / ==不一样,它们用来比较两个对象是否属于同一个类的实例,没有考虑继承关系。

基本用法

以下分别展示了反射的基本用法:

类的获取方式

针对我们所知的不同情况分别有3种方法获取Class对象

  • 当已知类名的时候,通过”类名.class”获得

  • 当已知对象的时候,通过”对象.getClass”获得

  • 当已知包括包名在内的完整类名(假设为String格式)的时候,可通过”Class.forName(classPath)”或者”ClassLoader.loadClass(classPath)”获得

比如我们有一个类,类的结构如下:

package com.sahadev;/*** Created by Sahadev on 2017/4/27.*/public class ClassABean {public boolean mFlag;private IMethod mIMethod;public ClassABean() {}public ClassABean(boolean mFlag, IMethod iMethod) {super();this.mFlag = mFlag;this.mIMethod = iMethod;}private void printBValue(){System.out.println("The mFlag = " + mFlag);}
}

那么类ClassABean的字节码的获取方式有以下3种:

  • ClassABean.class;
  • new ClassABean().getClass();
  • Class.forName(“com.sahadev.ClassABean”);或者ClassLoader.loadClass(“com.sahadev.ClassABean”);

获取到Class字节码对象之后,我们就可以对其进行操作了。

通过无参构造方法实例化对象

通过无参构造的方式有两种,一种是我们上面看到的,使用newInstance()方法,而另一种是获得类的无参构造方法,然后通过无参构造方法创建对象。其中newInstance()方法默认调用的是无参构造方法,如果类没有无参构造方法,则会有异常抛出。

这两种方法的使用方式分别如下:

    //通过newInstance()方法构造ClassABean instanceA = ClassABean.class.newInstance();//通过无参构造方法构造Constructor<ClassABean> constructor = ClassABean.class.getConstructor();//获取无参构造方法ClassABean instanceB = constructor.newInstance();//实例化

通过有参构造方法实例化对象

通过有参构造方法实例化对象的方法如下:

    Constructor<ClassABean> constructor = ClassABean.class.getConstructor(Boolean.class, ClassBBean.class);//获取指定参数的构造方法ClassABean instanceB = constructor.newInstance(true, new ClassBBean());//通过对象参数实例化对象

上面的代码等价于:

    ClassABean instanceB  = new ClassABean(true, new ClassBBean());

通过以上有参构造方法构造的对象,它们的成员属性现在都已经被赋了值。其中属性mFlag的值为true,属性mIMethod的实际实现者为ClassBBean。

PS: 在我们的示例中提到的ClassBBean类与ClassCBean类都同样实现了IMethod接口。

方法调用

从以上的示例中我们知道了如何通过反射来实例化一个对象,接下来我们通过反射来调用一下类的私有方法。

在ClassABean类中提供了一个私有方法printBValue(),我们看看如何通过反射来调用这个方法:

    Method method = ClassABean.class.getDeclaredMethod("printBValue");ClassABean instanceB = new ClassABean(true,new ClassBBean());method.setAccessible(true);method.invoke(instanceB);

控制台会正确输出我们预想中的值:

The mFlag = true

这样调用和我们通过普通方法调用的效果是一致的,只是反射可以调用类的私有方法。

在这里细心的同学就会发现,Class类本身提供了两个获取方法的方法,一个是getDeclaredMethod,另一个是getMethod。那这两者有什么区别呢?getDeclaredMethod用于获取所有的方法,包括私有方法。而getMethod则用于获取public方法,其它权限方法无法获得。

属性获取与赋值

属性的获取与方法类同:

    Field flagField = ClassABean.class.getDeclaredField("mFlag");flagField.setAccessible(true);ClassABean classABeanInstance = new ClassABean(true, new ClassBBean());boolean flag = (boolean) flagField.get(classABeanInstance);

这样就可以获得对象classABeanInstance的mFlag的值,同样的,我们还可以获得属性mIMethod的值:

    Field iMethodField = ClassABean.class.getDeclaredField("mIMethod");iMethodField.setAccessible(true);ClassABean classABeanInstance = new ClassABean(true, new ClassBBean());IMethod iMethod = (IMethod) iMethodField.get(classABeanInstance);

其中IMethod的具体实例为ClassBBean对象。

接下来我们演示一下如何替换属性的值,这种方式在很多地方都很常见,它的用途很广:

    Field iMethodField = ClassABean.class.getDeclaredField("mIMethod");iMethodField.setAccessible(true);ClassABean classABeanInstance = new ClassABean(true, new ClassBBean());ClassCBean classCBean = new ClassCBean();iMethodField.set(classABeanInstance, classCBean);IMethod iMethod = (IMethod) iMethodField.get(classABeanInstance);

通过这样的方式,我们再获取mIMethod的值将会是classCBean对象。我们在这里使用了set的方法,set方法用于给指定对象的属性赋值。

用例1(修改TextView的autoLink的点击实现)

相关文章介绍:如何修改TextView链接点击实现(包含链接生成与点击原理分析)

用例2(热修复实现)

相关文章介绍:一步步手动实现热修复

扩展了解

通过反射我们可以获得一个类的注解,它的父类以及实现的接口等。了解反射可以有助于我们实现抽象能力更强的框架。

扩展阅读:https://developer.android.google.cn/reference/java/lang/Class.html

参考地址

http://c.biancheng.net/cpp/html/1781.html
http://www.voidcn.com/blog/zbuger/article/p-5771880.html
http://www.fanyilun.me/2015/10/29/Java反射原理/
http://rednaxelafx.iteye.com/blog/548536
http://blog.csdn.net/u013551462/article/details/51261817

简明扼要的反射入门教程相关推荐

  1. Clojure入门教程: Clojure – Functional Programming for the JVM中文版

    http://xumingming.sinaapp.com/302/clojure-functional-programming-for-the-jvm-clojure-tutorial/ api:h ...

  2. 七牛服务器入门教程_教程:使用无服务器,StepFunction和StackStorm构建社区的入门应用程序…...

    七牛服务器入门教程 by Dmitri Zimine 由Dmitri Zimine 使用无服务器,StepFunction和StackStorm Exchange构建社区注册应用 (Building ...

  3. “易语言.飞扬”十分钟入门教程(修订版1,update for EF1.1.0)

    "易语言.飞扬"十分钟入门教程 (修订版1,update for EF1.1.0) 作者:liigo,2007.8.12 本文地址:http://blog.csdn.net/lii ...

  4. DWR入门教程(http://www.cnblogs.com/cyjch/archive/2012/02/16/2353758.html)

    文章转载自<http://www.cnblogs.com/cyjch/archive/2012/02/16/2353758.html>,多谢大牛分享! DWR入门教程 DWR(Direct ...

  5. Clojure入门教程

    Clojure入门教程: Clojure – Functional Programming for the JVM中文版 发表于 2011 年 12 月 07 日 由 xumingming 作者: x ...

  6. sql数据库教程百度云_【推荐】零基础水彩画入门教程|零基础水彩教程百度云...

    零基础水彩画入门教程|零基础水彩教程百度云! 照着教程画却总是画不好,这些水彩技法你真的学会了吗? 盲目地照着葫芦画瓢,不懂控制确实很难学会,可以关注一下公众号:每日学绘画,可以领取水彩电子书和全套视 ...

  7. “易语言.飞扬”十分钟入门教程

    "易语言.飞扬"十分钟入门教程 作者:liigo 2007.1.1 原文链接:http://blog.csdn.net/liigo/archive/2007/01/01/14720 ...

  8. Mono入门教程(一)--------------C++中整合Mono

    前言 本教程是针对需要将C#作为自己所开发的C++项目提供脚本的入门教程.由于mono教程相对较少,或缺乏完整性,故作此教程.虽然是教程,但是作者仍然是一个水平有限的程序员,因此文中如有纰漏请指正,我 ...

  9. ShaderToy入门教程(2) - 光照和相机

    回顾上一篇 ShaderToy入门教程(1) - SDF 和 Raymarching 算法 继续上篇,这篇涵盖以下黑体所示内容 符号距离函数 Ray-marching算法 曲面法线和光照 相机变换 构 ...

最新文章

  1. hbase源码系列(十二)Get、Scan在服务端是如何处理?
  2. Uber无人车撞死人,安全员被控过失杀人,算法免于追责
  3. 校园网站建设策划方案离不开三方面
  4. 最短路径算法——Dijkstra and Floyd算法
  5. 计算机网络思科实验,思科综合实验
  6. 腾讯二面,我被 “赛马” 问题难住了
  7. 使用map的find头文件_C++ map的基本操作和使用
  8. Protobuf生成Java代码(Maven)
  9. [原创]Nexus5 内核编译烧录过程记录
  10. 如何判断各个IE浏览器版本
  11. 图片裁剪,合成(设置透明背景)
  12. WIN10 U盘打开无权限问题
  13. PHP混淆zym解密
  14. Java用ListArray以人名的姓氏排队
  15. (附源码)基于PHP下的大学生校园交流论坛的设计与实现 毕业设计101634
  16. 2020计算机夏令营+预推免统计
  17. 取消wps右键菜单_iRightMouse for Mac (超级右键鼠标辅助工具)
  18. VirtualBox 搭建虚拟环境
  19. matlab多变量复相关分析,Matlab多变量回归分析教程
  20. nuxt服务端渲染技术

热门文章

  1. 20000W的电灯泡,真的是叼炸天
  2. java 对象 序列化 文件中_如何将一个java对象序列化到文件里
  3. hive 语句总结_Hive常用命令总结
  4. python bp神经网络分类预测结果图_深度学习入门(四)BP神经网络——数字分类...
  5. 十、CSS三行代码实现 溢出的文字省略号显示(white-space: nowrap;overflow: hidden;text-overflow: ellipsis;)
  6. python web开发 CSS基础
  7. LeetCode 996. 正方形数组的数目(回溯+剪枝)
  8. LeetCode 410. 分割数组的最大值(极小极大化 二分查找 / DP)
  9. LeetCode 1503. 所有蚂蚁掉下来前的最后一刻(脑筋急转弯)
  10. 如何在linux中使用u盘,如何在Linux系统下使用U盘