在调用Integer.toString()时,在Groovy中动态更改Object.toString()无效

时间:2017-10-14 16:30:13

标签: groovy metaprogramming

我将重写的方法toString注入Object.metaClass

Object.metaClass.toString ={
   System.out.println("the string is $delegate")
}

我认为以下代码将执行此方法:

1500.toString()

但它没有,没有任何东西被打印到控制台。这让我感到困惑:如果事情变坏了,那么错误就是抛弃;如果找到并调用Object.metaClass.toString,那么该消息将会出现,但为什么它不起作用?里面发生了什么?

2 个答案:

答案 0 :(得分:2)

此行为是正确的,因为java.lang.Integer会使用自己的实现覆盖Object.toString()。如果您的假设是正确的,则意味着您可以通过强制使用父类的实现来中断重写方法。

考虑使用Groovy脚本:

Object.metaClass.toString = {
    System.out.println("the string is $delegate")
}

class GroovyClassWithNoToString {}

class GroovyClassWithToString {
    @Override
    String toString() {
        return "aaaa"
    }
}

new GroovyClassWithNoToString().toString()

new GroovyClassWithToString().toString()

1500.toString()

Runtime.runtime.toString()

当你运行它时,你会看到类似的东西:

the string is GroovyClassWithNoToString@3a93b025
the string is java.lang.Runtime@128d2484

您可以看到GroovyClassWithNoToString.toString()调用Object.toString()方法及其修改后的版本,Runtime.toString()调用Object.toString() - 我选择此类作为纯Java类的示例不要覆盖toString()方法。

正如您所见,来自toString()级别的覆盖Object方法对于基于Object.toString()实现的类是有意义的。提供自己toString()实现的类不会使用动态修改的方法。它还解释了为什么以下代码有效:

Object.metaClass.printMessage = {
    System.out.println("Hello!")
}

1500.printMessage()

在此示例中,我们向printMessage()类添加了一个名为Object的新方法,并且所有不覆盖此方法的类都将使用我们刚创建的动态方法。 Integer类没有像那样的方法,所以它会打印出来:

Hello!

正如所料。

另请注意,toString()应返回String,最好不要在此方法中输出任何内容 - 您最终可能会因循环调用而导致讨厌的StackOverflowError toString()方法。

更新:Groovy运行时如何选择toString()方法?

让我向您展示当我们调用以下脚本时会发生什么:

Object.metaClass.toString = {
    System.out.println("Hello!")
}

1500.toString()

让我们看一下运行时Groovy是什么。 Groovy使用元对象协议(MOP)来例如调用Groovy代码中调用的任何方法。简而言之,当您调用任何Java或Groovy方法时,它使用MOP作为中间层来查找方法的执行计划 - 直接调用它或使用例如一种动态注入的方法。

在我们的例子中,我们使用普通的Java类 - Integer。在这种情况下,Groovy将为Java类的元类实现创建PojoMetaMethodSite类的实例 - Integer。每个元方法都使用Groovy groovy.lang.MetaClass实现之一执行。在这种情况下,正在使用groovy.lang.MetaClassImpl。选择执行方法的最后一种方法是MetaClassImpl.getMethodWithCachingInternal(Class sender, CallSite site, Class [] params)。如果在此方法的开头放置断点并使用调试器运行脚本,您将看到使用以下参数执行此方法:

enter image description here

在第1331行中,您可以看到正在使用名为chooseMethod(e.name, methods, params)的辅助方法:

cacheEntry = new MetaMethodIndex.CacheEntry (params, (MetaMethod) chooseMethod(e.name, methods, params));

当我们尝试在toString()对象上调用Integer时,此方法负责选择正确的执行方法。让我们到那儿看看会发生什么。以下是此方法实现的内容:

/**
 * Chooses the correct method to use from a list of methods which match by
 * name.
 *
 * @param methodOrList   the possible methods to choose from
 * @param arguments
 */
protected Object chooseMethod(String methodName, Object methodOrList, Class[] arguments) {
    Object method = chooseMethodInternal(methodName, methodOrList, arguments);
    if (method instanceof GeneratedMetaMethod.Proxy)
        return ((GeneratedMetaMethod.Proxy)method).proxy ();
    return method;
}
  

来源:https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/MetaClassImpl.java#L3158

现在让我们看一下调用脚本时收到的参数:

enter image description here

在我们的案例中最有趣的是methodOrList.data的第一个元素。这是一个方法对象:

public java.lang.String java.lang.Integer.toString()

这是toString()类从其父类重写的方法Integer。 Groovy运行时选择此方法,因为从运行时的角度来看它是最准确的 - 它是Integer类提供的最具体的方法。如果在类级别没有覆盖toString()方法(例如我之前提到的Runtime类示例),那么调用toString()方法的最佳候选者是我们提供的ClosureMetaMethodObject.metaClass.toString = ...。我希望它能让你更好地了解幕后发生的事情。

答案 1 :(得分:1)

我认为你不能以这种方式覆盖Object.toString()。

但这有效:

Integer.metaClass.toString = { ->
   System.out.println("the string is $delegate")
}

https://groovyconsole.appspot.com/script/5077208682987520