| ////////////////////////////////////////// |
| |
| 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] |
| |