Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy casting collection unasked for it

I have some code written in Java that uses Generics. This is a simple version:

// In Java
public interface Testable {
    void test();
}

public class TestableImpl implements Testable {
    @Override
    public void test(){
        System.out.println("hello");
    }
}

public class Test {
    public <T extends Testable> void runTest(Collection<T> ts){
        System.out.println("Collection<T>");
        for(T t: ts)
            t.test();
    }

    public void runTest(Object o){
        System.out.println("Object");
        System.out.println(o);
    }
}


// in Groovy - this is how I have to use the code
Test test = new Test()
test.runTest([new TestableImpl(), new TestableImpl()]) 
test.runTest([1,2,3]) //exception here

I am suprised that the second method call is dispatched to the wrong method (wrong in my Javish understanding). Instead calling the Object overload, the Collection gets called.

I am using Groovy 2.1.9, Windows 7.

And the exception is:

Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: 
   Cannot cast object '1' with class 'java.lang.Integer' to class 'Testable'
org.codehaus.groovy.runtime.typehandling.GroovyCastException:
   Cannot cast object '1' with class 'java.lang.Integer' to class 'Testable'

Why? How to solve this?

How to make Groovy call the same method as Java would?


edit: to further explain the case, I'd like to write a Spock test for it (just imagine the method returns something, say a String..):

def "good dispatch"(in,out) {
    expect:
    test.runTest(in) == out

    where:
    in                   | out
    new Object()         | "a value for Object"
    new Integer(123)     | "a value for Object"
    [1,2,3]              | "a value for Object"
    [new TestableImpl()] | "a value for Testable Collection"

}
like image 829
Parobay Avatar asked Jan 09 '14 12:01

Parobay


1 Answers

Others have suggested possible ways to solve your problem but here is WHY it happens.

Groovy - being a dynamic language - uses the runtime type information to invoke the correct method. Java, on the other hand, determines which method will be used based on the static type.

A simple example that demonstrates the differences between JAVA and GROOVY:

void foo(Collection coll) {
    System.out.println("coll")   
}

void foo(Object obj) {
    System.out.println("obj")   
}

In GROOVY:

Object x = [1,2,3] //dynamic type at invocation time will be ArrayList
foo(x)
//OUT: coll

In JAVA:

Object x = Arrays.asList(1,2,3);
foo(x);
//OUT: obj
Collection x = Arrays.asList(1,2,3);
foo(x);
//OUT: coll

Now in your example (it does not really have anything to do with the use of generics):

test.runTest([new TestableImpl(), ...]) //ArrayList --> collection method will be used
test.runTest([1,2,3]) //also ArrayList --> best match is the collection variant
like image 137
kmera Avatar answered Oct 11 '22 21:10

kmera