如何在Groovy中从静态超类方法引用子类

时间:2019-03-03 20:48:59

标签: groovy

我正在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

我想我想问的是:当我在对象上调用静态方法时, 静态方法是在对象的超类中定义的,有没有一种方法可以获取 对象的实际类型?

1 个答案:

答案 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()调用替换了这些调用。发生这种情况是因为在其他任何子类上调用此静态方法都没有任何区别。

可能的解决方案:应用双重调度

有一种获取预期输出的方法-在echoDog类中重写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方法实现为简单起见。

我不知道这种方法是否可以解决您的问题,但是也许它将帮助您找到最终的解决方案。