反射是我学习Java时遇到的最大障碍,因为太抽象了。
我当时是跟着网络上的培训班视频自学的,视频里的老师说:类是对现实事物的抽象,而Class是对类的抽象。这句话对于刚开始学习Java的人来说,无异于天书。我当时的理解是:根据类,可以创建对象,所以类是比对象高一级的抽象。如果要对类进一步抽象,它应该是什么形式呢?
我无法想象。在我的认知里,Java除了类还是类,类层级的抽象似乎已经到尽头了。
如何对类进行抽象?
许多年后,再次回想那段经历,我发现自己当时陷入了一个思维陷阱,误以为"用类抽象类"是一个死循环,把自己困在里面了。
其实,换个角度这个问题就迎刃而解了。Java用类对世间万物进行抽象,包括人、动物、甚至空气、键盘、鼠标,那么用Java语言编写的类文件(xxx.java)不也是世间万物的一种吗?是不是也能用类表示?老师说的那句话没错,但更准确的说法是:
对于Java而言,类是对现实事物的抽象,其中有一个Class类用来描述Java类(文件)
Class类也是一个类,只不过它描述的事物比较特殊:Java类
按照这个思路,我们尝试写一个Class类,用来描述Java中的"类"。好,想法有了,怎么开始呢? 所谓抽象,就是抽取相像的。什么叫相像?单个事物肯定不能说相像,因为相像是相互比对后得出的结论。所以,我们至少要先编写两个类。
抽象最忌讳一上来就关注细节,这样是很难得到一个好的抽象模型的。反之,在设计之初应该观其大略,从大处着手"找共性"。
它们都是Class,它们都有Constructor、Field、Method
啊?这也太粗粒度了吧!别急,再慢慢细化就是了:
方法可以细分为:访问修饰符、返回值类型、方法名、参数列表
如果你足够耐心,还可以继续细化,比如Parameter可以进一步细分为:修饰符、参数类型、参数名、参数索引(第几个参数)。由于Java已经替我们做好抽象,这里就不再继续重复劳动了。
其他的就不列举了,总之最终我们会得到很多个大小模型,这些模型相互协作共同描绘了"Java类"这一抽象。
Class对象
什么是Class对象?简单来说,就是Class类的实例。
Java有一个Class类(上面截图第一个),那么按理说我们应该可以new一个Class对象。但Class类是个狠角色,它不仅对自己进行了阉割(final class,不允许继承),还隐藏了构造器(final constructor,不允许new)。也就是说,正常途径下没有人能创建Class对象,也不会有Class的子类。
官方说得很清楚:Only the Java Virtual Machine creates Class objects.
那虚拟机创建的Class对象长啥样呢?
在Java中,每个类在首次加载时都会有一个对应的Class对象被创建。
当你执行new User()时,JVM 会检查是否已经加载了User类:
- 如果没有加载,它会先加载User类,并创建User类的Class对象,然后根据该对象创建User类的实例。
- 否则,直接找到User类的Class对象并创建User类的实例。
也就是说,我们一直以为new User()的步骤是 User类 ==> User对象,但实际上是 User类 ==> User类的Class对象 ==> User对象。 注意,这里有一个反直觉的地方:
你说Class类无法直接通过new Class()创建Class对象,只能由JVM创建Class对象。好,我虽然不理解,但也认了。然而,为什么JVM创建的是Student Class对象?一个Class类,怎么会创建出Student Class对象呢?
我可没说JVM创建了Student Class对象。上面图中所说的Class对象(Student),指的是这个Class对象包含的信息来自Student类。
这样一来,顺便也解答了为什么Class类无法直接创建Class对象这个谜团:
Class对象是用来封装类信息的,直接new Class()得到的是空对象,所以干脆禁止了
也就是说,Class对象是用来封装类信息的,直接new Class()并不能与任何类产生关联,最终得到的是没有实际意义的空对象。于是Java干脆把new Class()这条路给堵了,当类加载时由JVM创建Class对象。这样不仅能由JVM保证同一个类只有一个Class对象(类加载是单线程的),还能保证Class对象是有意义的(包含具体类的信息)。
其他的Method对象、Field对象、Constructor对象也是同样的道理,分别是用来封装方法信息、字段信息、构造函数信息的。
重新理解反射
到这,反射就很好理解了。Java的反射机制由reflect package提供支持:
我们可以将反射机制的构成简单理解为:Class信息 + 获取/操作这些信息的API。Java将类的信息抽象成各种数据,并提供API让我们获取这些数据。
这里只提一点:最好把Class/Method/Field等对象理解为"执行器"。 以Method对象为例,它就像一个方法执行器,当你想要执行某个对象的某个方法时,根本不用求这个对象本身,而是去找到Method,把对象+参数告诉它,让Method帮你执行。
从companyClass获取echoMethod(执行器),传入company对象+参数,执行方法
这就像什么呢?有一个老师傅,一直不肯使出本门剑法的最后一招。正常来说,你要求他:师傅,请你调用一下剑法的最后一招让徒孙开开眼吧!现在有了反射,你就不用求他了,直接找到Method,把师傅(Object)绑了,并且找到师傅的剑(Param),人和剑一块儿都塞给Method,然后Method催动内力,逼着师傅拿着剑把最后一招耍出来了。
如果不是public的字段和方法,需要在操作前调用setAccessible(true)
大家有没有看过成龙的《醉拳》,酒鬼师傅为了教成龙功夫,用两根竹竿拴住他的双手,操作他做出各种招式。Method也是如此,原本是对象自己调用方法,现在则是反射调用方法。但前提是,必须要有对象,你才能操(qiang)作(po)对象执行方法。
还有一个问题,常常让人感到困惑:我们把对象和参数传给Method让它帮忙执行,这固然没错,但既然已经有目标对象了,为什么不直接调用对象的方法呢?
这是因为你看的角度不对。还是以上面的醉拳师傅为例,在还不知道是哪位徒弟、甚至没有徒弟的时候,师傅就要先准备好这一套训练方法。后面如果有人要学醉拳,直接套上竹竿就可以了。也就是说,反射是一种面向未来的通用处理方式,未来虽未来,但我的代码已经存在!
arduino
class 通用设计 {
public void 通用操作(obj ,param) {
// 操作1 2 3
method.invoke(obj, param); // 这里如果写成user.sing(),就不通用了
// 操作 4 5 6
}
}
反射是非常强大的元编程能力,是很多中间件和框架的底层支撑。
花了半年时间实现了GOF23种设计模式,gitee已开源:设计模式那些事儿