Groovy
使用ruby也好久了,虽然钟情于ruby语法的无穷灵活性,不过一直苦于没法使用java平台上各种各样的框架。虽然也早有jruby这样的存在,但是速度还是很成问题,而且这种间接的调用,使用起来也感觉不舒服。
学习了groovy之后,我觉得,如果喜欢ruby的朋友,都可以来试试jvm平台上的这门动态语言,其元编程的强大能力与ruby可一较高下。
(其实,java平台上有各种语言的翻版,例如kotlin对c#,scala对haskell,Clojure对lisp,groovy对ruby,使用这些语言的好处就是可以尽情使用java平台上各种框架和库,毕竟学习java,投资在框架和库上的学习时间也是很多的,如果使用jvm语言的话,这种投资不至于浪费掉)
(ps:我一直觉得,如果使用动态语言而不使用元编程,就失去了使用动态语言的意义。)
大部分的动态语言,其实现面向对象的机制,基本都是原型链(因为我最初学习的是as和js所以习惯这么叫),依靠在原型链上通过字符串的动态查询,得到字段或者方法,在ruby中因为不需要兼容java的关系,其所有的类,字段都是在原型链上,所以甚至都可以在运行时去掉。js甚至还支持字面量对象,即不属性任何类的原始对象。
groovy因为本身同时还支持java式的传统类继承,所以他的原型链,是又搞了一个metaClass出来,这个metaClass保存的是各种扩展方法和字段。
//使用字符串动态调用方法
println "1234"."length"()
println "1234".invokeMethod("length",null)
//动态获取属性
println "1234"["bytes"]
println "1234"."bytes"
//但是这些Java的反射也能做到,没什么大不了的,继续往下看
java自身支持一定的反射,在运行时可以从xx.class对象中查询类的方法和字段,但是并不能修改。因为不能破坏与java的兼容性,所以在groovy中还是可以通过xx.class使用原来java的那套反射方法,但是还可以访问xx.metaClass,概念和这个类似,不过在metaClass中可以添加新的方法。
下面一幅图,很好的说明了字段和方法是如何在运行时被动态查询的,和js和ruby之类的动态语言类似,但是因为groovy需要与java兼容,所以稍微复杂了一些。
这幅图看上去颇为繁琐,完成一件事情也有好多种方法。实际上,基本只用一种在其他动态语言中常用的方法就足够了,那就是操作metaClass,而不是在原类上做动作(其实我不明白groovy支持这些额外的路由特性有什么意义)
使用metaClass.invokeMethod来拦截方法调用。
String.metaClass.invokeMethod={
String name,args->
System.out.println("调用方法:$name")
//导向原方法
Integer.metaClass.getMetaMethod(name,args)?.invoke(delegate,name,args)
}
"1234".length()
//最后会输出 调用方法:length
类似java的动态代理,以及现在火热的aop面向切面编程,但是传统的做法是采用特殊的编译器将代码打入到字节码中,这种直接在语言中编写的做法明显更加灵活。
如果只需要拦截对不存在方法的调用,则只需要覆盖metaClass的methodMissing方法,可以观察上面的动态查询图来了解原因。
在现有类里加入方法,groovy一共有三种方式。
第一种是类似于c#,kotlin的扩展方法
//创建静态类
class Strings{
def static length2(String str)
{
str.length()
}
}
//使用use来限制作用域
use(Strings){
//在作用域内,可以像调用扩展方法一样调用类的静态方法
println "1234".length2()
}
和一般性扩展方法不同的就是,它可以限制作用域。
一般来说,我还是喜欢下面这种动态语言风格的元对象注入
//直接在metaClass上加入方法
String.metaClass.length3={
delegate.length()
}
println "1234".length3()
//和js的很像
String.prototype.length=function () {
}
//还可以向单独的实例中添加方法,和上面的唯一区别就是只访问实例的metaClass
def str="1234"
str.metaClass.length4={
delegate.length()
}
println str.length4()
还有一种方法就是所谓的mixin,这种技术的毛病我在之前一篇文章就提过了http://blog.youkuaiyun.com/o83290102o5/article/details/79034497
//定义需要被Mixin的类
class Methods{
def method1()
{}
}
@Mixin(Methods)
class TestClass{
}
new TestClass().method1()
上面三种方法,use我基本不用,也没见什么别的语言有这特性,真正需要用到的时候不多。
metaClass上添加属性和方法非常常用。
mixin的话,一般使用的时候需要比较谨慎,否则一不小心就会覆盖掉原来的方法
原型链继承
还是要强调的是,groovy与js和ruby这种动态语言最本质的不同就是,groovy它要考虑对原有java代码的兼容性,所以它的metaClass,只是一个在类继承结构基础上的附带物,就算你把它设置为null,原来类继承中该有的东西还是有,但如果你在js或ruby中这样做,这个类就会脱离原型链,它继承而来的方法会消失,字段也不能访问到了。
//设置metaClass为null,只是动态添加的字段和方法消失,对原来的字段方法无影响
//但是如果在js或者ruby中执行对等的操作,例如js将String.prototype=null,那么所有继承来的字段和方法都会消失
str.metaClass=null
println str.length()
下面是单纯用原型链来实现继承
//我们使用一种类似js的方式来编程
//父类的父类
class Grandfather{
}
Grandfather.metaClass.grandfatherMethod={
println "grandfather"
}
//父类
class Father{
}
//继承
Father.metaClass.mixin(Grandfather)
//父类方法
Father.metaClass.fatherMethod={
println "fatherMethod"
}
//上面没有使用传统的类继承结构
//我们现在用原型链继承来模拟继承
//子类
class Child{
}
Child.metaClass.mixin(Father)
//子类方法
Child.metaClass.childMethod={
println "childMethod"
}
new Child().grandfatherMethod()
(ps:同时支持类继承和原型链继承的语言我还知道一个as3,但是估计很多用过as3的人也不知道它还支持原型链。其实,如果在严格模式下使用原型链继承,编译器会报错,这说明一个语言中共存两种继承模式,本来就相当混乱,不应该提倡)
groovy用起来还是挺不错的,唯一让我觉得没ruby那么舒服的就是,为了和java的兼容性,使得它的动态特性不是那么纯粹
最后推荐一本书《ruby元编程》,虽然讲的不是groovy,但因为groovy和ruby很像,所以对学习元编程也是很有好处的。(况且讲groovy本身的书本来就没几本。)
欢迎关注我的github
https://github.com/luckyCatMiao