Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating Map using withDefault causing null when putting element

Tags:

groovy

I'm trying to use the Groovy way of creating a TreeMap<String, List<Data>> with default values so I easily add data to a new list if the key isn't already present.

TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>) [:].withDefault { [] }

As you can see, I have the requirement to use a TreeMap and withDefault only returns a Map instance, so I need to cast.

When I attempt to add a new list to the map,

myData[newKey].add(newData)

myData[newKey] is null. However, if I change my Map initilization to remove the TreeMap cast (and change the type to just Map instead of TreeMap), myData[newKey].add(newData) works as expected.

What's the reasoning for this? Can I not use withDefault if I cast the map?

like image 374
Cooper Avatar asked Jul 14 '14 14:07

Cooper


1 Answers

The problem isn't just about the cast. It also has to do with the declared type. The problem can be simplified to something like this:

def map1 = [:].withDefault { 0 }
TreeMap map2 = map1

When that is executed map1 is an instance of groovy.lang.MapWithDefault and map2 is an instance of java.util.TreeMap. They are 2 separate objects on the heap, not just 2 references pointing to the same object. map2 will not have any default behavior associated with it. It is as if you had done this:

def map1 = [:].withDefault { 0 }
TreeMap map2 = new TreeMap(map1)

That is what is happening with your code. The cast and the generics just makes it less clear with your code.

This:

TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>) [:].withDefault { [] }

Can be broken down to this:

def tmpMap = [:].withDefault { [] }
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>)tmpMap

I hope that helps.

EDIT:

Another way to see the same thing happening is to do something like this:

Set names = new HashSet()
ArrayList namesList = names

When the second line executes a new ArrayList is created as if you had done ArrayList namesList = new ArrayList(names). That looks different than what you have in your code, but the same sort of thing is happening. You have a reference with a static type associated with it and are pointing that reference at an object of a different type and Groovy is creating an instance of your declared type. In this simple example above, that declared type is ArrayList. In your example that declared type is TreeMap<String, List<Data>>.

like image 75
Jeff Scott Brown Avatar answered Nov 15 '22 08:11

Jeff Scott Brown