写在前面
本文地址:http://www.javashuo.com/article/p-mpsxbqsx-kh.html
这里是Oberon
本文容许转载,但请注明出处!若有转载,请通知做者本人!
不反对您转载,可是你说把我原来的格式都转没了,我这文风再玩点梗啥的,你这一转载把你们带沟里你说这算你责任算我责任呢。
至少博客园里能保证一个完整的格式,能让读者知道我哪句话在扯皮,那句话在说正经事
并且不建议轻易地转载个人这篇文章,我对于我写的东西也没有十足的把握保证足够严谨,都是我本身的我的理解。
若是您没有通知我就转载并被我发现了并且还转的一塌糊涂,我会把您地址附到这里,就像这样html以后还请您阅读愉快编辑器
反射是光在两种物质分界面上改变传播方向又返回原来物质中的现象
反射是生物体对外界刺激作出应激行为的过程,根据产生的缘由分为条件反射和非条件反射等,典型的实验案例包括巴甫洛夫的狗……
反射是一些面向对象程序设计语言提供的针对类对象元数据(Metadata)的一种访问机制。函数
啊,诚然,一旦涉及到“元XXX”事情一般就开始变得无比抽象,以致于我不由念叨起那句诀flex
太极生两仪,两仪生四象,四象生八卦……ui
不过元数据这个概念在数据库里仍是比较常见的,好比,某个关系型数据库里有张表:加密
水果设计
编号 名字 数量 1 苹果 6 2 香蕉 3 3 梨 5 4 橘子 3 5 菠萝 2
数据,就是存在表里的一条一条的记录,(1,苹果,6),(3,梨,5)都是数据,那么,元数据就是凌驾于这些数据之上的用于描述数据的数据,对于这张表而言,也就是这张表的表头(关系数据理论里称之为关系模式):(编号,名称,数据)
。指针
划重点
元数据(Metadata):用于描述数据的数据code
众所周知,类(Class)是面向对象的一个重要概念,尽管,针对于数据库来讲,对象模型和关系模型是不一样的概念(上文提到的是关系模型的一个例子),可是,对象模型中的对象和关系模型中的关系,其级别是等同的。
好吧,咱们先把关系放在一边,咱们只把上边的东西看作一张表。
难道你就没有把它改写成以下形式的冲动吗??
public class Fruit { public int no; public string name; public int count; public Fruit(int no, string name, int count) { // ... } }
好了,上面的类定义的语义就是
有这样一类东西,咱们称呼这类东西为水果,结构以下……
那么,这样一来,咱们就能够定义一个no
为9,name
叫作“西瓜”,count
为5的一个对象,这个对象具备具体的数据。
而上面的类定义代码,包含的就是这个类的元数据。
以人为例,数据注重的是这人的脸长啥样,而元数据注重的是这人有没有脸(好像不太对……)
反射是一些面向对象程序设计语言提供的针对类对象元数据(Metadata)的一种访问机制。
本文一开始就说了,罚站20年!
不过在此以前先解释一件事,元数据在哪。
任何一个面向对象的程序设计语言,其类类型都具有一个元数据的存储,至少程序会使用这个元数据可以动态地构造此类的对象。但不一样的语言机制不一样,好比C++这种的,由于直接和系统进行愉♂快的互♂动,所以元数据就直接使用系统的内存地址了,这种数据使用是很不直观的,同时也不使用任何托管机制作后援(巨硬魔改的C++/CLI不在讨论范围内),所以这种贴近底层的语言不支持反射机制,虽然能够经过强行向程序代码中经过工厂类模式强行注入可读的元信息(方法参见这位大佬的文章)。
可是,正如前面所说的,若是元数据在托管编译或解释的状态下会保留一份可读的版本,这是提供给解释器或者托管平台用的,固然,这种状况下语言通常会提供一个较为完善的元数据访问机制,这就是反射。这类语言典型的表明就是C#(.NET托管)、Java(JVM虚拟机)、Python(解释器提供)等。
反射嘛。那还不容易,拿个镜子就能够了呀!
或者用羊角锤以偷袭的方式砸膝盖什么的也是很容易的呀!
不过这么说来,拿羊角锤偷袭镜子岂不是更棒!!
正如以前所说,反射机制是对类的元数据的获取和操纵,所以,一个重要的前提就是:
这个程序设计语言的运做机制当中,类的元数据必须是可见的,若是可读的话那更好
只有当类的元数据是可见的,反射机制才有访问它们的可能,可是元数据的可读性会决定反射机制访问它们的难易程度。
这里补充一句,有人会说,在使用IDE或者代码编辑器的时候,咱们写object.property
这种访问方式的时候编译器不就直接告诉咱们了么??
关于这一点,这里暂时只说一个前提:
反射机制的实际动做是聚焦于运行时(Runtime)的。
在程序代码编译以前咱们恣意地书写这MyObject.id.hashCode.getFlush().balabala
的时候,这是预编译的过程,预编译的时候固然这些元数据都是以字面形式给出的(由于你的代码里写了这个类的定义),你能够很是愉悦地Ctrl+C
Ctrl+V
或者享受着IntelliSense带给你的N倍快乐,这个时候再谈反射就没什么意义了,所以,反射机制访问元数据都是在编译后的运行时发生的。
以C++为例,这些元数据是否可见?答案是确定的,那为何不支持反射机制呢,由于这些元数据是以指针的方式给出的,指针在已编译的C++程序中的存在形式就是地址,说的再粗暴点,就是4或8字节的二进制数……
也就是说,在已经编译完成的C++程序的眼里,类的元数据已经变成二进制的地址码了,若是某人在没有源代码的状况下想给这个项目写一个反射机制,那么他将不得不面对一大堆的:
0xb08dfe231a1c002e 0xb08dfe231bc128f6 0xb08dfe2417a90f5d ......
看到这些,他长舒了一口气,优雅地点燃了一根香烟,而后绝不犹豫地戳到电脑屏幕上:
鬼知道这是什么玩意啊!!
若是原项目加个壳、模板元编一下再作个混淆加密的话那更无法看了,所以若是必定要实现反射机制,通常都是把反射机制直接囊括到项目开发过程中(就像上面那位大佬的文章中提到的那样,原项目的做者也是反射机制的构造者)。
这样的话就会存在一个上上上个世纪汽车行业出现的问题:
这辆车的件没法用到另一辆车上!
这个反射机制没法用到别的项目上!
固然,这样说可能有些绝对,但以C++的方式实现一个可以普遍用于全部项目的反射机制应该是极端困难的。
上面大佬的文章当中,这个C++的项目要使用反射机制,是借助工厂模式实现的,关于这些的实现方法,详见大佬文章(固然我本身也没彻底看懂)
C#、Java,这两种语言都是托管代码的(C#使用.NET进行托管,Java则交给了JVM虚拟机)。
与C++不一样的是,他们并不直接接触系统底层,而是经过中间代码访问底层的。
中间代码由谁处理呢,C#是经过.NET提供的CLR,产生的中间语言是程序集,而Java靠的是JVM,其中间产物是class文件。
若是有幸使用一些IDE打开这两个文件往里窥探一遭的话,咱们应当不难从中找到这些元数据的信息。
这就好像,一群孩子进了幼儿园,一个托管老师全程进行看护。
把拔码麻区上办,我区悠贰园呐
固然,托管老师确定是知道孩子叫什么名的,访问他们天然也是很容易的。同理,托管环境(或虚拟环境)也是同样的,由于衔接上下两层,所以把底层的元数据和上层的可读文本构造反射的桥梁是很容易办到的,所以,C#和Java都提供了一套很是完善的反射库,他们能够被用于使用这两种语言写的任意一个类当中。
举个最简单的例子
我……我有一个梦想,我想要这样一个函数,可以返回Person类是否有我所说的方法,可是我不知道Person类里有什么,好比我想问他有没有
Eat()
方法,它返回true
,我问他有没有Fly()
方法,它能返回false
好了,换做是你,你会怎么实现这样一个函数呢??
而反射机制偏偏作到了!
你提供给反射机制一个字符串形式的函数名,反射机制不只能够得知这个函数是否存在,甚至能帮助你去执行这个函数(Invoke
)。
什么,你很差问它有没有某个函数??好啊,反射机制甚至能够告诉你这个类都有哪些属性哪些函数,继承自谁,可见性如何,是否抽象等等。
上面那个例子其实就是一个经典的用途。
或者,咱们能够考虑另一个场景。
你写了某个函数接受了一个抽象为
Object
的对象,你但愿,若是Object的对象存在方法Grow
则调用之,不然什么也不作。
这个时候首先能够经过反射机制肯定方法是否存在,但即使方法已经存在,咱们是没法直接调用的,由于对象已经抽象为Object
,而Object
并不存在方法Grow
,因此直接调用就洗洗睡了。
若是咱们知道类在抽象以前是什么类型的时候,那固然能够具象化回来。
可是抽象虽然发生于编译时或运行时(动态建立的对象),但具象类型的获知倒是在编译以前的代码源文件,并且还有些时候你根本没法知道原类型,那也没办法拆箱。
注
这里面我为了方便,也是想不出啥更好的词
这里我称派生类向基类的多态转化为抽象
反过来的过程称为具象
Grow
呢反射机制能够获取到完整的可用方法的列表,咱们在列表中找到了Grow
,存在形式为Method/MethodInfo
对象或干脆就是个字符串。
但不管是哪一种,obj.Grow();
是不可能了,好在反射机制连这件事都考虑在内了——Invoke
调用!!
反射机制不只知道你想要什么方法,还能够帮助你调用这个方法,这个调用就经过一个叫作Invoke
的方法完成。
不一样语言对Invoke
的定义不尽相同但功能上大同小异,经过Invoke
调用某方法的过程实质上是转调和回调(或者是间接调用)。
间接调用比直接调用更加的强大灵活,但绕了远路。
宏大一点……好吧,其实每个磅礴的工程都是从一点一滴作起的。
一个很经典的案例,就是上文那篇大佬文章里的一个经常使用功能——序列化(Serialization)
虽然C#和Java自己就有能够用于序列化的一些结构和功能库(Serializable
接口之类的),可是有些时候咱们对序列化机制若是有更高的可定制性要求的时候,咱们每每倾向于本身构建一套心仪的序列化功能库。
因而乎就有一个最简单的问题摆在面前:
如今有
Class1
类的对象,还有Class2
类的对象,还有Class3
类的一些对象想要转化成可解析的内容,以供发送或保存(固然这也就意味着,这些对象的全部属性和状态都要保存),可是这老大老二老三一家一个样,属性也各不相同,我又不想挨个单独写,那该怎么办呢??
如今有了反射机制,问题就很容易解决了。三胞胎嫌分起来麻烦??反射机制能够把他们安排的明明白白!!你能够向反射类提供一个完整的类名,反射机制就能保证给你这个类对应的可用属性的列表,以及一整套处理方案(Get和Set),以后还不是想来啥来啥,美滋滋~~
固然,以上都是反射机制用途中小的不能再小的冰山一角,好比我还能够经过反射机制根据个人输入建立我想要的类型的对象等等。
冷静点!任何事物都有多面性,反射也不例外,咱们看看反射机制有什么特色,它究竟是否适合全部情形。
稀有属性
反射机制可让你的代码很是灵活,以不变应万变。
这也正是反射机制带来的最大的好处。
普通属性
反射机制是在运行时起做用,固然,运行期间发生什么,编译以前是没法获知的,反射就是处理这件事的。
糟糕属性
反射机制最大的问题!
反射机制的效率是十分低下的,首先在运行时获取元数据再转化成可读形式就不是一个很快的过程,而反射的Invoke调用是个彻彻底底的间接调用。
不当地使用大量反射会致使程序效率的急剧降低。
显然,用反射进行调用的代码每每比直接调用写起来复杂,因此除非你写代码是按行数计工资,不然能直接调用就不要反射。
反射机制通常容许用户传入字符串……
而后就是万劫不复深渊之伊始
这时候用户传的字符串就能够很是的五花八门了,就好像一个动物园里,反射机制是一个可爱的小动物,而游客开始不分青红皂白地对它投各类食,参差不齐,但是你的反射机制很脆弱,它可禁不起这折腾,吃到很差的东西就会生病罢工(抛异常,而后停止),所以你这当奶妈奶爸就要多操心,帮它收拾(捕获),告诉他如何分辨食物(预先判断)……
不过呢,有些时候引入反射机制偏偏就是出于健壮性的考虑……
若是我养的不是个反射机制而是一只熊猫的话我会上天的!!
反射是个强大的武器,但使用应多加谨慎!
以上