我将重写的方法toString
注入Object.metaClass
:
Object.metaClass.toString ={
System.out.println("the string is $delegate")
}
我认为以下代码将执行此方法:
1500.toString()
但它没有,没有任何东西被打印到控制台。这让我感到困惑:如果事情变坏了,那么错误就是抛弃;如果找到并调用Object.metaClass.toString
,那么该消息将会出现,但为什么它不起作用?里面发生了什么?
答案 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()
方法。
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));
当我们尝试在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
现在让我们看一下调用脚本时收到的参数:
在我们的案例中最有趣的是methodOrList.data
的第一个元素。这是一个方法对象:
public java.lang.String java.lang.Integer.toString()
这是toString()
类从其父类重写的方法Integer
。 Groovy运行时选择此方法,因为从运行时的角度来看它是最准确的 - 它是Integer
类提供的最具体的方法。如果在类级别没有覆盖toString()
方法(例如我之前提到的Runtime
类示例),那么调用toString()
方法的最佳候选者是我们提供的ClosureMetaMethod
在Object.metaClass.toString = ...
。我希望它能让你更好地了解幕后发生的事情。
答案 1 :(得分:1)
我认为你不能以这种方式覆盖Object.toString()。
但这有效:
Integer.metaClass.toString = { ->
System.out.println("the string is $delegate")
}