我正在Groovy中尝试做的简化版本:
class Animal {
static def echo() {
println this.name // ie "class.name"
}
}
class Dog extends Animal {
}
class Cat extends Animal {
}
Dog.echo()
Cat.echo()
// Output:
// => Animal
// => Animal
//
// What I want:
// => Dog
// => Cat
我想我想问的是:当我在对象上调用静态方法时, 静态方法是在对象的超类中定义的,有没有一种方法可以获取 对象的实际类型?
答案 0 :(得分:3)
静态方法不是在对象上下文中定义的,而是在类上下文中定义的。您可能会对Groovy静态方法中this
的存在感到困惑。但是,它只是语法糖,最终将this.name
替换为Animal.class.name
。
如果在启用静态编译的情况下从示例中编译Animal
类,您将看到它编译为以下Java等效文件(反编译 .class 文件后的结果):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
public class Animal implements GroovyObject {
public Animal() {
MetaClass var1 = this.$getStaticMetaClass();
this.metaClass = var1;
}
public static Object echo() {
DefaultGroovyMethods.println(Animal.class, Animal.class.getName());
return null;
}
}
您可以在echo
方法中看到以下行:
DefaultGroovyMethods.println(Animal.class, Animal.class.getName());
直接对Animal
类名进行操作。因此,从echo
方法的角度来看,扩展多少个类无关紧要。只要这些类调用echo
类中定义的Animal
方法,您始终会看到Animal
作为结果打印出来。
还有更多。如果使用以下编译器配置脚本:
config.groovy
withConfig(configuration) {
ast(groovy.transform.CompileStatic)
ast(groovy.transform.TypeChecked)
}
,然后使用此配置选项通过以下命令编译脚本(将其称为 script.groovy ):
groovyc --configscript=config.groovy script.groovy
然后反编译 .class 文件后,您会看到类似以下内容:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.Binding;
import org.codehaus.groovy.runtime.InvokerHelper;
public class script extends groovy.lang.Script {
public script() {
}
public script(Binding context) {
super(context);
}
public static void main(String... args) {
InvokerHelper.runScript(script.class, args);
}
public Object run() {
Animal.echo();
return Animal.echo();
}
}
您可以看到,即使您已经在Groovy脚本中调用了Dog.echo()
和Cat.echo()
,编译器还是用两次Animal.echo()
调用替换了这些调用。发生这种情况是因为在其他任何子类上调用此静态方法都没有任何区别。
有一种获取预期输出的方法-在echo
和Dog
类中重写Cat
静态方法。我可以假设您的真实方法可能比上面显示的示例echo
方法做更多的事情,因此您可能需要从父类调用超级echo
方法。但是...存在两个问题:(1)您不能在静态上下文中使用super.echo()
,并且(2)它不能解决问题,因为父方法仍在{{1}中运行}类上下文。'
要解决此类问题,您可能需要模仿一种称为double dispatch的技术。简而言之-当我们在被调用的方法中没有关于调用方的信息时,让我们允许调用方通过方法调用传递此信息。考虑以下示例:
Animal
这听起来像样板解决方案-它要求在每个子类中实现import groovy.transform.CompileStatic
@CompileStatic
class Animal {
// This is a replacement for the previous echo() method - this one knows the animal type from a parameter
protected static void echo(Class<? extends Animal> clazz) {
println clazz.name
}
static void echo() {
echo(Animal)
}
}
@CompileStatic
class Dog extends Animal {
static void echo() {
echo(Dog)
}
}
@CompileStatic
class Cat extends Animal {
static void echo() {
echo(Cat)
}
}
Animal.echo()
Dog.echo()
Cat.echo()
方法。但是,它在需要echo
参数的方法中封装了echo
逻辑,因此我们可以让每个子类介绍其具体子类型。当然,这不是一个完美的解决方案。它要求在每个子类中实现Class<? extends Animal>
方法,但是没有其他替代方法。另一个问题是,这不会阻止您调用echo
,这将导致与调用Dog.echo(Animal)
相同的效果。这种类似于双重分派的方法更像是介绍Animal.echo()
方法的简写,该版本使用通用的静态echo
方法实现为简单起见。
我不知道这种方法是否可以解决您的问题,但是也许它将帮助您找到最终的解决方案。