4

Let's assume that we have a function in groovy that takes as a parameter BigDecimal:

 void func(BigDecimal bd) {...}

And calling it again in other class on groovy var.func(0)

This is working fine, but in java it will not compile at all. I know that there is a constructor in BigDecimal that will work for Integer, but what's the reason that this works in groovy and java complain about it? Is there something with converting def variables to something known in java?

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
Georgi Stoyanov
  • 469
  • 1
  • 5
  • 24

1 Answers1

2

Groovy uses argument type coercion when you call a method with a value of a different than declared type. In case of BigDecimal argument type coercion Groovy uses BigDecimalCachedClass.coerceArgument(Object argument) method:

public Object coerceArgument(Object argument) {
    if (argument instanceof BigDecimal) {
        return argument;
    } else if (argument instanceof Long) {
        return new BigDecimal((Long) argument);
    } else if (argument instanceof BigInteger) {
        return new BigDecimal((BigInteger) argument);
    }

    if (argument instanceof Number) {
        return new BigDecimal(((Number) argument).doubleValue());
    }
    return argument;
}

When you pass an Integer as a parameter then if (argument instanceof Number) branch is satisfied and Groovy converts input Integer to its BigDecimal representation.

It does not work in Java, because Java is not dynamic language, so all types have to be resolved at compile time. Groovy supports dynamic types, so your final type can be resolved in the runtime.

@CompileStatic and @TypeChecked

Groovy allows you to turn of its dynamic features. If you annotate your class with @CompileStatic or @TypeChecked, then calling var.func(0) won't work anymore, because Groovy will not use its dynamic features anymore.

Consider following simple Groovy class:

class NumTest {

    static void main(String[] args) {
        test(20)
    }

    static void test(BigDecimal number) {
        println number
    }
}

When you compile it with groovyc you will see a Java class like:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import java.math.BigDecimal;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class NumTest implements GroovyObject {
    public NumTest() {
        CallSite[] var1 = $getCallSiteArray();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].callStatic(NumTest.class, Integer.valueOf(20));
    }

    public static void test(BigDecimal number) {
        CallSite[] var1 = $getCallSiteArray();
        var1[1].callStatic(NumTest.class, number);
    }
}

What's interesting is that calling test(20) is not a direct static method call, but this instead:

var1[0].callStatic(NumTest.class, Integer.valueOf(20));

But if we annotate our class with @CompileStatic, it won't compile anymore and we will have to replace test(20) with direct test(BigDecimal.valueOf(20)) call:

import groovy.transform.CompileStatic

@CompileStatic
class NumTest {

    static void main(String[] args) {
        test(BigDecimal.valueOf(20))
    }

    static void test(BigDecimal number) {
        println number
    }
}

Compiling this class with groovyc generates completely different 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 java.math.BigDecimal;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;

public class NumTest implements GroovyObject {
    public NumTest() {
        MetaClass var1 = this.$getStaticMetaClass();
        this.metaClass = var1;
    }

    public static void main(String... args) {
        test(BigDecimal.valueOf((long)20));
        Object var10000 = null;
    }

    public static void test(BigDecimal number) {
        DefaultGroovyMethods.println(NumTest.class, number);
        Object var10000 = null;
    }
}

Here you can see that there is a direct call to test(BigDecimal.valueOf((long)20)); method, so there is no type coercion and you have to pass a valid type in place.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • Thank you for the good explanation :) Do you know where I could check how are achieved some of the "groovy magics" like using `def`, it's something similar I think? – Georgi Stoyanov Jan 17 '18 at 08:23
  • @GeorgiStoyanov There was a question about `def` vs. `Object`, I think you can find some useful information here https://stackoverflow.com/questions/40825109/groovy-def-vs-datatype/40827126 – Szymon Stepniak Jan 17 '18 at 08:31
  • Does anybody know where in the groovy docs this coercion to BigDecimal for a method argument is documented? – eekboom Jan 05 '21 at 11:13
  • The `BigDecimalCachedClass` instance is created in `ClassInfo.java`. Does this mean that it's not possible to use `coerceArgument` for user classes ? – stilgar May 10 '22 at 11:11