这种行为是正确的,因为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 类的示例。
正如您所见,从Object 级别覆盖toString() 方法对于基于Object.toString() 实现的类是有意义的。提供自己的toString() 实现的类不会使用您动态修改的方法。它还解释了为什么以下代码有效:
Object.metaClass.printMessage = {
System.out.println("Hello!")
}
1500.printMessage()
在这个例子中,我们将一个名为printMessage() 的新方法添加到Object 类中,所有不覆盖此方法的类都将使用我们刚刚创建的这个动态方法。 Integer 类没有类似的方法,所以会打印出来:
Hello!
正如预期的那样。
还请记住,toString() 应该返回一个 String,并且最好不要在此方法中打印任何要输出的内容 - 您最终可能会因循环调用 toString() 方法而导致讨厌的 StackOverflowError。
更新: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)。如果你在这个方法的开头放一个断点,然后用调试器运行一个脚本,你会看到这个方法是用以下参数执行的:
在第 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;
}
来源: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 类从其父类覆盖的方法toString()。 Groovy 运行时选择了这个方法,因为从运行时的角度来看它是最准确的——它是为Integer 类提供的最具体的方法。如果在类级别没有覆盖toString() 方法(例如我之前提到的Runtime 类示例),那么调用toString() 方法的最佳候选者是我们在Object.metaClass.toString = ... 中提供的ClosureMetaMethod。我希望它能让您更好地了解幕后发生的事情。