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

Changing Object.toString() dynamically in Groovy has no effect when calling Integer.toString()

我将覆盖的方法toString注入Object.metaClass

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

我认为下面的代码会执行这个方法:

1500.toString()

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

我认为您不能那样覆盖 Object.toString()。

但这行得通:

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

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

此行为是正确的,因为 java.lang.Integer 用它自己的实现覆盖了 Object.toString()。如果您的假设是正确的,那么这意味着您可以通过强制使用来自父 class 的实现来打破被覆盖的方法。

考虑以下 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() - 我选择了这个 class 作为例子纯 Java class 不会覆盖 toString() 方法。

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

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

1500.printMessage()

在这个例子中,我们将一个名为 printMessage() 的新方法添加到 Object class 并且所有未覆盖此方法的 classes 将使用此动态我们刚刚创建的方法。 Integer class 没有这样的方法,所以它会打印出来:

Hello!

符合预期。

还要记住 toString() 应该 return 一个 String 并且最好不要在这个方法中打印任何输出 - 你可能会得到令人讨厌的 WhosebugError 由循环调用 toString() 方法引起。

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

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

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

1500.toString()

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

在我们的例子中,我们使用普通的 Java class - Integer。在这种情况下 Groovy 将创建 PojoMetaMethodSite class to meta class implementation for Java class - an Integer. Every meta method is executed using one of the Groovy groovy.lang.MetaClass implementation. In this case groovy.lang.MetaClassImpl is being used. One of the last methods that picks a method to execute is MetaClassImpl.getMethodWithCachingInternal(Class sender, CallSite site, Class [] params) 的实例。如果您在此方法的开头放置一个断点,并且 运行 带有调试器的脚本,您将看到此方法使用以下参数执行:

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

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

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

/**
 * 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

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

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

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

这是 Integer class 从其父 class 覆盖的方法 toString()。 Groovy 运行时间选择这个方法,因为从 运行 时间的角度来看它是最准确的 - 它是 Integer class 最具体的方法假如。如果在 class 级别没有覆盖 toString() 方法(例如我之前提到的 Runtime class 示例),那么调用 toString() 方法的最佳候选者是ClosureMetaMethod 由我们在 Object.metaClass.toString = ... 中提供。我希望它能让您更好地了解引擎盖下发生的事情。