Groovy LazyMap - Caching Map

I was working on a Groovy class recently which involved putting a series of closures into a map to do some processing when invoked, but I realized that I didn't really want to run the closures each time they were accessed, once they ran the first time, I would prefer that the data was cached. So the code would look something like this.

Map myProcesses = [coolId : { retriever.somehow() }]
myProcesses.coolId() // <-- the first time this invokes a method call
myProcesses.coolId() // <-- should return value stored from the last call

The use of the closures worked nicely, I just had to figure out how to handle caching. At first I looked into the memoize method on my closures, but there seemed to be some disconnect between how I was trying to use them and what Groovy wanted. When I used coolId() it complained that the method doCall was not available. So, I'm not exactly sure what was happening there.

Map myProcesses = [coolId : { retriever.somehow() }.memoize()]

Then I realized I should be able to do something simpler anyway. Really I wanted a LazyMap, I 
didn't need caching on each individual closure. I had used the LazyMap class provided in the apache commons library to do the following.

return org.apache.commons.collections4.Map.LazyMap.lazyMap(
  new HashMap<String, MyObject>(),
  new org.apache.commons.collections4.Transformer<String, MyObject>() {
    @Override
    public MyObject transform(String input) {
      return retriever.goGetIt(input);
    }
  });

I also remembered coming across some LazyMap references in Groovy, so I did some looking, but the only class I found was in the groovy.json.internal package. Probably not a class I should be using. Was it possible that Groovy didn't have an implementation of a LazyMap? That seemed unlikely to me, almost everything in the commons libraries seem to be built into Groovy. So I started looking at the Groovy docs and ran across the withDefault method that Groovy added to Maps. I realized that an empty map whose default method would invoke my closures and store the results was exactly what I wanted.

Map myInnerProcesses = [coolId : { retriever.somehow() }]
Map myProcesses = [:].withDefault { myInnerProcesses[it] }
myProcesses.coolId() // <-- the first time this invokes a method call
myProcesses.coolId() // <-- should return value stored from the last call

So, by using withDefault I was able to add caching in a single line of code.

Post a Comment