Java arrays cannot be changed in length, so you cannot add elements easily. One way is to convert the array to a java.util.List , add the element, and convert back. A second way is to construct a new array of size+1, copy the old values over, and set the new element to the last index position. Either takes some lines of code.
But Java arrays also have their benefits in terms of language support. They work with the subscript operator to easily retrieve elements of an array by index likemyarray[index] , or to store elements at an index position with myarray [index] = newElement .
We will demonstrate how Groovy lists give you the best of both approaches, extending the features for smart operator implementations, method overloading, and using lists as Booleans. With Groovy lists, you will also discover new ways of leveraging the power of the Java Collections API.
Specifying lists:
Listing 4.4 shows various ways of specifying lists. The primary way is with square brackets around a sequence of items, delimited with commas:
- def list = [item, item, item]
Lists can be created and initialized at the same time by calling toList on ranges.
- Listing 4.4 Specifying lists
- myList = [1,2,3]
- assert myList.size() == 3
- assert myList[0] == 1
- assert myList instanceof ArrayList
- emptyList = []
- assert emptyList.size() == 0
- longList = (0..1000).toList()
- assert longList[555] == 555
- explicitList = new ArrayList()
- explicitList.addAll(myList)
- assert explicitList.size() == 3
- explicitList[0] = 10
- assert explicitList[0] == 10
- explicitList = new LinkedList(myList) // 1) Fill from myList
- assert explicitList.size() == 3
- explicitList[0] = 10
- assert explicitList[0] == 10
Using list operators:
Lists implement some of the operators that you saw in section 3.3. Listing 4.4 contained two of them: the getAt and putAt methods to implement the subscript operator. But this was a simple use that works with a mere index argument. There’s much more to the list operators than that.
The subscript operator
The GDK overloads the getAt method with range and collection arguments to access a range or a collection of indexes. This is demonstrated in Listing 4.5.
- Listing 4.5 Accessing parts of a list with the overloaded subscript operator
- myList = ['a','b','c','d','e','f']
- assert myList[0..2] == ['a','b','c'] // getAt(Range)
- assert myList[0,2,4] == ['a','c','e'] // getAt(collection of indexes)
- myList[0..2] = ['x','y','z'] // putAt(Range)
- assert myList == ['x','y','z','d','e','f']
- myList[3..5] = [] // 1) Removing elements
- assert myList == ['x','y','z']
- myList[1..1] = ['1','2'] // 2) Adding elements
- assert myList == ['x','1','2','z']
Consequently, you get the last entry of a non-empty list with list[-1] and the next-to-last with list[-2]. Negative indexes can also be used in ranges, so list[-3..-1] gives you the last three entries. When using a reversed range, the resulting list is reversed as well, so list[4..0] is [4,3,2,1,0] . In this case, the result is a new list object rather than a sublist in the sense of the JDK. Even mixtures of positive and negative indexes are possible, such as list[1..-2] to cut away the first entry and the last entry.
- myList = ['x','1','2','z']
- alist = myList[1..-2]
- assert alist == ['1','2']
Adding and removing items
Although the subscript operator can be used to change any individual element of a list, there are also operators available to change the contents of the list in a more drastic way. They are plus(Object) , plus(Collection) , leftShift(Object) , minus(Collection) , and multiply . Listing 4.6 shows them in action. The plus method is overloaded to distinguish between adding an element and adding all elements of a collection. The minus method only works with collection parameters.
- Listing 4.6 List operators involved in adding and removing items
- myList = []
- myList += 'a' // plus(Object)
- assert myList == ['a']
- myList += ['b','c'] // plus(Collection)
- assert myList == ['a','b','c']
- myList = []
- myList << 'a' << 'b' // leftShift is like append
- assert myList == ['a','b']
- assert myList - ['b'] == ['a'] // minus(Collection)
- // Multiply
- assert myList * 2 == ['a','b','a','b']
Groovy lists are more than flexible storage places. They also play a major role in organizing the execution flow of Groovy programs. Listing 4.7 shows the use of lists in Groovy’s if , switch , and for control structures.
- Listing 4.7 Lists taking part in control structures
- myList = ['a', 'b', 'c']
- assert myList.isCase('a')
- // 1) Classify by containment
- candidate = 'a'
- switch(candidate){
- case myList : assert true; break
- default : assert false
- }
- // 2) Intersection filter
- assert ['x','a','z'].grep(myList) == ['a']
- // 3) Empty lists are false
- myList = []
- if (myList) assert false
- // Lists can be iterated with a 'for' loop
- log = ''
- for (i in [1,'x',5]){ // 4) for in Collection
- log += i
- }
- assert log == '1x5'
There are so many useful methods on the List type that we cannot provide an example for all of them in the language description. The large number of methods comes from the fact that the Java interface java.util.List is already fairly wide (25 methods in JDK 1.4).
Furthermore, the GDK adds methods to the List interface, to the Collection interface, and to Object . Therefore, many methods are available on the List type, including all methods of Collection and Object. While working with lists in Groovy, there is no need to be aware of whether a method stems from the JDK or the GDK, or whether it is defined in the List or Collection interface. However, for the purpose of describing the Groovy List datatype, we fully cover the GDK methods on lists and collections, but not all combinations from overloaded methods and not what is already covered in the previous examples. We provide only partial examples of the JDK methods that we consider important.
Manipulating list content
A first set of methods is presented in Listing 4.8. It deals with changing the content of the list by adding and removing elements; combining lists in various ways; sorting, reversing, and flattening nested lists; and creating new lists from existing ones.
- Listing 4.8 Methods to manipulate list content
- assert [1,[2,3]].flatten() == [1,2,3]
- assert [1,2,3].intersect([4,3,1])== [3,1]
- assert [1,2,3].disjoint([4,5,6])
- list = [1,2,3]
- def popped = list.pop() // 1) Treating a list like a stack
- assert popped == 3
- assert list == [1,2]
- assert [1,2].reverse() == [2,1]
- assert [3,1,2].sort() == [1,2,3]
- def list = [ [1,0], [0,1,2] ]
- list = list.sort { a,b -> a[0] <=> b[0] } // 2) Comparing lists by first element
- assert list == [ [0,1,2], [1,0] ]
- list = list.sort { item -> item.size() } // 3) Comparing lists by size
- assert list == [ [1,0], [0,1,2] ]
- list = ['a','b','c']
- list.remove(2) // 4) Removing by index
- assert list == ['a','b']
- list.remove('b') // 5) Removing by value
- assert list == ['a']
- list = ['a','b','b','c']
- list.removeAll(['b','c'])
- assert list == ['a']
- // 6) Transforming one list to another list
- def doubled = [1,2,3].collect{ item ->
- item*2
- }
- assert doubled == [2,4,6]
- // 7) Finding any element matching the closure
- def odd = [1,2,3].findAll{ item ->
- item % 2 == 1
- }
- assert odd == [1,3]
- def x = [1,1,1]
- assert [1] == new HashSet(x).toList()
- assert [1] == x.unique()
- def x = [1,null,1]
- assert [1,1] == x.findAll{it != null}
- assert [1,1] == x.grep{it}
Accessing list content
Lists have methods to query their elements for certain properties, iterate through them, and retrieve accumulated results. Query methods include a count of given elements in the list, min and max , a find method that finds the first element that satisfies a closure, and methods to determine whether any or every element in the list satisfies a closure. Iteration can be achieved as usual, forward with each or backward with eachReverse .
Cumulative methods come in simple and sophisticated versions. The join method is simple: It returns all elements as a string, concatenated with a given string. The injectmethod is inspired by Smalltalk. It uses a closure to inject new functionality. That functionality operates on an intermediary result and the current element of the iteration. The first parameter of the inject method is the initial value of the intermediary result. In listing 4.9, we use this method to sum up all elements and then use it a second time to multiply them.
- Listing 4.9 List query, iteration, and accumulation
- // Querying
- def list = [1,2,2,3,3,3]
- assert list.count(2) == 2
- assert list.max() == 3
- assert list.min() == 1
- def even = list.find { item ->
- item % 2 == 0
- }
- assert even == 2
- assert list.every { item -> item < 5}
- assert list.any { item -> item < 2}
- // Iteration
- def store = ''
- list.each { item ->
- store += item
- }
- assert store == '122333'
- store = ''
- list.reverseEach{ item ->
- store += item
- }
- assert store == '333221'
- // Accumulation
- assert list.join('-') == '1-2-2-3-3-3'
- def result = list.inject(0){ clinks, guests ->
- clinks += guests
- }
- assert result == 0+1+2+2+3+3+3
- assert list.sum() == 14
- def factorial = list.inject(1){ fac, item ->
- fac *= item
- }
- assert factorial == 1*1*2*2*3*3*3
The GDK introduces two more convenience methods for lists: asImmutable and asSynchronized . These methods use Collections.unmodifiableList andCollections.synchronizedList to protect the list from unintended content changes and concurrent access. See these methods’ Javadocs for more details on the topic.
Lists in action:
After all the artificial examples, you deserve to see a real one. Here it is: We will implement Tony Hoare’s Quicksort 1 algorithm in listing 4.10. To make things more interesting, we will do so in a generic way; we will not demand any particular datatype for sorting. We rely on duck typing—as long as something walks like a duck and talks like a duck, we happily treat it as a duck. For our use, this means that as long as we can use the < , = , and > operators with our list items, we treat them as if they were comparable.
The goal of Quicksort is to be sparse with comparisons. The strategy relies on finding a good pivot element in the list that serves to split the list into two sublists: one with all elements smaller than the pivot, the second with all elements bigger than the pivot. Quicksort is then called recursively on the sublists. The rationale behind this is that you never need to compare elements from one list with elements from the other list. If you always find the perfect pivot, which exactly splits your list in half, the algorithm runs with a complexity of n*log(n). In the worst case, you choose a border element every time, and you end up with a complexity of n^2 . In listing 4.10, we choose the middle element of the list, which is a good choice for the frequent case of preordered sublists.
- Listing 4.10 Quicksort with lists
- def quickSort(list) {
- if (list.size() < 2) return list
- def pivot = list[list.size().intdiv(2)]
- // 1) Classify by pivot
- def left = list.findAll {item -> item < pivot }
- def middle = list.findAll {item -> item == pivot }
- def right = list.findAll {item -> item > pivot }
- return (quickSort(left) + middle + quickSort(right))
- }
- assert quickSort([]) == []
- assert quickSort([1]) == [1]
- assert quickSort([1,2]) == [1,2]
- assert quickSort([2,1]) == [1,2]
- assert quickSort([3,1,2]) == [1,2,3]
- assert quickSort([3,1,2,2]) == [1,2,2,3]
- // 2) Duck-typed items
- assert quickSort([1.0f,'a',10,null])== [null, 1.0f, 10, 'a']
- // 3) Duck-typed structure
- assert quickSort('Karin and Dierk') == ' DKaadeiiknnrr'.toList()
If we had to explain the Quicksort algorithm without the help of Groovy, we would sketch it in pseudocode that looks exactly like listing 4.10. In other words, the Groovy code itself is the best description of what it does!
Supplement:
* Groovy > Document > Collections
沒有留言:
張貼留言