loading...

Today, I learned that Tuple != Tuple2

nancyd profile image Nancy Deschenes ・3 min read

Once upon a time, I wrote a service class that generates pairs of users. I thought to myself, "Ohh! I know this! I need to return a list of tuples!"

List<Tuple<User, User>> getUserMatches() {
  List<Tuple<User, User>> returnVal = []
  while (moreUsers()) {
    User a = getUserA()
    User b = getUserB() 
    returnVal.add(new Tuple[a, b])
  }
}

Test the code. The code runs. All is good. Move on.

Later, a co-worker asks, "why aren't the tests running?" What? no, my tests run, I'm sure! On closer inspection, not only do the tests not pass, they fail to compile. I shake my head. I can't imagine... What's worse, IntelliJ shows absolutely no compilation problems. Without a compilation error, it's hard to fix the problem. What I get instead is an exception:

java.lang.reflect.MalformedParameterizedTypeException
    at sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.validateConstructorArguments(ParameterizedTypeImpl.java:60)
    at sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.<init>(ParameterizedTypeImpl.java:53)
    at sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(ParameterizedTypeImpl.java:95)
    at sun.reflect.generics.factory.CoreReflectionFactory.makeParameterizedType(CoreReflectionFactory.java:105)
    at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:140)
[...]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.grails.cli.support.GrailsStarter.rootLoader(GrailsStarter.java:236)
    at org.codehaus.groovy.grails.cli.support.GrailsStarter.main(GrailsStarter.java:264)

Some googling leads me this blog post. Ah, okay, I need to find out where I managed to parametrize a class incorrectly.

The exception only occurs when compiling the tests, so that where I look. I try to get the system to only compile some of the tests, to figure out which is causing the problem. Finally find that the test for my service above is responsible. I still have no idea what's wrong with it, mind you. All I know is that when I try to compile it, it fails with the dreaded java.lang.reflect.MalformedParameterizedTypeException.

I comment out half the file. The problem still happens. I comment out another quarter. Another eighth. Until all I have left is

@TestFor(UserMergeService) 
class UserMergeServiceSpec extends Specification {
}

And it still crashes the compiler. I comment out the annotation, and it compiles! I put the whole file back in, and as long as the annotation is commented out, it works.

It was positively mystifying.

I tried to approach it from the other end. I set a breakpoint where the MalformedParameterizedTypeException was thrown, and walked up the stack, and looked at local values, and... there's my Tuple. I look at the definition:

public class Tuple extends AbstractList {
  // ...
}

Hm. That's not parametrized. How do you parametrize a Tuple, then?

That's when I found this blog post that cleared everything up.

Tuple is an immutable list of items, and is not parametrizable. Tuple2 is a pair of two things, and the class of those two things can be parametrized.

I don't know why this was a problem only when running the tests; the code compiles and runs just fine on its own, and the "bad code" is in the Service class, not in the test, but the test must have tickled something... At least it's fixed!

Discussion

markdown guide
 

Nice post, thanks!

Glad to see some Grails, Groovy and Spock around here 🖖🏼