by
Mark Volkmann, Partner
Object Computing, Inc. (OCI)
Groovy is an open-source scripting language that is implemented in Java and is tightly integrated with it. It requires Java's JDK 1.4. Groovy adds some features of the Ruby and Python scripting languages to Java. Features of Groovy include dynamic typing, closures, easy object navigation and more compact syntax for working with Lists and Maps. These features and more are described in detail in this article.
Here's a quote from the Groovy web site. "Groovy is designed to help you get things done on the Java platform in a quicker, more concise and fun way - bringing the power of Python and Ruby inside the Java Platform."
Groovy scripts can use any Java classes. They can be compiled to Java bytecode (in .class files) that can be invoked from normal Java classes. The Groovy compiler, groovyc, compiles both Groovy scripts and Java source files, however some Java syntax (such as nested classes) is not supported yet.
In theory, entire applications could be written in Groovy that would have similar performance characteristics to an equivalent Java application. This distinguishes Groovy from other scripting languages such as Ruby, Python, Perl and BeanShell. One of the things that makes Groovy perform more slowly than Java today is that the generated bytecode uses reflection to call constructors and private/protected methods. This will be addressed over upcoming releases.
Groovy was created by James Strachan and Bob McWhirter. James is also involved in the development of many other open source products including Jelly, dom4j, Jaxen, Betwixt and Maven. Bob is the creator of Jaxen and Drools (an open source, object-oriented, Java rules engine).
This article doesn't cover every feature of Groovy, but it covers a large number of them. It assumes that you are familiar enough with Java syntax to be able to compare Java syntax to Groovy syntax.
If we could all agree on what makes a good programming language syntax then we wouldn't need so many of them. Based on the number of programming languages out there, we obviously don't agree. After reading this article, you may decide that you like Java syntax just fine and that Groovy syntax is just too much syntactic sugar for your tastes. If that is your conclusion, I encourage you to investigate BeanShell from Pat Niemeyer at www.beanshell.org. It sticks much closer to standard Java syntax. On the other hand, if you prefer the shorter syntax of Groovy then that's just groovy!
To download Groovy, follow these steps.
The latest version in CVS can also be downloaded. For instructions on doing this see here.
To install Groovy, follow these steps.
There are four ways to execute Groovy scripts. In all of these cases, the script lines are parsed, converted to Java source and compiled to Java bytecode.
The command groovysh
starts an interactive shell where
Groovy statements can be entered.
Enter any number of statements, pressing the enter key after each.
Statements are not evaluated or executed until
the execute
command is entered.
The command groovyConsole
launches a Swing window.
Enter Groovy statements in the bottom part of the window.
Select Run from the Actions menu to execute them.
Output appears in the top part of the window.
Scripts can be opened and saved using the File menu.
A Groovy script file (typically named with a .groovy file extension)
can be executed using the command
groovy script-name.groovy
.
A Groovy script file can be compiled to a Java .class file using
the command groovyc script-name.groovy
.
If there are loose statements in the script,
this .class file will contain a main method, so it can be executed as a
Java application using the command java script-name
.
The contents of the main method will be discussed later.
The classpath must contain the
groovy*.jar
and asm*.jar
files
from the Groovy lib directory.
There’s even a custom Ant task to do this!
The class is org.codehaus.groovy.ant.Groovyc
.
Here are some of the key differences between Java and Groovy syntax. Others will be covered later.
java.lang
, groovy.lang
and
groovy.util
are automatically imported.Types are optional for variables, properties, method/closure parameters and method return types. They take on the type of whatever was last assigned to them. Different types can be used later. Any type can be used, even primitives (through autoboxing). Many type coersions occur automatically when needed such as conversions between these types: String, primitive types (like int) and type wrapper classes (like Integer). This allows primitives to be added to collections.
Groovy adds many methods to standard Java classes such as java.lang.Object and java.lang.String. See groovy.codehaus.org/groovy-jdk.html. Those added to Object are covered here. Others will be discussed later.
This returns a string of the form
<class-name@hashcode property-name=property-value ...>
For example, <Car@ef5502 make=Toyota model=Camry>
.
These static methods print the toString
value of object.
For example, print car
or println car
.
This provides dynamic method invocation using reflection.
The syntax is object.invokeMethod(method-name, argument-array)
.
The following example prints the value 4.
s = 'abcabc' // a java.lang.String method = 'indexOf' args = ['b', 2] println s.invokeMethod(method, args)
Literal strings can be surrounded with single or double quotes.
When double quotes are used, they can contain embedded values.
The syntax for an embedded value is ${expression}
which is just like Ruby except $ is used instead of #.
Strings surrounded by double quotes that contain at least one embedded value
are represented by a groovy.lang.GString
object.
Other strings are represented by a java.lang.String
.
GStrings are automatically coerced to java.lang.String
when needed.
Javadoc for Groovy classes like groovy.lang.GString can be found at groovy.codehaus.org/apidocs/.
Embedded values are very useful for implementing toString methods. For example,
String toString() { "${name} is ${age} years old." }
Multi-line strings can be created in three ways. The following examples are equivalent. The last example uses what is called a "here-doc". After the three less-than characters, a delimiter string is specified. The string value includes all the characters between the first and second occurrence of the delimiter string. "EOS" (standing for "End Of String") is a common delimiter value, but others can be used.
s = " This string spans three \"lines\" and contains two newlines." s = """ This string spans three "lines" and contains two newlines.""" s = <<<EOS This string spans three "lines" and contains two newlines. EOS
Note that the last newline character in the previous code snippet is NOT retained.
The following methods are added to java.lang.String
.
This determines whether a string contains a substring.
'Groovy'.contains('oo')
returns true
.
This counts occurrences of a substring within a string.
'Groovy Tool'.count('oo')
returns 2
.
This tokenizes a string using a given delimeter
and returns a list of the tokens.
Specifying the delimiter parameter is optional.
It defaults to whitespace characters.
'apple^banana^grape'.tokenize('^')
returns ['apple', 'banana', 'grape']
.
This removes first occurrence of a substring from a string.
'Groovy Tool' - 'oo'
returns 'Grvy Tool'
.
This repeats a string a given number of times.
'Groovy'
* 3 returns 'GroovyGroovyGroovy'
.
First, let's review the support for regular expressions in J2SE 1.4. Then we'll discuss how Groovy builds on this.
In J2SE 1.4, regular expressions are supported by classes in
java.util.regex
package.
Pattern objects represent a compiled regex.
They are created with Pattern.compile("pattern")
.
The javadoc for this class describes regular expression syntax.
Matcher objects hold the results of matching a Pattern against a String.
They are created with pattern.matcher("text")
.
To simply determine if the text matches the pattern,
matcher.matches()
is used.
Backslashes in patterns must be escaped with an extra backslash.
Groovy supports regular expressions with three operators.
~"pattern"
creates a Pattern object
and is equivalent to Pattern.compile("pattern")
.
"text" =~ "pattern"
creates a Matcher object
and is equivalent to
Pattern.compile("pattern").matcher("text")
.
"text" ==~ "pattern"
-
returns a boolean match indication and is equivalent to
Pattern.compile("pattern").matcher("text").matches()
.
See the javadoc of Pattern and Matcher for additional methods.
For example,
pattern = "\\d{5}" // matches zip codes (5 digits) text = "63304" // a zip code println text ==~ pattern // prints "true" m = text =~ pattern println m.matches() // prints "true" // The next line requires a literal string for the pattern. // A variable can't used. p = ~"\\d{5}" m = p.matcher(text) println m.matches() // prints "true"
Groovy scripts are source files that typically have a ".groovy" file extension. They can contain (in any order) loose statements, method definitions not associated with a class, and class definitions.
For example,
// These are loose statements. println 'loose statement' myMethod 'Mark', 19 println new MyClass(a1:'Running', a2:26.2) // This is a method definition that // is not associated with a class. def myMethod(p1, p2) { println "myMethod: p1=${p1}, p2=${p2}" } // This is a definition of a class that // has two properties and one method. class MyClass { a1; a2 String toString() { "MyClass: a1=${a1}, a2=${a2}" } }
Method and class definitions do not have to appear before their first use.
Loose methods get compiled to static methods
in the class that corresponds to the script file.
For example, a loose method named foo
in a script called Bar.groovy
will get compiled to a static method named
foo
in the class Bar
.
Loose statements are collected in a run
method
that is invoked by a generated main
method
When groovyc
is used to compile a script,
the generated class will have a run
method
that contains all the loose statements
and a main
method that invokes run
.
Currently scripts cannot invoke code in other scripts unless they are compiled and imported. This should be fixed soon.
Groovy supports operator overloading for a fixed set of operators. Each operator is mapped to a particular method name. Implementing these methods in your classes allows the corresponding operators to be used with objects from those classes. The methods can be overloaded to work with various parameter types.
a == b
maps to a.equals(b)
a != b
maps to !a.equals(b)
a === b
maps to a == b
in Java
a <=> b
maps to a.compareTo(b)
a > b
maps to a.compareTo(b) > 0
a >= b
maps to a.compareTo(b) >= 0
a < b
maps to a.compareTo(b) < 0
a <= b
maps to a.compareTo(b) <= 0
The comparison operators handle null values and never generate a NullPointerException. Null is treated as less than everything else.
Notice that in Groovy
the ==
operator is used to test
whether two objects have the same value and
the ===
operator is used to test
whether they are the same object in memory.
The compareTo
method returns an int
less than 0 if a < b,
greater than 0 if a > b,
and 0 if a is equal to b.
a + b
maps to a.plus(b)
a - b
maps to a.minus(b)
a * b
maps to a.multiply(b)
a / b
maps to a.divide(b)
a++
and ++a
maps to a.increment(b)
a--
and --a
maps to a.decrement(b)
a[b]
maps to a.get(b)
a[b] = c
maps to a.put(b, c)
A closure is a snippet of code that optionally accepts parameters.
Each closure is compiled into a new class that extends
groovy.lang.Closure
.
This class has a call
method that can be used
to invoke a closure and pass parameters to it.
They can access and modify variables
that are in scope when the closure is created.
Variables created inside a closure are available
in the scope where the closure is invoked.
Closures can be held in variables and passed as parameters to methods.
This is particularly useful in certain
list, map and string methods that are described later.
The syntax for defining a closure is
{ comma-separated-parameter-list | statements }
For example,
closure = { bill, tipPercentage | bill * tipPercentage / 100 } tip = closure.call(25.19, 15) tip = closure(25.19, 15) // equivalent to previous line
Passing the wrong number of parameters
results in an IncorrectClosureArgumentException
.
The keyword it
is used in closures with one parameter.
The parameter list can be omitted and referred to
in statements with it
.
For example, the following closures are equivalent.
{ x | println x } { println it }
Here's an example of a method that takes a List and a Closure as parameters.
It is written as a "loose method", but it also
could be written as a method of some class.
The method iterates over the List,
invoking the Closure on each item in the List.
It populates a new List with the items for which the closure returns true
and then returns the new List.
Note that Groovy provides find
and findAll
methods that can be used instead of this.
def List myFind(List list, Closure closure) { List newList = [] for (team in list) { if (closure.call team) newList.add team } newList }
Here's an example of using this method.
class Team { name; wins; losses } teams = [] teams.add new Team(name:'Rams', wins:12 , losses:4) teams.add new Team(name:'Raiders', wins:4 , losses:12) teams.add new Team(name:'Packers', wins:10 , losses:6) teams.add new Team(name:'49ers', wins:7 , losses:9) winningTeams = myFind(teams) { it.wins > it.losses } winningTeams.each { println it.name }
There’s no need to write a method like myFind
since the List class already has a findAll
method.
To use it,
winningTeams = teams.findAll { it.wins > it.losses }
Here's an example of a simple Groovy Bean.
class Car { String make String model }
This class declares two properties and no methods.
However, there is a lot going on behind the scenes.
Classes, properties and methods are public by default.
Public and protected properties result in private fields for which
public/protected get and set methods are automatically generated.
These can be overridden to provide custom behavior.
For properties that are explicitly declared to be private
,
get and set methods are not generated.
The above Groovy code is equivalent to the following Java code.
public class Car { private String make; private String model; public String getMake() { return make; } public String getModel() { return model; } public void setMake(String make) { this.make = make; } public void setModel(String model) { this.model = model; } }
The classes generated by Groovy Beans extend java.lang.Object
and implement groovy.lang.GroovyObject
.
This adds methods the methods
getProperty
, setProperty
,
getMetaClass
, setMetaClass
and invokeMethod
.
groovy.lang.MetaClass
allows methods to be added at runtime.
Groovy Beans can be created using named parameters. For example, the following code calls the Car no-arg constructor and then a set method for each specified property.
myCar = new Car(make:'Toyota', model:'Camry')
Groovy lists are instances of java.util.ArrayList
.
They can be created using a comma-separted list
of values in square brackets.
For example,
cars = [new Car(make:'Honda', model:'Odyssey'), new Car(make:'Toyota', model:'Camry')] println cars[1] // refers to Camry for (car in cars) { println car } // invokes Car toString method class Car { make; model String toString() { "Car: make=${make}, model=${model}" } }
To locate list items starting from the end of the list, use negative indexes.
Empty lists can be created with []
. For example,
cars = []
Items can be added to lists in two ways.
cars.add car cars << car
Lists can be created from arrays with
array.toList()
.
Arrays can be created from lists with
list.toArray()
.
Groovy adds several methods to java.util.List
.
This counts the elements in a list that are equal to a given object.
[1, 2, 3, 1].count(1)
returns 2
.
This creates an immutable copy of a collection
using the static unmodifiableList
method
in java.util.Collections
. For example,
list = [1, 2, 3].immutable() list.add 4 // throws java.lang.UnsupportedOperationException
This creates a list containing the common elements of two lists.
[1, 2, 3, 4].intersect([2, 4, 6])
returns [2, 4]
.
This concatenates list item toString
values
with a given string between each.
For example, this places a caret delimiter
between all the strings in a List.
['one', 'two', 'three'].join('^')
returns "one^two^three"
.
This sorts list elements and creates a new list.
It accepts a java.util.Comparator
or a closure for custom ordering.
fruits = ['kiwi', 'strawberry', 'grape', 'banana'] // The next line returns [banana, grape, kiwi, strawberry]. sortedFruits = fruits.sort() // The next line returns [kiwi, grape, banana, strawberry]. sortedFruits = fruits.sort {l, r | return l.length() <=> r.length()}
The last call to sort
above is an example of a method
that takes a closure as a parameter.
There are many methods in Groovy that do this.
Groovy Beans can easily be sorted on multiple properties.
Suppose there is a Player
bean with properties
name
, age
and score
.
To sort a list of these beans called players
based on age
and then score
,
players.sort { [it.age, it.score] }
These find the minimum or maximum list item or string character.
They accept a java.util.Comparator
or a closure for custom ordering.
For example, these find the minimum and maximum number in a list.
[5, 9, 1, 6].min()
returns 1
.
[5, 9, 1, 6].max()
returns 9
.
This reverses the order of elements in a list or characters in a string.
[1, 2, 3].reverse()
returns [3, 2, 1]
.
Groovy overloads the plus and minus operators
for working with java.util.List
objects.
This creates a union of two lists, but duplicates are not removed.
[1, 2, 3] + [2, 3, 4]
returns [1, 2, 3, 2, 3, 4]
.
This removes all elements from the first list that are in the second.
[1, 2, 3, 4] - [2, 4, 6]
returns [1, 3]
.
When the list items are not primitives,
the equals
method is used to compare them.
Groovy maps are instances of java.util.HashMap
.
They can be created using a comma-separated list
of key/value pairs in square brackets.
The keys and values are separated by a colon.
For example,
players = ['baseball':'Albert Pujols', 'golf':'Tiger Woods'] println players['golf'] // prints Tiger Woods println players.golf // prints Tiger Woods for (player in players) { println "${player.value} plays ${player.key}" } // This has the same result as the previous loop. players.each {player | println "${player.value} plays ${player.key}" }
Empty maps can be created with [:]
. For example,
players = [:]
The Groovy switch
statement takes any kind of object
including Class, List, Range and Pattern.
case
statements compare values
using the isCase
method.
Many overloaded versions of isCase
are provided.
Unless overloaded for specific types, isCase
uses the equals
method.
When a case
is followed by a class name,
isCase
uses instanceof
.
The isCase
method can be overrriden in your own classes.
Here's an example of a switch statement that operates on many different types of values.
switch (x) { case 'Mark': println "got my name" break case 3..7: println 'got a number in the range 3 to 7 inclusive' break case ['Moe', 'Larry', 'Curly']: println 'got a Stooge name' break case java.util.Date: println 'got a Date object' break case ~"\\d{5}": println 'got a zip code' break default: println "got unexpected value ${x}" }
Ranges are created by using the ".." and "..." operators. Here are some examples.
3..7
creates a range from 3 to 7
3...7
creates a range from 3 to 6
"A".."D"
creates a range from "A" to "D"
"A"..."D"
creates a range from "A" to "C"
Ranges are represented by objects from classes that
implement the groovy.lang.Range
interface
which extends java.util.AbstractList
.
A Range is an immutable List.
The Range interface adds getFrom
and getTo
methods to get lower and upper values.
Two implementations of the Range interface are provided.
groovy.lang.IntRange
is used when bounds are integers.
It adds a contains
method to test
whether a value is in the range.
groovy.lang.ObjectRange
is used when bounds are any other type.
It also adds a contains
method, but it is only useful
when the objects used for the bounds
implement java.lang.Comparable
.
Ranges are useful in loops. See the examples in the next section.
Here are six ways to loop through a range of values.
for (i in 1..1000) { println i }
i = 1 while (i <= 1000) { println i; i++ }
(1..1000).each { println it }
1000.times { println it } // values go from 0 to 999
1.upto(1000) { println it }
1.step(1001, 1) { println it } // values go from 1 to 1000; // stopping one before the parameter value
Several List, Map and String methods accept a closure as a parameter.
This iterates through collection items or string characters.
It is an alternative to using java.util.Iterator
that results in more compact code.
For example, to print each number in a List
[5, 9, 1, 6].each {x | println x}
or
[5, 9, 1, 6].each {println it}
This transforms a collection or string into a new one.
For example, to double each number in a List and create a new List
doubles = [5, 9, 1, 6].collect {x | x * 2}
This sets doubles
to [10, 18, 2, 12]
.
This finds the first occurrence of a collection item or string character
that meets some criteria.
For example, to find the first number in a list that is greater than 5
[5, 9, 1, 6].find {x | x > 5}
returns 9
.
This finds all occurrences of a collection item or string character
that meet some criteria.
For example, to find all the numbers in a list that are greater than 5
[5, 9, 1, 6].findAll {x | x > 5}
returns [9, 6]
.
This determines whether every collection item or string character
meets some criteria.
For example, to determine whether all the numbers in a List
are less than 7
[5, 9, 1, 6].every {x | x < 7}
returns false
.
This determines whether any collection item or string character
meets some criteria.
For example, to determine whether any of the numbers in a List
are less than 7
[5, 9, 1, 6].any {x | x < 7}
returns true
.
This passes a value into the first iteration and passes the result of each iteration into the next one. For example, to find 5 factorial (in an unusual way)
factorial = [2, 3, 4, 5].inject(1) { prevResult, x | prevResult * x }
This closure is executed four times.
1) 1 * 2
2) 2 * 3
3) 6 * 4
4) 24 * 5
It returns 120
.
The ellipses (...) in the code examples below indicate omitted code.
file = new File('myFile.txt') file.eachLine { println it } lineList = file.readLines()
file = new File('myFile.txt') file.eachByte { println it } byteList = file.readBytes()
dir = new File('directory-path') dir.eachFile { file | . . . }
These methods of reading from a Reader or InputStream insure that it gets closed regardless of whether an exception is thrown.
file.withReader { reader | . . . } reader.withReader { reader | . . . } inputStream.withStream { is | . . . }
These methods of writing to a Writer or OutputStream insure that it gets closed regardless of whether an exception is thrown.
file.withWriter { writer | . . . } file.withPrintWriter { pw | . . . } file.withOutputStream { os | . . . } writer.withWriter { writer | . . . } outputStream.withStream { os | . . . }
To append strings
s = 'foo' s = s << 'bar'
To append to a StringBuffer
sb = new StringBuffer('foo') sb << 'bar'
To add to lists
colors = ['red', 'green'] colors << 'blue'
To write to the end of streams
w = new File('myFile.txt').newWriter()gipoco.com is neither affiliated with the authors of this page or responsible
for its contents. This is a safe-cache copy of the original web site.gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.