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.