Статический метод определяется не в контексте объекта, а в контексте класса.Вас может смущать присутствие this
в статическом методе Groovy.Тем не менее, это только синтаксический сахар, который в конечном итоге заменяет 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();
}
}
Youвидно, что даже если вы вызвали Dog.echo()
и Cat.echo()
в своем скрипте Groovy, компилятор заменил эти вызовы двойным вызовом Animal.echo()
.Это произошло потому, что вызов этого статического метода для любого другого подкласса не имеет значения.
Возможное решение: применение двойной диспетчеризации
Существует один способ получить ожидаемый результат - переопределить echo
staticметод в Dog
и Cat
классе.Я могу предположить, что ваш реальный метод может сделать что-то большее, чем примерный метод echo
, который вы показали выше, поэтому вам может потребоваться вызвать метод super echo
из родительского класса.Но ... есть две проблемы: (1) вы не можете использовать super.echo()
в статическом контексте, и (2) это не решает проблему, потому что родительский метод все еще работает в контексте класса Animal
.'
Чтобы решить эту проблему, вы можете имитировать технику, называемую double dispatch .Вкратце - когда у нас нет информации о вызывающем объекте в вызываемом методе, давайте разрешим вызывающему передать эту информацию с помощью вызова метода.Рассмотрим следующий пример:
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
, в которой для простоты используется стандартная реализация метода echo
.
Я не знаю, решает ли этот подход вашу проблему,но, возможно, это поможет вам найти окончательное решение.