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

标签 groovy metaprogramming

我注入(inject)了被覆盖的方法toString进入 Object.metaClass :

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

我认为以下代码将执行此方法:
1500.toString()

但事实并非如此,控制台上没有打印任何内容。这正是让我感到困惑的地方:如果出现问题,就会抛出错误;如果 Object.metaClass.toString找到并调用,然后消息会出现,但为什么它不起作用?里面发生了什么?

最佳答案

这种行为是正确的,因为 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() - 我选择这个类作为不覆盖 toString() 的纯 Java 类的示例方法。

如您所见,覆盖 toString()来自 Object 的方法level 对于基于 Object.toString() 的类有意义执行。提供自己的 toString() 实现的类不会使用您动态修改的方法。它还解释了为什么以下代码有效:
Object.metaClass.printMessage = {
    System.out.println("Hello!")
}

1500.printMessage()

在本例中,我们添加了一个名为 printMessage() 的新方法。至Object class 和所有不覆盖此方法的类将使用我们刚刚创建的这个动态方法。 Integer类没有这样的方法,所以它会打印出来:
Hello!

正如预期的那样。

还要记住 toString()应该返回 String最好不要在这个方法中打印任何输出 - 你可能会得到讨厌的 StackOverflowError由对 toString() 的循环调用引起方法。

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

让我向您展示当我们调用以下脚本时会发生什么:
Object.metaClass.toString = {
    System.out.println("Hello!")
}

1500.toString()

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

在我们的例子中,我们使用普通的 Java 类 - Integer .在这种情况下,Groovy 将创建 PojoMetaMethodSite 的实例。 Java 类的类到元类实现 - 一个 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;
}

Source: 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 的最具体的方法。提供的类(class)。如果没有toString()在类级别重写的方法(例如,我前面提到的 Runtime 类示例)然后是调用 toString() 的最佳候选者方法是 ClosureMetaMethod由我们提供 Object.metaClass.toString = ... .我希望它能让您更好地了解幕后发生的事情。

关于groovy - 调用 Integer.toString() 时,在 Groovy 中动态更改 Object.toString() 无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46746809/

相关文章:

grails - 使用电子邮件服务的Grails错误

c++ - 有状态的元编程是病态的(还)吗?

ruby - 动态创建类方法

javascript - 是否有 Julia 的 JavaScript 和 TypeScript 元编程和宏的等价物?

scala - 将 JavaScript (d3) 翻译为 Java

android - 刷新项目的 Gradle 任务

bash - curl - 如何在登录后获取 cookie 以发送 curl 命令?

java - 从 Java 调用 Groovy 函数

c++ - 分数元类减法方法不编译

python - 搜索随机 python 程序生成器