Wednesday, October 06, 2021

I still love Groovy . . .

I was joyfully coding in Groovy for several years. Back to Java two years ago, and have not been writing any production code in Groovy, still writing my own productive non-production utilities in Groovy whenever and wherever I can.

I am trying my best to apply neat things that I learned while working in Groovy projects by using its ecosystem frameworks like Grails framework, Gradle build tool,  Spock framework etc. Within the limitations of Java, Spring Boot, and Maven development world, I am trying hard to write less verbose, and more readable code by leveraging new Java language features including some of each of its version's preview features.

Java is evolving at a steady pace now. Better late than never ;). Still far-away compared to what Groovy was 10+ years ago, or any of current modern languages, in terms of developer's productivity.

I was bit happy to see some convenient factory methods making into Java's collection classes, version after version, since Java 9. Have been happily using one such static factory method .of() on List and Map without caring much of their internal implementations. 

Environment: Java 16, Groovy 3.0.9 on macOS Catalina 10.15.7

Today, I was happily writing code using Map.of() method and kept on adding elements, I had about a dozen of static keys and values to add. IntelliJ was also going happy with me. At some point suddenly IntelliJ turned angry (red) at me. The error was not clear, another Java classic hard-to-understand compilation error. I started to wonder what did I do wrong, was going back and forth on each element I was adding. Quickly realized I was hitting some limitation. Java language team chose the lucky number 10 for these convenient factory methods. There are actually 10 static factory methods named of() that take one to ten arguments.

I fell in love with it, the very first-time I started using it as it's little more concise and readable (not as concise and readable as groovy, but close), but quickly ran into limitations.
  • Map.of() method introduced in Java 9 allows to create an immutable map with up to 10 keys-value pairs.
  • It return an immutable map.
So, use it when you are ok with immutable small Map of up to 10 elements.

The following groovy snippet shows how close (still little more verbose) Java got to Groovy from the painful-finger-typing way to create and initialize a Map with a fixed set of elements. 

// Groovy def groovyMap = [ 'a' : [1], 'b' : [1,2], 'c' : [1,2,3], 'd' : [1,2,3,4], 'e' : [1,2,3,4,5], 'f' : [1,2,3,4,5,6], 'g' : [1,2,3,4,5,6,7], 'h' : [1,2,3,4,5,6,7,8], 'i' : [1,2,3,4,5,6,7,8,9], 'j' : [1,2,3,4,5,6,7,8,9,10], 'k' : [1,2,3,4,5,6,7,8,9,10,11], 'l' : [1,2,3,4,5,6,7,8,9,10,11,12], ] println groovyMap // Java var javaMap = Map.of( "a" , List.of(1), "b" , List.of(1,2), "c" , List.of(1,2,3), "d" , List.of(1,2,3,4), "e" , List.of(1,2,3,4,5), "f" , List.of(1,2,3,4,5,6), "g" , List.of(1,2,3,4,5,6,7), "h" , List.of(1,2,3,4,5,6,7,8), "i" , List.of(1,2,3,4,5,6,7,8,9), "j" , List.of(1,2,3,4,5,6,7,8,9,10), "k" , List.of(1,2,3,4,5,6,7,8,9,10,11), "l" , List.of(1,2,3,4,5,6,7,8,9,10,11,12) ) println javaMap

IntelliJ goes unhappy, with error:
Cannot resolve method 'of(java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>, java.lang.String, java.util.List<E>

Java compiler stays unhappy with compilation error:

no suitable method found for of(java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>,java.lang.String,java.util.List<java.lang.Integer>) method java.util.Map.<K,V>of() is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length)) method java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V) is not applicable (cannot infer type-variable(s) K,V (actual and formal argument lists differ in length))

When I executed the above code snippet in groovyconsole Groovy compiler at least gave me little better message pointing at the line (29: "k" , List.of(1,2,3,4,5,6,7,8,9,10,11),) that failed compilation and made me think of exceeding some limitation.

groovy.lang.MissingMethodException: No signature of method: static java.util.Map.of() is applicable for argument types: (String, List12, String, List12, String, ListN, String, ListN, String...) values: [a, [1], b, [1, 2], c, [1, 2, 3], d, [1, 2, 3, 4], e, [1, 2, ...], ...] at ConsoleScript9.run(ConsoleScript9:29)

The workaround, I had to go more verbose; at least, better than old way of painful-finger-typing. ;)
import static java.util.Map.entry; // Groovy def groovyMap = [ 'a' : [1], 'b' : [1,2], 'c' : [1,2,3], 'd' : [1,2,3,4], 'e' : [1,2,3,4,5], 'f' : [1,2,3,4,5,6], 'g' : [1,2,3,4,5,6,7], 'h' : [1,2,3,4,5,6,7,8], 'i' : [1,2,3,4,5,6,7,8,9], 'j' : [1,2,3,4,5,6,7,8,9,10], 'k' : [1,2,3,4,5,6,7,8,9,10,11], 'l' : [1,2,3,4,5,6,7,8,9,10,11,12], ] println groovyMap // Java var javaMap = Map.ofEntries( entry("a" , List.of(1)), entry("b" , List.of(1,2)), entry("c" , List.of(1,2,3)), entry("d" , List.of(1,2,3,4)), entry("e" , List.of(1,2,3,4,5)), entry("f" , List.of(1,2,3,4,5,6)), entry("g" , List.of(1,2,3,4,5,6,7)), entry("h" , List.of(1,2,3,4,5,6,7,8)), entry("i" , List.of(1,2,3,4,5,6,7,8,9)), entry("j" , List.of(1,2,3,4,5,6,7,8,9,10)), entry("k" , List.of(1,2,3,4,5,6,7,8,9,10,11)), entry("l" , List.of(1,2,3,4,5,6,7,8,9,10,11,12)) ) println javaMap

NOTE: It's only the Map.of() that has this limitation, the methods List.of(), Set.of() do not have.

Gotcha

  • The method map.of() returns an immutable map though the method signature says it returns simply a Map.
  • In other words it is an unmodifiable map, keys and values cannot be added, removed or updated.
  • When operation to modify the returned Map like put(), replace(), or remove() are performed, they would result with an UnsupportedOperationException with a null exception exception message ;)

Conclusion

I still love Groovy for its simple, less confusing, yet more expressive syntax.

References


No comments:

Post a Comment