RonPringadi
RonPringadi

Reputation: 1494

How to override Groovy variable and method using anonymous class?

I have the following code. I have an abstract JobParams, a class extending that abstract GradleJobParams, and a gjp variable with value using anonymous class declaration. I want to test the overriding behavior of groovy. I can override the method setupRoot() but not the property testVar, why is that?

Tested on: https://groovyconsole.appspot.com/script/5146436232544256

abstract class JobParams {
    int root
    def testVar=1
    def setupRoot () {
        println("The root");
    }
    def printTestVar () {
        println("The testVar:" + testVar);
    }
}

class GradleJobParams extends JobParams {
}

def gjp = [
    testVar:3,
    setupRoot:{
        println("Override root");
    }
] as GradleJobParams;

println("Starting");
gjp.printTestVar();
gjp.setupRoot();

The result is:

Starting
The testVar:1
Override root

Upvotes: 1

Views: 2138

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42184

Java (and thus Groovy) does not support overriding fields from the parent class with subclassing. Instead, it uses a mechanism called hiding fields:

Hiding Fields

Within a class, a field that has the same name as a field in the superclass hides the superclass's field, even if their types are different. Within the subclass, the field in the superclass cannot be referenced by its simple name. Instead, the field must be accessed through super, which is covered in the next section. Generally speaking, we don't recommend hiding fields as it makes code difficult to read.


Source: https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html

It can be simply illustrated with the following example in Java:

final class SubclassHiddingFieldExample {

    static abstract class A {
        int value = 10;

        void printValue1() {
            System.out.println(value);
        }

        void printValue2() {
            System.out.println(this.value);
        }

        void printValue3() {
            System.out.println(((B)this).value);
        }
    }

    static class B extends A {
        int value = 12;
    }

    public static void main(String[] args) {
        final B b = new B();
        b.printValue1();
        b.printValue2();
        b.printValue3();
    }
}

Output:

10
10
12

As you can see, only printValue3 prints out 3, because it cast this explicitly to B class.

Now, if you look at the decompiled bytecode of your JobParams class, you can see that the printTestVar method code is an equivalent of the following Java code:

//
// 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.callsite.CallSite;

public abstract class JobParams implements GroovyObject {
    private int root;
    private Object testVar;

    public JobParams() {
        CallSite[] var1 = $getCallSiteArray();
        byte var2 = 1;
        this.testVar = Integer.valueOf(var2);
        MetaClass var3 = this.$getStaticMetaClass();
        this.metaClass = var3;
    }

    public Object setupRoot() {
        CallSite[] var1 = $getCallSiteArray();
        return var1[0].callCurrent(this, "The root");
    }

    public Object printTestVar() {
        CallSite[] var1 = $getCallSiteArray();
        return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
    }

    public MetaClass getMetaClass() {
        MetaClass var10000 = this.metaClass;
        if (var10000 != null) {
            return var10000;
        } else {
            this.metaClass = this.$getStaticMetaClass();
            return this.metaClass;
        }
    }

    public void setMetaClass(MetaClass var1) {
        this.metaClass = var1;
    }

    public Object invokeMethod(String var1, Object var2) {
        return this.getMetaClass().invokeMethod(this, var1, var2);
    }

    public Object getProperty(String var1) {
        return this.getMetaClass().getProperty(this, var1);
    }

    public void setProperty(String var1, Object var2) {
        this.getMetaClass().setProperty(this, var1, var2);
    }

    public int getRoot() {
        return this.root;
    }

    public void setRoot(int var1) {
        this.root = var1;
    }

    public Object getTestVar() {
        return this.testVar;
    }

    public void setTestVar(Object var1) {
        this.testVar = var1;
    }
}

You can see that the line that prints out the value of the testVar field is represented by:

return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));

It means that no matter what value of testVar your subclass defines, the printTestVar method uses testVar field defined in the JobParams class. Period.

Using Groovy auto getter methods

There is one way you to implement the expected behavior. Every class field in Groovy has a getter method associated with that field compiled by Groovy for you. It means that you can access testVar by calling the getTestVar() method generated by the Groovy compiler. You can use it to override the value returned by a getter method for any field from the subclass. Consider the following example:

abstract class JobParams {
    int root
    def testVar=1
    def setupRoot () {
        println("The root");
    }
    def printTestVar () {
        println("The testVar:" + getTestVar()); // <-- using a getTestVar() method instead a testVar field
    }
}

class GradleJobParams extends JobParams {
}

def gjp = [
    getTestVar: 3, // <-- stubbing getTestVar() method to return a different value
    setupRoot:{
        println("Override root");
    }
] as GradleJobParams;

println("Starting");
gjp.printTestVar();
gjp.setupRoot();

Output:

Starting
The testVar:3
Override root

Upvotes: 2

Related Questions