Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Sign Up
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<p>A map is a mapping from unique unordered keys to values:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def map= ['id':'FX-11', 'name':'Radish', 'no':1234, 99:'Y'] //keys can be of any type, and mixed together; so can values assert map == ['name':'Radish', 'id':'FX-11', 99:'Y', 'no':1234] //order of keys irrelevant assert map.size() == 4 assert [1:'a', 2:'b', 1:'c' ] == [1:'c', 2:'b'] //keys unique def map2= [ 'id': 'FX-17', name: 'Turnip', //string-keys that are valid identifiers need not be quoted 99: 123, //any data can be a key (-97): 987, //keys with complex syntax must be parenthesized "tail's": true, //trailing comma OK ] assert map2.id == 'FX-17' //we can use field syntax for keys that are valid identifiers assert map2['id'] == 'FX-17' //we can always use subscript syntax assert map2.getAt('id') == 'FX-17' //some alternative method names assert map2.get('id') == 'FX-17' assert map2['address'] == null //if key doesn't exist in map assert map2.get('address', 'No fixed abode') == 'No fixed abode' //default value for non-existent keys assert map2.class == null //field syntax always refers to value of key, even if it doesn't exist //use getClass() instead of class for maps... assert map2.getClass() == LinkedHashMap //the kind of Map being used assert map2."tail's" == true //string-keys that aren't valid identifiers used as field by quoting them assert ! map2.'99' && ! map2.'-97' //doesn't work for numbers, though map2.name = 'Potato' map2[-107] = 'washed, but not peeled' map2.putAt('alias', 'Spud') //different alternative method names when assigning value map2.put('address', 'underground') assert map2.name == 'Potato' && map2[-107] == 'washed, but not peeled' && map2.alias == 'Spud' && map2.address == 'underground' assert map2 == [ id: 'FX-17', name: 'Potato', alias: 'Spud', address: 'underground', 99: 123, (-97): 987, (-107): 'washed, but not peeled', "tail's": true ] def id= 'address' def map3= [id: 11, (id): 22] //if we want a variable's value to become the key, we parenthesize it assert map3 == [id: 11, address: 22] </pre></td></tr></table><p>It's a common idiom to construct an empty map and assign values:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def map4= [:] map4[ 1 ]= 'a' map4[ 2 ]= 'b' map4[ true ]= 'p' //we can use boolean values as a key map4[ false ]= 'q' map4[ null ]= 'x' //we can also use null as a key map4[ 'null' ]= 'z' assert map4 == [1:'a', 2:'b', (true):'p', (false):'q', (null):'x', 'null':'z' ] </pre></td></tr></table><p>To use the value of a String as the key value of a map, simply surround the variable with parenthesis. </p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def foo = "test" def map = [(foo):"bar"] println map // will output ["test":"bar"] map = [foo:"bar"] println map // will output ["foo":"bar"] </pre></td></tr></table><p> </p><p>We can use each() and eachWithIndex() to access keys and values:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def p= new StringBuffer() [1:'a', 2:'b', 3:'c'].each{ p << it.key +': '+ it.value +'; ' } //we supply a closure with either 1 param... assert p.toString() == '1: a; 2: b; 3: c; ' def q= new StringBuffer() [1:'a', 2:'b', 3:'c'].each{ k, v-> q << k +': '+ v +'; ' } //...or 2 params assert q.toString() == '1: a; 2: b; 3: c; ' def r= new StringBuffer() [1:'a', 2:'b', 3:'c'].eachWithIndex{ it, i-> //eachIndex() always takes 2 params r << it.key +'('+ i +'): '+ it.value +'; ' } assert r.toString() == '1(0): a; 2(1): b; 3(2): c; ' </pre></td></tr></table><p>We can check the contents of a map with various methods:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>assert [:].isEmpty() assert ! [1:'a', 2:'b'].isEmpty() assert [1:'a', 2:'b'].containsKey(2) assert ! [1:'a', 2:'b'].containsKey(4) assert [1:'a', 2:'b'].containsValue('b') assert ! [1:'a', 2:'b'].containsValue('z') </pre></td></tr></table><p>We can clear a map:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def m= [1:'a', 2:'b'] m.clear() assert m == [:] </pre></td></tr></table><p>Further map methods:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def defaults= [1:'a', 2:'b', 3:'c', 4:'d'], overrides= [2:'z', 5:'x', 13:'x'] def result= new HashMap(defaults) result.putAll(overrides) assert result == [1:'a', 2:'z', 3:'c', 4:'d', 5:'x', 13:'x'] result.remove(2) assert result == [1:'a', 3:'c', 4:'d', 5:'x', 13:'x'] result.remove(2) assert result == [1:'a', 3:'c', 4:'d', 5:'x', 13:'x'] </pre></td></tr></table><p>Great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that a map should not contain itself as a key.</p><h3>Collection views of a map</h3><p>We can inspect the keys, values, and entries in a view:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def m2= [1:'a', 2:'b', 3:'c'] def es=m2.entrySet() es.each{ assert it.key in [1,2,3] assert it.value in ['a','b','c'] it.value *= 3 //change value in entry set... } assert m2 == [1:'aaa', 2:'bbb', 3:'ccc'] //...and backing map IS updated def ks= m2.keySet() assert ks == [1,2,3] as Set ks.each{ it *= 2 } //change key... assert m2 == [1:'aaa', 2:'bbb', 3:'ccc'] //...but backing map NOT updated ks.remove( 2 ) //remove key... assert m2 == [1:'aaa', 3:'ccc'] //...and backing map IS updated def vals= m2.values() assert vals.toList() == ['aaa', 'ccc'] vals.each{ it = it+'z' } //change value... assert m2 == [1:'aaa', 3:'ccc'] //...but backing map NOT updated vals.remove( 'aaa' ) //remove value... assert m2 == [3:'ccc'] //...and backing map IS updated vals.clear() //clear values... assert m2 == [:] //...and backing map IS updated assert es.is( m2.entrySet() ) //same instance always returned assert ks.is( m2.keySet() ) assert vals.is( m2.values() ) </pre></td></tr></table><p>We can use these views for various checks:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def m1= [1:'a', 3:'c', 5:'e'], m2= [1:'a', 5:'e'] assert m1.entrySet().containsAll(m2.entrySet()) //true if m1 contains all of m2's mappings def m3= [1:'g', 5:'z', 3:'x'] m1.keySet().equals(m3.keySet()) //true if maps contain mappings for same keys </pre></td></tr></table><p>These views also support the removeAll() and retainAll() operations:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def m= [1:'a', 2:'b', 3:'c', 4:'d', 5:'e'] m.keySet().retainAll( [2,3,4] as Set ) assert m == [2:'b', 3:'c', 4:'d'] m.values().removeAll( ['c','d','e'] as Set ) assert m == [2:'b'] </pre></td></tr></table><p>Some more map operations:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def m= [1:'a', 2:'b', 3:'c', 4:'d', 5:'e'] assert [86: m, 99: 'end'].clone()[86].is( m ) //clone() makes a shallow copy def c= [] def d= ['a', 'bb', 'ccc', 'dddd', 'eeeee'] assert m.collect{ it.value * it.key } == d assert m.collect(c){ it.value * it.key } == d assert c == d assert m.findAll{ it.key == 2 || it.value == 'e' } == [2:'b', 5:'e'] def me= m.find{ it.key % 2 == 0 } assert [me.key, me.value] in [ [2,'b'], [4,'d'] ] assert m.toMapString() == '[1:"a", 2:"b", 3:"c", 4:"d", 5:"e"]' def sm= m.subMap( [2,3,4] ) sm[3]= 'z' assert sm == [2:'b', 3:'z', 4:'d'] assert m == [1:'a', 2:'b', 3:'c', 4:'d', 5:'e'] //backing map is not modified assert m.every{ it.value.size() == 1 } assert m.any{ it.key % 4 == 0 } </pre></td></tr></table><p>Getting Map key(s) from a value.</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def family = [dad:"John" , mom:"Jane", son:"John"] def val = "John" </pre></td></tr></table><p>The simplest way to achieve this with the previous map:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>assert family.find{it.value == "John"}?.key == "dad" //or assert family.find{it.value == val}?.key == "dad" </pre></td></tr></table><p>Note that the return is only the key <em>dad</em>. As you can see from the <em>family</em> <em>Map</em> both <em>dad</em> and <em>son</em> are keys for the same values.</p><p>So, let's get all of the keys with the value "John"<br /> Basically, findAll returns a collection of Mappings with the value "John" that we then iterate through and print the key <em>if</em> the key is groovy true.</p><p>This will place your results for the keys into a <em>List</em> of keys</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def retVal = [] family.findAll{it.value == val}.each{retVal << it?.key} assert retVal == ["son", "dad"] </pre></td></tr></table><p>If you just wanted the collection of Mappings:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>assert family.findAll{it.value == val} == ["son":"John", "dad":"John"] //or def returnValue = family.findAll{it.value == val} assert returnValue == ["son":"John", "dad":"John"] </pre></td></tr></table><h3>Special Notations</h3><p>We can use special notations to access all of a certain key in a list of similarly-keyed maps:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def x = [ ['a':11, 'b':12], ['a':21, 'b':22] ] assert x.a == [11, 21] //GPath notation assert x*.a == [11, 21] //spread dot notation x = [ ['a':11, 'b':12], ['a':21, 'b':22], null ] assert x*.a == [11, 21, null] //caters for null values assert x*.a == x.collect{ it?.a } //equivalent notation try{ x.a; assert 0 }catch(e){ assert e instanceof NullPointerException } //GPath doesn't cater for null values class MyClass{ def getA(){ 'abc' } } x = [ ['a':21, 'b':22], null, new MyClass() ] assert x*.a == [21, null, 'abc'] //properties treated like map subscripting def c1= new MyClass(), c2= new MyClass() assert [c1, c2]*.getA() == [c1.getA(), c2.getA()] //spread dot also works for method calls assert [c1, c2]*.getA() == ['abc', 'abc'] assert ['z':900, *:['a':100, 'b':200], 'a':300] == ['a':300, 'b':200, 'z':900] //spread map notation in map definition assert [ *:[3:3, *:[5:5] ], 7:7] == [3:3, 5:5, 7:7] def f(){ [ 1:'u', 2:'v', 3:'w' ] } assert [*:f(), 10:'zz'] == [1:'u', 10:'zz', 2:'v', 3:'w'] //spread map notation in function arguments def f(m){ m.c } assert f(*:['a':10, 'b':20, 'c':30], 'e':50) == 30 def f(m, i, j, k){ [m, i, j, k] } //using spread map notation with mixed unnamed and named arguments assert f('e':100, *[4, 5], *:['a':10, 'b':20, 'c':30], 6) == [ ["e":100, "b":20, "c":30, "a":10], 4, 5, 6 ] </pre></td></tr></table><h3>Grouping</h3><p>We can group a list into a map using some criteria:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>assert [ 'a', 7, 'b', [2,3] ].groupBy{ it.class } == [ (String.class): ['a', 'b'], (Integer.class): [ 7 ], (ArrayList.class): [[2,3]] ] assert [ [name:'Clark', city:'London'], [name:'Sharma', city:'London'], [name:'Maradona', city:'LA'], [name:'Zhang', city:'HK'], [name:'Ali', city: 'HK'], [name:'Liu', city:'HK'], ].groupBy{ it.city } == [ London: [ [name:'Clark', city:'London'], [name:'Sharma', city:'London'] ], LA: [ [name:'Maradona', city:'LA'] ], HK: [ [name:'Zhang', city:'HK'], [name:'Ali', city: 'HK'], [name:'Liu', city:'HK'] ], ] </pre></td></tr></table><p>By using groupBy() and findAll() on a list of similarly-keyed maps, we can emulate SQL:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>assert ('The quick brown fox jumps over the lazy dog'.toList()*. toLowerCase() - ' '). findAll{ it in 'aeiou'.toList() }. //emulate SQL's WHERE clause with findAll() method groupBy{ it }. //emulate GROUP BY clause with groupBy() method findAll{ it.value.size() > 1 }. //emulate HAVING clause with findAll() method after the groupBy() one entrySet().sort{ it.key }.reverse(). //emulate ORDER BY clause with sort() and reverse() methods collect{ "$it.key:${it.value.size()}" }.join(', ') == 'u:2, o:4, e:3' </pre></td></tr></table><p>An example with more than one "table" of data:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>//find all letters in the "lazy dog" sentence appearing more often than those //in the "liquor jugs" one... def dogLetters= ('The quick brown fox jumps over the lazy dog'.toList()*. toLowerCase() - ' '), jugLetters= ('Pack my box with five dozen liquor jugs'.toList()*. toLowerCase() - ' ') assert dogLetters.groupBy{ it }. findAll{ it.value.size() > jugLetters.groupBy{ it }[ it.key ].size() }. entrySet().sort{it.key}.collect{ "$it.key:${it.value.size()}" }.join(', ') == 'e:3, h:2, o:4, r:2, t:2' </pre></td></tr></table><h3>HashMap Internals</h3><p>A HashMap is constructed in various ways:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def map1= new HashMap() //uses initial capacity of 16 and load factor of 0.75 def map2= new HashMap(25) //uses load factor of 0.75 def map3= new HashMap(25, 0.8f) def map4= [:] //the shortcut syntax </pre></td></tr></table><p>The capacity is the number of buckets in the HashMap, and the initial capacity is the capacity when it's created. The load factor measures how full the HashMap will get before its capacity is automatically increased. When the number of entries exceeds the product of the load factor and the current capacity, the HashMap is rehashed so it has about twice the number of buckets. A HashMap gives constant-time performance for lookup (getting and putting). Iterating over collection views gives time performance proportional to the capacity of the HashMap instance plus its the number of keys. So don't set the initial capacity too high or the load factor too low. As a general rule, the default load factor (0.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost. Creating a HashMap with a sufficiently large capacity will allow mappings to be stored more efficiently than letting it perform automatic rehashing as needed to grow the table.</p><p>A HashSet is implemented with a HashMap, and is constructed with the same choices of parameters:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def set1= new HashSet() //uses initial capacity of 16 and load factor of 0.75 def set2= new HashSet(25) //uses load factor of 0.75 def set3= new HashSet(25, 0.8f) def set4= Collections.newSetFromMap( [:] ) //we can supply our own empty map for the implementation </pre></td></tr></table><h3>Sorted Maps</h3><p>A sorted map is one with extra methods that utilize the sorting of the keys. Some constructors and methods:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def map= [3:'c', 2:'d' ,1:'e', 5:'a', 4:'b'], tm= new TreeMap(map) assert tm.firstKey() == map.keySet().min() && tm.firstKey() == 1 assert tm.lastKey() == map.keySet().max() && tm.lastKey() == 5 assert tm.findIndexOf{ it.key==4 } == 3 </pre></td></tr></table><p>We can construct a TreeMap by giving a comparator to order the elements in the map:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def c= [ compare: {a,b-> a.equals(b)? 0: Math.abs(a)<Math.abs(b)? -1: 1 } ] as Comparator def tm= new TreeMap( c ) tm[3]= 'a'; tm[-7]= 'b'; tm[9]= 'c'; tm[-2]= 'd'; tm[-4]= 'e' assert tm == new TreeMap( [(-2):'d', 3:'a', (-4):'e', (-7):'b', 9:'c'] ) assert tm.comparator() == c //retrieve the comparator def tm2= new TreeMap( tm ) //use same map entries and comparator assert tm2.comparator() == c def tm3= new TreeMap( tm as HashMap ) //special syntax to use same map entries but default comparator only assert tm3.comparator() == null </pre></td></tr></table><p>The range-views, headMap() tailMap() and subMap(), are useful views of the items in a sorted map. They act similarly to the corresponding range-views in a sorted set.</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def sm= new TreeMap(['a':1, 'b':2, 'c':3, 'd':4, 'e':5]) def hm= sm.headMap('c') assert hm == new TreeMap(['a':1, 'b':2]) //headMap() returns all elements with key < specified key hm.remove('a') assert sm == new TreeMap(['b':2, 'c':3, 'd':4, 'e':5]) //headmap is simply a view of the data in sm sm['a']= 1; sm['f']= 6 assert sm == new TreeMap(['a':1, 'b':2, 'c':3, 'd':4, 'e':5, 'f':6]) //if backing sorted map changes, so do range-views def tm= sm.tailMap('c') assert tm == new TreeMap(['c':3, 'd':4, 'e':5, 'f':6]) //tailMap() returns all elements with key >= specified element def bm= sm.subMap('b','e') assert bm == new TreeMap(['b':2, 'c':3, 'd':4]) //subMap() returns all elements with key >= but < specified element try{ bm['z']= 26; assert 0 } catch(e){ assert e instanceof IllegalArgumentException } //attempt to insert an element out of range </pre></td></tr></table><h3>Immutable Maps</h3><p>We can convert a map into one that can't be modified:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def imMap= (['a':1, 'b':2, 'c':3] as Map).asImmutable() try{ imMap['d']= 4; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } imMap= Collections.unmodifiableMap( ['a':1, 'b':2, 'c':3] as Map ) //alternative way try{ imMap['d']= 4; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } def imSortedMap= ( new TreeMap(['a':1, 'b':2, 'c':3]) ).asImmutable() try{ imSortedMap['d']= 4; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } imSortedMap= Collections.unmodifiableSortedMap( new TreeMap(['a':1, 'b':2, 'c':3]) ) //alternative way try{ imSortedMap['d']= 4; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } </pre></td></tr></table><p>We can create an empty map that can't be modified:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def map= Collections.emptyMap() assert map == [:] try{ map['a']= 1; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } map= Collections.EMPTY_MAP assert map == [:] try{ map['a']= 1; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } </pre></td></tr></table><p>We can create a single-element list that can't be modified:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def singMap = Collections.singletonMap('a', 1) assert singMap == ['a': 1] try{ singMap['b']= 2; assert 0 } catch(e){ assert e instanceof UnsupportedOperationException } </pre></td></tr></table><h3>Observable Maps</h3><p>We can convert a map into an observable one with the 'as' keyword too. An observable map will trigger a PropertyChangeEvent every time a value changes:</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>// don't forget the imports import java.beans.* def map = [:] as ObservableMap map.addPropertyChangeListener({ evt -> println "${evt.propertyName}: ${evt.oldValue} -> ${evt.newValue}" } as PropertyChangeListener) map.key = 'value' // prints key: null -> value map.key = 'Groovy' // prints key: value -> Groovy </pre></td></tr></table><p>We can also wrap an existing map with an ObservableMap</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>import java.beans.* def sorted = [a:1,b:2] as TreeMap def map = new ObservableMap(sorted) map.addPropertyChangeListener({ evt -> println "${evt.propertyName}: ${evt.oldValue} -> ${evt.newValue}" } as PropertyChangeListener) map.key = 'value' assert ['a','b','key'] == (sorted.keySet() as List) assert ['a','b','key'] == (map.keySet() as List) </pre></td></tr></table><p>Lastly we can specify a closure as an additional parameter, it will work like a filter for properties that should or should not trigger a PropertyChangeEvent when their values change, this is useful in conjunction with Expando. The filtering closure may take 2 parameters (the property name and its value) or less (the value of the property).</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>import java.beans.* def map = new ObservableMap({!(it instanceof Closure)}) map.addPropertyChangeListener({ evt -> println "${evt.propertyName}: ${evt.oldValue} -> ${evt.newValue}" } as PropertyChangeListener) def bean = new Expando( map ) bean.lang = 'Groovy' // prints lang: null -> Groovy bean.sayHello = { name -> "Hello ${name}" } // prints nothing, event is skipped assert 'Groovy' == bean.lang assert 'Hello Groovy' == bean.sayHello(bean.lang) </pre></td></tr></table><h3>Infinite nested maps tip</h3><p>With the help of <a href="http://groovy.codehaus.org/groovy-jdk/java/util/Map.html#withDefault%28groovy.lang.Closure%29">withDefault</a> we can easily create inifinite nested maps structure :</p><table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="language=groovy" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6bGFuZ3VhZ2U9Z3Jvb3Z5fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>Map infiniteNestedMaps = { [:].withDefault{ owner.call() } }() infiniteNestedMaps.a.b.c.d.e.f.g = 42 assert infiniteNestedMaps.a.b.c.d.e.f.g == 42 </pre></td></tr></table>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced