Reputation: 11557
A simplified version of what I'm trying to do in 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
I think what I'm asking here is: when I call a static method on an object, and the static method is defined in the object's superclass, is there a way to obtain the actual type of the object?
Upvotes: 2
Views: 1139
Reputation: 42262
A static method is not defined in the object context, but in the class context. You might get confused by the presence of this
in the Groovy static method. However, it's only a syntactic sugar that eventually replaces this.name
with Animal.class.name
.
If you compile the Animal
class from your example with a static compilation enabled, you will see that it compiles to the following Java equivalent (result after decompiling the .class file):
//
// 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;
}
}
You can see that the following line in the echo
method:
DefaultGroovyMethods.println(Animal.class, Animal.class.getName());
operates directly on the Animal
class name. So from the echo
method perspective, it doesn't matter how many classes extend it. As long as those classes invoke echo
method defined in the Animal
class, you will always see Animal
printed as a result.
And there is even more than that. If you use the following compiler configuration script:
config.groovy
withConfig(configuration) {
ast(groovy.transform.CompileStatic)
ast(groovy.transform.TypeChecked)
}
and then compile the script (let's call it script.groovy) using this configuration option with the following command:
groovyc --configscript=config.groovy script.groovy
then you will see something like this after decompiling the .class file:
//
// 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 can see that even though you have invoked Dog.echo()
and Cat.echo()
in your Groovy script, the compiler replaced these calls with the double Animal.echo()
invocation. It happened because calling this static method on any other subclass does not make any difference.
There is one way to get the expected output - override echo
static method in Dog
and Cat
class. I can assume that your real method may do something more than the exemplary echo
method you have shown above, so you might need to call the super echo
method from a parent class. But... there are two problems: (1) you can't use super.echo()
in the static context, and (2) it doesn't solve the problem, because parent method still operates in the Animal
class context.'
To solve this kind of issue you might want to mimic a technique called double dispatch. In short - when we don't have information about the caller in the method that was called, let's allow the caller to pass this information with the method call. Consider the following example:
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()
This may sound like a boilerplate solution - it requires implementing echo
method in each subclass. However, it encapsulates the echo
logic in the method that requires Class<? extends Animal>
parameter, so we can let every subclass to introduce their concrete subtype. Of course, this is not a perfect solution. It requires implementing echo
method in each subclass, but there is no other alternative way. Another problem is that it doesn't stop you from calling Dog.echo(Animal)
which will cause the same effect as calling Animal.echo()
. This double dispatch like approach is more like introducing a shorthand version of echo
method which uses the common static echo
method implementation for simplicity.
I don't know if this kind of approach solves your problem, but maybe it will help you find a final solution.
Upvotes: 3