blob: 0153983c06b39b1ee400a405174e0edd2194d1c8 [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Working with collections
:gdk: http://www.groovy-lang.org/gdk.html[Groovy development kit]
:java-util-list: http://docs.oracle.com/javase/8/docs/api/java/util/List.html[java.util.List]
:java-lang-comparable: java.lang.Comparable
Groovy provides native support for various collection types, including <<Collections-Lists,lists>>,
<<Collections-Maps,maps>> or <<Collections-Ranges,ranges>>. Most of those are based on the Java
collection types and decorated with additional methods found in the {gdk}.
[[Collections-Lists]]
== Lists
=== List literals
You can create lists as follows. Notice that `[]` is the empty list
expression.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_literals,indent=0]
-------------------------------------
Each list expression creates an implementation of {java-util-list}.
Of course lists can be used as a source to construct another list:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_construct,indent=0]
----------------------------------------------------------------------------
A list is an ordered collection of objects:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_usecases,indent=0]
----------------------------------------------------------------------------
=== List as a boolean expression
Lists can be evaluated as a `boolean` value:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_to_bool,indent=0]
----------------------------------------------------------------------------
=== Iterating on a list
Iterating on elements of a list is usually done calling the `each` and `eachWithIndex` methods, which execute code on each
item of a list:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_each,indent=0]
----------------------------------------------------------------------------
In addition to iterating, it is often useful to create a new list by transforming each of its elements into
something else. This operation, often called mapping, is done in Groovy thanks to the `collect` method:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_collect,indent=0]
----------------------------------------------------------------------------
=== Manipulating lists
[[List-Filtering]]
==== Filtering and searching
The {gdk} contains a lot of methods on collections that enhance
the standard collections with pragmatic methods, some of which are illustrated here:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk1,indent=0]
----------------------------------------------------------------------------
And here is idiomatic Groovy code for finding the maximum and minimum in a collection:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk2,indent=0]
----------------------------------------------------------------------------
In addition to closures, you can use a `Comparator` to define the comparison criteria:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_comparator,indent=0]
----------------------------------------------------------------------------
==== Adding or removing elements
We can use `[]` to assign a new empty list and `<<` to append items to it:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_leftshift,indent=0]
----------------------------------------------------------------------------
We can add to a list in many ways:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_add,indent=0]
----------------------------------------------------------------------------
It is however important that the `+` operator on a list is *not mutating*. Compared to `<<`, it will create a new
list, which is often not what you want and can lead to performance issues.
The {gdk} also contains methods allowing you to easily remove elements from a list by value:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk3,indent=0]
----------------------------------------------------------------------------
It is also possible to remove an element by passing its index to the `remove` method, in which case the list is mutated:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk_remove_index,indent=0]
----------------------------------------------------------------------------
In case you only want to remove the first element having the same value in a list, instead of removing all
elements, you can call the `remove` method passing the value:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk5,indent=0]
----------------------------------------------------------------------------
As you can see, there are two `remove` methods available. One that takes an integer and removes an element
by its index, and another that will remove the first element that matches the passed value. So what should we
do when we have a list of integers? In this case, you may wish to use `removeAt` to remove an element by its
index, and `removeElement` to remove the first element that matches a value.
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk4,indent=0]
----------------------------------------------------------------------------
Of course, `removeAt` and `removeElement` will work with lists of any type.
Additionally, removing all the elements in a list can be done by calling the `clear` method:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk6,indent=0]
----------------------------------------------------------------------------
==== Set operations
The {gdk} also includes methods making it easy to reason on sets:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_gdk7,indent=0]
----------------------------------------------------------------------------
==== Sorting
Working with collections often implies sorting. Groovy offers a variety of options to sort lists,
from using closures to comparators, as in the following examples:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_sort,indent=0]
----------------------------------------------------------------------------
==== Duplicating elements
The {gdk} also takes advantage of operator overloading to provide methods allowing duplication of elements
of a list:
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=list_multiply,indent=0]
----------------------------------------------------------------------------
[[Collections-Maps]]
== Maps
=== Map literals
In Groovy, maps (also known as associative arrays) can be created using the map literal syntax: `[:]`:
[source,groovy]
--------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_literal,indent=0]
--------------------------------------------------
Map keys are strings by default: `[a:1]` is equivalent to `['a':1]`. This can be confusing if you define a variable
named `a` and that you want the *value* of `a` to be the key in your map. If this is the case, then you *must* escape
the key by adding parenthesis, like in the following example:
[source,groovy]
--------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_literal_gotcha,indent=0]
--------------------------------------------------
In addition to map literals, it is possible, to get a new copy of a map, to clone it:
[source,groovy]
--------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_construct,indent=0]
--------------------------------------------------
The resulting map is a *shallow* copy of the original one, as illustrated in the previous example.
=== Map property notation
Maps also act like beans so you can use the property notation to get/set
items inside the `Map` as long as the keys are strings which are valid
Groovy identifiers:
[source,groovy]
--------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_property,indent=0]
--------------------------------------------------
Note: by design `map.foo` will always look for the key `foo` in the map. This
means `foo.class` will return `null` on a map that doesn't contain the `class` key. Should you really want to know
the class, then you must use `getClass()`:
[source,groovy]
--------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_property_gotcha,indent=0]
--------------------------------------------------
=== Iterating on maps
As usual in the {gdk}, idiomatic iteration on maps makes use of the `each` and `eachWithIndex` methods.
It's worth noting that maps created using the map literal notation are *ordered*, that is to say that if you iterate
on map entries, it is guaranteed that the entries will be returned in the same order they were added in the map.
[source,groovy]
--------------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_iteration,indent=0]
--------------------------------------------------------------------------------
=== Manipulating maps
==== Adding or removing elements
Adding an element to a map can be done either using the `put` method, the subscript operator or using `putAll`:
[source,groovy]
---------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_add,indent=0]
---------------------
Removing all the elements of a map can be done by calling the `clear` method:
[source,groovy]
---------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk1,indent=0]
---------------------
Maps generated using the map literal syntax are using the object `equals` and `hashcode` methods. This means that
you should *never* use an object which hash code is subject to change over time, or you wouldn't be able to get
the associated value back.
It is also worth noting that you should *never* use a `GString` as the key of a map, because the hash code of a `GString`
is not the same as the hash code of an equivalent `String`:
[source,groovy]
---------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=gstring_gotcha,indent=0]
---------------------
[[JN1035-Maps-Collectionviewsofamap]]
==== Keys, values and entries
We can inspect the keys, values, and entries in a view:
[source,groovy]
-------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_views,indent=0]
-------------------------------------------------------------------------
Mutating values returned by the view (be it a map entry, a key or a value) is highly discouraged because success
of the operation directly depends on the type of the map being manipulated. In particular, Groovy relies on collections
from the JDK that in general make no guarantee that a collection can safely be manipulated through `keySet`, `entrySet`, or
`values`.
==== Filtering and searching
The {gdk} contains filtering, searching and collecting methods similar to those found
for <<List-Filtering,lists>>:
[source,groovy]
-----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk2,indent=0]
-----------------------------------------------------------------------------
[[Maps-Grouping]]
==== Grouping
We can group a list into a map using some criteria:
[source,groovy]
----------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=map_gdk3,indent=0]
----------------------------------------------------------------
[[Collections-Ranges]]
== Ranges
Ranges allow you to create a list of sequential values. These can be
used as `List` since
http://docs.groovy-lang.org/latest/html/api/groovy/lang/Range.html[Range] extends
{java-util-list}.
Ranges defined with the `..` notation are inclusive (that is the list
contains the from and to value).
Ranges defined with the `..<` notation are half-open, they include the
first value but not the last value.
[source,groovy]
----------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=intrange,indent=0]
----------------------------------------------------------------------------
Note that int ranges are implemented efficiently, creating a lightweight
Java object containing a from and to value.
Ranges can be used for any Java object which implements {java-lang-comparable}
for comparison and also have methods `next()` and `previous()` to return the
next / previous item in the range. For example, you can create a range of `String` elements:
[source,groovy]
--------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=stringrange,indent=0]
--------------------------------------
You can iterate on a range using a classic `for` loop:
[source,groovy]
----------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=range_for,indent=0]
----------------------
but alternatively you can achieve the same effect in a more Groovy idiomatic style, by iterating a range
with `each` method:
[source,groovy]
----------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=range_each,indent=0]
----------------------
Ranges can be also used in the `switch` statement:
[source,groovy]
----------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=range_switch,indent=0]
----------------------
== Syntax enhancements for collections
=== GPath support
Thanks to the support of property notation for both lists and maps, Groovy provides syntactic sugar making it
really easy to deal with nested collections, as illustrated in the following examples:
[source,groovy]
------------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=gpath_support_1,indent=0]
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=gpath_support_2,indent=0]
------------------------------------------------------------------------------
=== Spread operator
The spread operator can be used to "inline" a collection into another. It is syntactic sugar which often avoids calls
to `putAll` and facilitates the realization of one-liners:
[source,groovy]
------------------------------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=gpath_support_3,indent=0]
------------------------------------------------------------------------------
[[Collections-Gettingefficientwiththestar-dotoperator]]
=== The star-dot `*.' operator
The "star-dot" operator is a shortcut operator allowing you to call a method or a property on all elements of a
collection:
[source,groovy]
-------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=stardot_1,indent=0]
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=stardot_2,indent=0]
-------------------------------------------------
[[Collections-Slicingwiththesubscriptoperator]]
=== Slicing with the subscript operator
You can index into lists, arrays, maps using the subscript expression. It is interesting that strings
are considered as special kinds of collections in that context:
[source,groovy]
--------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript,indent=0]
--------------------------------------------------
Notice that you can use ranges to extract part of a collection:
[source,groovy]
-----------------------------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_2,indent=0]
-----------------------------------------------------------
The subscript operator can be used to update an existing collection (for collection type which are not immutable):
[source,groovy]
---------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_3,indent=0]
---------------------------------
It is worth noting that negative indices are allowed, to extract more easily from the end of a collection:
[source,groovy]
---------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_4,indent=0]
---------------------------------
You can use negative indices to count from the end of the List, array,
String etc.
[source,groovy]
--------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_4a,indent=0]
--------------------------------
Eventually, if you use a backwards range (the starting index is greater than
the end index), then the answer is reversed.
[source,groovy]
--------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithCollectionsTest.groovy[tags=subscript_5,indent=0]
--------------------------------
[[Collections-EnhancedCollectionMethods]]
== Enhanced Collection Methods
In addition to <<Collections-Lists,lists>>, <<Collections-Maps,maps>> or <<Collections-Ranges,ranges>>, Groovy offers
a lot of additional methods for filtering, collecting, grouping, counting, ... which are directly available on either
collections or more easily iterables.
In particular, we invite you to read the {gdk} API docs and specifically:
* methods added to `Iterable` can be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Iterable.html[here]
* methods added to `Iterator` can be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Iterator.html[here]
* methods added to `Collection` can be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Collection.html[here]
* methods added to `List` can be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/List.html[here]
* methods added to `Map` can be found http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Map.html[here]