在 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")
}
此行为是正确的,因为 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 = ...
中提供。我希望它能让您更好地了解引擎盖下发生的事情。
我将覆盖的方法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")
}
此行为是正确的,因为 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 = ...
中提供。我希望它能让您更好地了解引擎盖下发生的事情。