Friday, October 2, 2009

Groovy JDK (GDK): List Enhancements

In prior blog posts, I have discussed several highly useful classes and methods provided as part of the Groovy JDK (GDK). These posts have covered GDK-extended classes such as File, String, and Number. In this post, I cover GDK's extension of one of the most popular interfaces in the Java Collections Framework: the List.

The GDK List extension provides numerous convenient methods and includes methods that provide mathematical operator support for Lists. In this blog post, I look at several of these methods and operators provided by the Groovy extended List.

In my first code listing, I set up two Lists. The first is set up without any explicit specification of interface or implementation. The second is set up with explicit interface declared, but no specific implementation of that interface is specified.

println "====== SET UP TWO LISTS ====="

// Obtain a List implicitly (dynamic typing)
implicitList = ["Arizona", "New Mexico", "Colorado", "Massachusetts", "Utah", "Wyoming"]
println "Implicit: ${implicitList.getClass().name}"

// Explicitly specify List interface, but rely on implementation implicitly
List explicitList = ["Phoenix", "Santa Fe", "Denver", "Boston", "Salt Lake City", "Cheyenne"]
println "Explicit: ${explicitList.getClass().name}\n"

I included a println statement after each variable declaration that prints out the type of concrete class actually being used. As the output in the next screen snapshot indicates, an ArrayList is implicitly applied in both cases.

With two lists prepared, we can start using GDK's extended List functions to operate on these lists. I have blogged previously about using the Java Collections class to make provide a Collection that is immutable or a to provide a Collection that is synchronized. The GDK List class makes these Collections.unmodifiableList(List) and Collections.synchronizedList(List) methods even easier to use directly on the List with its List.asImmutable() and List.asSynchronized() methods. These are demonstrated in the next (second) code listing.

// Demonstrate easy access to immutable version of List
List immutableList = implicitList.asImmutable()
println "List.asImmutable: ${immutableList.getClass().name}"
println "Collections.unmodifiableList: ${Collections.unmodifiableList(implicitList).getClass().name}\n"

// Demonstrate easy access to synchronized version of List.
List synchronizedList = implicitList.asSynchronized();
println "List.asSynchronized: ${synchronizedList.getClass().name}"
println "Collections.synchronizedList: ${Collections.synchronizedList(implicitList).getClass().name}\n"

The output from the above code is shown in the next screen output. We see that the same type of objects returned from the respective Collections methods are returned here.

The GDK List class adds methods for accessing the first element, the last element, the head (first element), or the tail elements (all elements except the head element). These four methods are demonstrated in the next (third) code listing.

// Demonstrate List.first()/List.head() and List.last()/List.tail()
println "First state is ${implicitList.first()} and last state is ${implicitList.last()}"
println "Head state is ${implicitList.head()} and tail states are ${implicitList.tail()}\n"

The output corresponding to the code listing directly above is shown next.

Groovy makes it really easy to generate a List with entries in the reverse order of the original List via its GDK List.reverse() method. This is demonstrated in the next (fourth) code listing.

// Demonstrate List.reverse()
reverseList = implicitList.reverse();
println "Content of reverseList: $reverseList\n"

The output from running the above Groovy code is shown next.

During my transition from being primarily a C++ developer to being primarily a Java developer, I often found myself missing operator overloading. Groovy brings it back in the Java world. One example of this is the support for the + operator supported by Groovy's GDK List. The next example demonstrates adding an element to a List using the + operator and adding another List (in this case a single-element List) to the original List using the + operator.

// Demonstrate plus (+) operator
implicitList += "Montana"
println "Montana added with +: $implicitList"
explicitList += ["Helena"]
println "Helena added with +: $explicitList\n"

The GDK List supports LIFO stack semantics with its List.push(T) and List.pop() methods, demonstrated in the next code example.

// Demonstrate List.push()
implicitList.push "Idaho"
println "Implicit with push-ed Idaho: $implicitList"
explicitList.push "Boise"
println "Explicit with pushed-ed Boise: $explicitList\n"

// Demonstrate List.pop()
println "Implicit after pop-ed: $implicitList"
println "Explicit after pop-ed: $explicitList\n"

The following output demonstrates that these List.push(T) and List.pop() methods work as we would expect.

The Groovy GDK List class supports the - operator similarly to how it supports the + operator. The following code snippet demonstrates removing one List from another using List.minus(Collection) method (there is also an overloaded version for removing an individually provided object) and using the - operator.

// Demonstrate minus and "-"
rockyMountainStates = implicitList.minus("Massachusetts")
println "Rocky Mountain States (after 'minus'): $rockyMountainStates"
capitalsToRemove = ["Boston"]
rockyMountainCapitals = explicitList - capitalsToRemove
println "Rocky Mountain Capitals (after '-'): $rockyMountainCapitals\n";

This code's output is shown next with Massachusetts and Boston missing from their respective Lists.

As a potential preview of what is to come with Java SE 7, Groovy supports subscript notation for Lists. What this means is that individual elements of a GDK List can be accessed in an array-like fashion.

// Use subscript operator to replace Massachusets/Boston with Idaho/Boise
implicitList[3] = "Idaho"
println "Put implicitList[3]: $implicitList"
explicitList[3] = "Boise"
println "Put explicitList[3]: $explicitList\n"

// Demonstrate retrieving by subscript operator
println "Get implicitList[2]: ${implicitList[2]}"
println "Get explicitList[2]: ${explicitList[2]}\n"

The above Groovy code places "Idaho" and "Boise" back in the original Lists in place of "Massachusetts" and "Boston" respectively. Then, the elements for "Colorado" and "Denver" are extracted from the Lists. Both the setting and the getting of elements of the underlying List were accomplished using [index] syntax where index is a 0-based index value. The output is shown next.

The GDK List.reverseEach(Closure) method allows one to iterate in reverse order over the target List and invoke the provided closure on each item iterated over. The following code demonstrates this with a very simplistic closure that simply prints the currently iterated element's String contents.

// Demonstrate reverse iteration with simple closure
implicitList.reverseEach() {println it}

The output for the above code is shown next.

I previously blogged about the GDK String.execute() method that allows the String upon which that call is invoked to be executed against the underlying operating system. The GDK List class provides a similar List.execute() method that expects the first list item to be the actual command to be executed and the remaining list elements to be the parameters to that command. In other words, List.head() should provide the command in a List built to be used with execute() and List.tail() should provide the parameters to that command.

An example of this is shown in the next code listing (Windows-specific example):

// Demonstate List.process
commandList = ["cmd", "/C", "type", "groovyList.groovy"]
println "${commandList.execute().text}"

The output from the above is actually simply the entire Groovy script containing the code examples in this post. For brevity, I only show the bottom of that output here.

The GDK List adds several convenience methods and operator support to the standard JDK List. This makes List data structures easier than ever to use and gives us a taste of what may come in future versions of Java.

No comments: