Groovy:this.metaClass 与 instance.metaClass

Posted

技术标签:

【中文标题】Groovy:this.metaClass 与 instance.metaClass【英文标题】:Groovy: this.metaClass Versus instance.metaClass 【发布时间】:2016-03-30 17:01:42 【问题描述】:

我在书中遇到过下面的 groovy 脚本代码。它给我带来了一些奇怪的输出。

class Person
  def work()
    println "work()"
  
  def sports=['basketball','football','voleyball']
  def methodMissing(String name, args)
    if(name in sports)
        println "injected $name into Person class"
        Person instance=this
        println "this.metaClass:\t\t$this.metaClass"
        println "instance.metaClass:\t$instance.metaClass"
        assert this.metaClass==instance.metaClass
    else
        println "no such method:$name() in Person class"
    
  

def jack=new Person()
jack.football()

输出如下:

injected football into Person class
this.metaClass:     groovy.lang.MetaClassImpl@245b4bdc[class Person]
instance.metaClass: org.codehaus.groovy.runtime.HandleMetaClass@245b4bdc[groovy.lang.MetaClassImpl@245b4bdc[class Person]]
Caught: Assertion failed: 
//I did not paste the detailed assertion here for simplicity

所以我很困惑:

    为什么 this.metaClass 不等于 instance.metaClass? 还有,我不能用this.metaClass来注入新方法; groovy 告诉我 this.metaClass 没有我打算注入的此类属性。 “org.codehaus.groovy.runtime.HandleMetaClass@245b4bdc[groovy.lang.MetaClassImpl@245b4bdc[class Person]]”是什么意思?我知道“245b4bdc”可能是对象指针。但是为什么 HandleMetaClass 和 MetaClassImpl 的指针值相同“245b4bdc”?

目前,我发现@245b4bdc 不是“对象引用”,所以 HandleMetaClass@245b4bdc 不一定是与 MetaClassImpl@245b4bdc 相同的实例。我们可以使用 Object.is() 方法来判断它们是否相同。(我这样做了,结果是 false

【问题讨论】:

如果你把它改成assert this.class.metaClass == instance.metaClass,它就会通过。 为什么? this.class.metaClass == Person.metaClass 应该吗? 在这里找到了更好的答案,***.com/a/45407488/42769,其实就是我。 【参考方案1】:

    为什么 this.metaClass != instance.metaClass?

    它涉及到 Groove 对字段的访问。

    当从“outside”访问实例字段时,groovy 实际上调用了函数 getFieldName()。在我的示例中,当我使用“instance”时,我处于外部;所以 instance.metaClass 会调用 instance.getMetaClass()

    从“inside”访问实例字段时,groovy 直接访问该字段,不会调用getFieldName()。在我们的示例中,当我使用“this”时,我处于“inside”;所以“this.metaClass”会直接访问“metaClass”。

    最后,getMetaClass() 返回一个 HandleMetaClass 对象,而内部的 metaClass 是一个 MetaClassImpl 对象。所以this.metaClass!=instance.metaClass

    为什么 this.metaClass.say=->println "say" 会抛出 MissingPropertyException?

    this.metaClass 的类型是 MetaClassImpl

    MetaClassImpl 是一个底层类,它支持上层类(例如HandleMetaClass)进行注入。不适合开发者直接使用,所以不支持注入方式:xxxx.say=->println "say"

代码示例(针对问题 1):

class Person
  def work()
    println "work()"
  
  def sports=['basketball','football','voleyball']
  def methodMissing(String name, args)
    if(name in sports)
        Person instance=this

        println "this.metaClass:\n\t$this.metaClass"
        println "instance.metaClass:\n\t$instance.metaClass"
        //output: false
        println "this.metaClass.is(instance.metaClass):\n\t$this.metaClass.is(instance.metaClass)"

        //output: true
        println "this.getMetaClass().is(instance.getMetaClass()):\n\t$this.getMetaClass().is(instance.getMetaClass())"

    else
        println "no such method:$name() in Person class"
    
  

def jack=new Person()
jack.football()
jack.football()

代码示例(对于问题 2):

class Cat
    def a=new groovy.lang.MetaClassImpl(Cat)
try
    a.say=->println "say"
catch(MissingPropertyException e)
    println "[Fail]\n\tcan not inject method say() into MetaClassImpl class.\n"


def b=new org.codehaus.groovy.runtime.HandleMetaClass(a)
println b
b.say=->println "[say]"
println "[OK]\n\tcan inject method say() into HandleMetaClass class\n"
def method=b.getMetaMethod("say")
method.invoke(this)

【讨论】:

那么当当前类中没有定义metaClass时,它是如何进行字段访问的呢?也许它的访问修饰符在其Object 超类中受到保护?但无论如何,根据 groovy 文档,为了获得字段访问权限,需要从定义它的类中访问该属性。 metaClass 没有在 Person 子类中定义,所以我想知道字段访问是如何完成的。

以上是关于Groovy:this.metaClass 与 instance.metaClass的主要内容,如果未能解决你的问题,请参考以下文章

Groovy 中的安全导航运算符

Groovy06_Groovy方法扩展与脚本调用

Groovy06_Groovy方法扩展与脚本调用

groovy与java语法区别

Groovy05_Groovy方法调用与运算符重载

Groovy与Gradle在Android中的应用