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?
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>>.
Related
Is there a way to "apply" null safety to a multiple assignment from a null? For example, this piece of code would obviously throw:
// verbosity is for clarity
def (a,b) = new HashMap<String, List<String>>().get("nothing")
but it would be neat to have it define a and b with null value.
So far I came up with
def (a,b) = new HashMap<String, List<String>>().get("nothing").with {[it?.get(0), it?.get(1)]}
But that's really ugly...
This is the safe bet, if you have both missing keys and null values:
def (a,b) = new HashMap<String, List<String>>().get("nothing") ?: []
The next one is from Groovy, but modifies the underlying map (so only
works for mutable maps and whether you are fine with modifying it) and
it only gives the default for missing keys:
def (a,b) = new HashMap<String, List<String>>().get("nothing", [])
Similar (also from Groovy and also modifying the underlying map):
withDefault. This option is great if you plan to pass the map around
to other places, that would have to deal with the default all over again
(also only defaults for missing keys):
def (a,b) = new HashMap<String, List<String>>().withDefault{[]}.get("nothing")
And the next one is from Java, but that also only falls back, if the key
is missing:
def (a,b) = new HashMap<String, List<String>>().getOrDefault("nothing",[])
Groovy here. I have a class Fizz:
#Canonical
class Fizz {
Integer id
String name
}
In my program, I compose a map of them by their integral id field:
// Here, each key of the map corresponds to the Fizz#id of its value
// example:
// allFizzes[1] = new Fizz(1, 'Foobar')
// allFizzes[3004] = new Fizz(3004, 'Wakka wakka')
Map<Integer,Fizz> allFizzes = getSomehow()
I would know like to obtain a set of "bad" Fizzes whose name equals the string 'Sampson'. My best attempt:
Set<Fizz> badFizzes = allFizzes.find { k,v -> v.equals('Sampson') }
However this gives me runtime errors:
Exception in thread "main" org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '24={
"id": 24,
"name": "Sampson"
},
' with class 'java.util.LinkedHashMap$Entry' to class 'java.util.Set'
So it looks like even though I'm specifying v.equals('Sampson') that Groovy is strill trying to save a Map key,value pair into a Set. What's the Grooviest solution here?
You'll want to use findAll (which returns a Collection) in place of find (which returns an Object). findAll applies the passed in closure to each EntrySet in the Map and returns a new collection with elements that satisfied your closure's criteria. You can then call values() on this newly returned collection.
Map<String, Integer> myMap = ["fizzStrength":29, "fizzQuality": 123, "fizzFizziness":3]
Set<Integer> found = myMap.findAll { k,v -> v > 4 }.values() // returns [29, 123]
I have two map in the following way
def map1 = ['a':1,'b':2]
def map2 = ['a':345,'c':10,'b':1]
I would like to create a result map which would look basically look to match the keys of the two maps and would make the value of map1 as the key and value of map2 as the value itself. The output would look like this:
map3=[1:345,2:1]
You can do this easily with a simple loop:
map3 = map1.collectEntries { key, val -> [(val): map2[key]] }
Is there a way to instantiate the value of map lazy?
For example
class MapTest {
#Lazy(soft = true) HashMap<String, List<String>> map
}
Doing like this I can use this call and get null without recieving NullPointerException
new MapTest().map.key1
However attempt to call
map.key1.remove(1)
will lead to NullPointerException due the value being null. (it would be fine if it threw IndexOutOfBounds exception)
Is there a way to instantiate the list value of the map?
try map.withDefault :
def map = [:].withDefault { [] }
assert map.key1.isEmpty()
Some explanation :
[:] is the groovy way to instantiate an empty hash map
withDefault is a groovy method on a map wich take a closure. this closure is call every time a key is requested to initialize the value if it doesn't exist. this closure take one parameter (the key) and should the value
[] is the groovy way to create an empty list - { [] } is a closure wich return an empty list for every key
see others examples here
Accessing map inside map with closure,
I have a map object the values is another map object
e.g:-
`
to access the data like this I can issue
def map = [name:"Gromit", likes:"cheese", id:1234]
def map2 =[map1:map]
map2.each{entry ->
println entry.key
entry.value.each {entry1 -> println entry1.key
println entry1.value
}
}
to access a single map i can issue
map.each{entry ->
println entry.key
println entry.value
}
'
How can I write a DSL for the above map example in simple any hint?
Here is an illustration of printing the keys and values of the inner map. Try this:
map1=new HashMap()
map2=new HashMap()
map2.put("1","one")
map1.put("map2",map2)
map1.each{ entry1 ->
def innerMap = entry1.value
innerMap.each { entry2 ->
println "key is ${entry2.key}"
println "value is ${entry2.value}"
}
}
anish, I assume you look for a shorter way to access the map, this would be map2.map1. You can then use map2.map1.name to get "Gromit". If a shorter way to get the map was not your question, then please specify more.