CHAPTER 2
In order to get working in Groovy, you need to learn only a surprisingly small subset of the language. This section will outline all of the Groovy syntax you’ll need in order to work through the examples in the following sections. Use this chapter to get a feel for the language and pick up enough syntax to get started.
While they don’t actually direct Groovy to do anything, comments are extremely useful in helping explain your code to others. Groovy supports two main types of comments:
Code Listing 2: Comments provides examples of line and block comments.
//This is a line comment |
Groovy handles a range of data values:
Booleans
A Boolean value can be either true or false.
Numbers
There are two primary types of number in Groovy:
Note: A handy option for large numbers is to use an underscore (_) to help break up the segments: 1_000_000.
Strings
A string is a piece of text surrounded by single quotes ('...'). We used a basic string earlier when we typed 'hello, world'. If we need to use a single quote in a string, it must be delimited using the backslash (\), e.g. 'It\'s Jim here'.
For longer strings we use three single quotes (''') to open and close the text.
Code Listing 3: Longer Strings
'''\ |
Note: The backslash in the top line of the haiku indicates that the first line continues into the second, avoiding a blank line at the top of the string.
Groovy also provides a special type of string, called a Groovy String, surrounded by double quotes ("...") for small strings and three double quotes ("""...""") for a multiline string. The Groovy String is used for interpolation of variables—we'll look at this shortly.
Lists
A list holds zero or more values. A list value is surrounded by square brackets ([ ]), and each item is separated by a comma (,):
Groovy is a relaxed language and doesn't demand that all of the list elements be of a similar kind. This means that you can mix different types of items in a list: numbers, booleans, strings, etc.
Lists are said to be zero indexed because the first item in the list has an index of 0. In the list [ 2, 4, 8 ], the number 2 is at index 0, and the number 4 is at index 1.
Ranges
Similar to lists, ranges define a series of numbers. A range is defined by providing a starting number, two full stops (..), and a final number. This format allows a range to increment or decrement:
Essentially, ranges are a quick way to prepare a list without explicitly providing each value. This is illustrated in a basic assertion: assert 1..3 == [ 1, 2, 3 ]
Maps
A map is a structure that consists of key-value pairs. Similar to a list, a map is surrounded by square brackets ([ ]), and each item is separated by a comma (,). Each item in the map is a key-value pair and is declared with the key first, followed by a colon (:), then the value. A map's keys are unique and are usually strings.
Here are some sample map values:
As demonstrated in the basic map example, the key doesn't have to be quoted, provided the key isn't one of Groovy's reserved keywords; this helps readability. However, this doesn’t work for strings that include elements such as punctuation, and you'll need to use quoted strings, as demonstrated in Code Listing 4: Map Keys.
[ 'Maximum temperature': 32, |
Variables are used to hold values for use through a program. In order to declare a variable, the def keyword is followed by the name of the variable. Code Listing 5: Declaring Variables provides a series of variable declarations.
Code Listing 5: Declaring Variables
def greeting |
Tip: You'll note that I've used an approach to formatting variable names in which the first word is in lowercase but any subsequent words start with an uppercase letter—this is called lower camel case.
A variable will start off with a value on many occasions, and we do this in the declaration by using the assignment operator (=). This is especially useful in the case of lists and maps because the empty form of each ([ ] and [ : ]) advises Groovy how we intend to use the variable. Code Listing 6: Assigning Values at Variable Declaration provides a number of examples.
Code Listing 6: Assigning Values at Variable Declaration
def greeting = 'hello, world' def pi = 3.14 def honest = true def emptyList = [ ] def dailyRainfall = [ 0, 4, 3, 7, 2, 4, 8 ] def emptyMap = [ : ] def weatherDetails = [ day: 'Monday', rainfall: 3.4, maxTemp: 31 ] def countdown = 10..0 |
Note: In this book I'm primarily using Groovy's ability to handle variables in a dynamic fashion. However, Groovy also lets me be explicit about variable types.
Groovy Strings
As we saw in the section on Values, a Groovy String is surrounded with double-quotes ("...") and provides variable interpolation. This means that we can use variable names, prefixed with the dollar ($) symbol, in the string, and Groovy will substitute in the variable's value. Code Listing 7: Groovy String in Action demonstrates this using a multiline Groovy String.
Code Listing 7: Groovy String in Action
def language = 'Groovy' |
Groovy String interpolation allows for more complex expressions by enclosing the expression in the dollar symbol and curly braces (${ }), as we can see in Code Listing 8: Expressions in Groovy Strings.
Code Listing 8: Expressions in Groovy Strings
assert "Total score: ${10 + 9 + 10 + 8}" == "Total score: 37" |
Note: I'll describe the assert statement shortly—it's just a check to make sure a statement is producing (returning) the correct value.
Lists
Once a list has been declared and items added, we can use index notation to access list items. As demonstrated in Code Listing 9: Accessing List Items, the desired index is placed within square brackets ([ ]). Remember that lists are zero-indexed so index 4 is actually the fifth list item. As a handy extra, Groovy lets you use a negative index to access items for the end of the list—e.g., an index of -2 indicates the second-to-last item in the list.
Code Listing 9: Accessing List Items
def dailyRainfall = [ 0, 4, 3, 7, 2, 4, 8 ] |
Maps
Values stored in a map are accessed using dot-point notation such as dailyRainfall.Friday, which returns the value stored against the friday key in the dailyRainfall map. Groovy Strings is particularly useful when providing a key because we can resolve the value of a variable that holds the key (e.g., dailyRainfall."$day"). Code Listing 10: Accessing Map Items demonstrates these approaches as well as using a multiline declaration for the map, a readable approach to presenting the key-value pairs.
Code Listing 10: Accessing Map Items
def dailyRainfall = [ monday : 7, |
Let’s avoid going into too much detail on variable types —we can achieve a lot with only a cursory understanding. However, there are occasions in which we need to manipulate a value's type so that the variable stores the value correctly. We often encounter this when we read text from a file—all of the input comes to us as a string, and we may need to tell Groovy to change the value to a number. This is achieved through a process known as "casting," and in Groovy we use the as keyword followed by the required type.
Groovy also provides a range of default methods that let us convert to a specific type. These methods are named using the "to" prefix followed by the type name. For example: toString(), toInteger(), and toBigDecimal(). These methods are very useful when we convert between number types and between strings and numbers.
Code Listing 11: Casting to Another Type
def highway = 66.toString() |
In Code Listing 11: Casting to Another Type, we see this order of actions:
Some of the vast library of types available for use will be discussed in the Classes and objects section.
An assertion checks that a statement is true. If the statement is found to be false, Groovy halts processing and displays an error. This is helpful for the code examples in this e-book because it helps illustrate an expected outcome. You should be careful with asserts in a Groovy script or application—they will cause your program to halt, but they are very useful in checking your program. Code Listing 12: Some Correct Assertions provides a few basic uses of assert.
Code Listing 12: Some Correct Assertions
assert 1 + 1 == 2 |
To set up an assertion, the assert keyword is followed by a statement that evaluates to a Boolean (true or false) result. For example, if I run assert 1 + '1' == 2, my code will fail and Groovy will display the error shown in Code Listing 13: A Failed Assertion Example.
Code Listing 3: A Failed Assertion Example
Assertion failed: assert 1 + '1' == 2 | | 11 false |
The output of the assertion error is very useful, and we can see that 1 + '1' returns 11 and not 2. Groovy illustrates what went wrong:
We just saw that the equality operator (==) is used to compare two values and return true when they are equivalent. The inequality operator (!=) provides the opposite functionality and returns true when the two values are different.
The numeric operators are known by most school students and are listed in Table 1.
Group | Operators | Examples |
|---|---|---|
Additive operators | Plus (+), minus (-) | 1 + 2 == 3 100 – 9 == 91 |
Multiplicative operators | Multiply (*), divide (/), modulo (%) | 10 * 2 == 20 10 / 2 == 5 10 % 3 == 1 |
Power operator | ** | 10 ** 2 == 100 |
Note: Groovy supports operator overloading, which allows you to define how the various operators work. This is useful for your own custom classes, but it can also modify existing operator implementations.
A number of other operators are available, including:
Table 2 provides a list of these commonly used operators.
Group | Operators | Examples |
Relational | Greater than (>) Less than (<) Greater than or equal to (>=) Less than or equal to (<=) Spaceship (<=>) | 10 > 5 5 < 10 9 >= 9 8 <= 11 |
Conditional | And (&&) Or (||) Ternary (? :) | true && true == true true || false == true |
Increment & Decrement | Increment (++) Decrement (--) | 10++ ++10 8-- |
Several of the operators mentioned in the table above need more context. First, the spaceship operator compares two operands and returns a result as follows:
This seems a little obtuse, even when seen in action in Code Listing 14: Results from the Spaceship Operator. For the most part, the spaceship operator is used when performing a sort—something we'll come across in the Chapter 4 Data Streams chapter.
Code Listing 14: Results from the Spaceship Operator
assert 10 <=> 10 == 0 |
The next item of interest is the ternary (or conditional) operator (? :), so named because it has three operands (op1 ? op2 : op3). The first operand (op1) is a boolean condition. If op1 is true, then op2 is returned; otherwise, op3 is returned. Code Listing 15: The Ternary Operator shows two example uses, but we'll also explore the ternary operator in later chapters.
Code Listing 15: The Ternary Operator
def witness = true ? 'honest' :'liar' |
I didn't list the left shift operator (<<) in Table 2: Other Operators, but it is very useful and adapts nicely to the context in which it is used:
While you can use the left shift operator to concatenate strings, it's often neater to use Groovy String's ability to interpolate variables. Code Listing 16 depicts and example of this.
Code Listing 16: Easy String Concatenation
Everything we interact with in Groovy is an object, so let's sort out a few key concepts:
Note: I'll be straight with you—we're not going to write many of our own classes. We can achieve a lot without taking that path. I've mentioned aspects of syntax here to aid you when looking at the documentation for existing classes.
Let's turn this theory into some real code!
Code Listing 17: Using Class Fields and Methods demonstrates that we use the class name (e.g., Math) followed by a full stop (.), then the name of the field (PI) or method (max) we wish to use. Use of the full stop is similar to what we saw with accessing map keys and is referred to as "dot notation."
Code Listing 17: Using Class Fields and Methods
println Math.PI |
Code Listing 18: Instantiating an Object demonstrates the use of the new keyword to create an instance (object) of the Random class. The new object is then stored in the random variable, and, through that variable, we can access the object's methods (e.g., nextInt).
Code Listing 18: Instantiating an Object
def random = new Random() |
Calls to class and object methods can require arguments and can return a result. For example, Math.max(8, 9) has two arguments (8 and 9) and returns the greater of the two (9). The call to random.nextInt(100) takes one value (100) and returns a random integer between 0 (inclusive) and 100 (exclusive).
Individual arguments are separated by a comma (,). Use of the parentheses enclosing the arguments is optional in Groovy, and Code Listing 19: Arguments without Parentheses demonstrates a few things:
Code Listing 19: Arguments without Parentheses
def highest = Math.max 8, 9 |
A special type of method, referred to as a “constructor,” is called when instantiating a new object. The call to new Random() is a call to the no-argument constructor provided by the Random class. Some classes provide constructors that accept arguments because these help define the object when it's instantiated.
In Code Listing 20: A Basic Groovy Class, I have declared a class that describes a weather station. The class keyword commences the declaration, followed by the class name (WeatherStation). Note the use of upper camel case for the class name. The class definition is enclosed in curly braces ({ }).
The WeatherStation class has a number of properties:
Code Listing 20: A Basic Groovy Class
class WeatherStation { |
WeatherStation also has a number of instance (object) methods:
You'll notice that the def keyword is prefixed to the last two method declarations. This is used in much the same manner as when we declare a dynamic variable and indicate that the method may return a value of any type. Additionally, we need some syntax to indicate that we're declaring a method when we don't want to specify the return type (as opposed to String toString())—def performs that role. You may be wondering how to recognize what a method will return if it is declared with def. This can often be deduced by the method name (e.g., you'd expect getAverageTemperature to return a number) or its documentation. In the case of most nontrivial code, you're likely to explicitly provide the return type of the method.
Note: WeatherStation consists of no class methods or class fields—they're not needed for this example.
The WeatherStation class has now been declared, so we're able to create instances of it. Remember that the new statement is used to create an instance of a class and the new WeatherStation statement creates a new instance to be held by the davisStation variable. Groovy helps us here with classes and generates a constructor for us that takes a set of key/value pairs for the object properties. If this looks like the map notation to you, you're absolutely right—we can pass the constructor a map containing keys with the same name as the object properties and their associated value. Groovy will tidy the syntax for us by removing the need to enclose the map in square brackets ([ ]). You don't need to set all of the properties, only the ones that get you started.
Now that I've instantiated the object, I can add some temperature readings using the addTempReading method: e.g., davisStation.addTempReading 5. You'll notice that I can also append a value using davisStation.tempReadings << 2 and could have, in fact, not bothered to provide an addTempReading method at all!
I can call the toString() method in a few ways. The first method is explicit: println davisStation.toString(). The second call (println davisStation) demonstrates that Groovy determines the println method needs the String version of the object and therefore calls the toString() method. Similarly, the third method demonstrates that interpolating an object in a Groovy String causes the toString() method to be called. This gives you a number of options, and you'll find that each approach is useful in different contexts.
Because I never declared a getUrl() method, the davisStation.getUrl() call may seem a little odd. Groovy provides getters and setters for all of the properties, which avoids the boilerplate getX and setX methods you see in Java code. All of the properties I have declared in WeatherStation are automatically given an associated set and get method. In fact, if I'd accessed the property with davisStation.url, it would have called the getUrl() method. This means that I could explicitly provide the getUrl() method if I want to—perhaps to verify that the URL is correctly formed.
Lastly, accessing davisStation.averageTemperature will look like I'm accessing a property rather than calling a method. This is another useful Groovy trick: methods with a get prefix and no parameters or a set prefix with one parameter can be accessed as if they're properties. Hence, davisStation.averageTemperature will actually call the getAverageTemperature method. This features works not just for our own Groovy methods but for those you'll find in existing Java libraries as well.
The small WeatherStation example demonstrates that Groovy will let us declare classes in a very succinct manner. Furthermore, when accessing objects, we can use a property approach rather than have get and set calls throughout our code.
Enumerations
An enumeration is a special type of class used primarily to provide a set of constants. This is a different concept than Math.PI in that the constants form a set of values. These values may form a series in which one constant follows another, but that's not a requirement. A good example of a series is the Month enumeration that lists the twelve months of the year.
The constants within an enumeration are referred to in the same manner as class fields, e.g., Month.FEBRUARY. Enumeration constants make comparisons such as equality (==) easier because they aren't strings and therefore don't suffer from mismatched case or spelling.
We'll mainly utilize existing enumerations in this book, but Code Listing 21: A Basic Enumeration demonstrates an enumeration with two constants (RAINFALL and TEMPERATURE). You'll note that we don't use the new keyword when assigning an enum constant to the variable—this is because they're more like class constants. You can imagine that the ReadingType enum would be useful when you come across two readings and want to ensure that they're a measurement of the same aspect of the weather.
enum ReadingType { |
Closures are similar to methods, but they aren't bound by a class definition. Groovy's closures are first-class functions because they can be passed as method arguments, returned from methods, and stored in variables. This is a very powerful concept—one we'll make a lot of use of in the coming chapters.
The basic syntax for a Groovy closure is one or more statements enclosed in curly braces ({ }). Code Listing 22: A Basic Closure shows a variable called square set to be a closure that can then be called using square(4). The really interesting thing here is that the closure is more like a value (such as a number or a string), and we can pass the closure around to other closures and methods—more about this shortly.
Code Listing 22: A Basic Closure
def square = { it**2 } |
You may have also noticed the use of a variable named it. By default, closures have a single parameter (it), and the square closure will find the square of the argument passed to it: { it**2 }.
Note: An argument is the value passed to the parameter of a function (i.e. a method or a closure).
As we saw in methods, closures return the result of their last statement. Often provided in a one-line form, this allows closures to be written in a very succinct manner. However, we can also use the return keyword to explicitly designate a value to be returned. We'll see an example of this momentarily.
Tip: Most closures are likely to be made up of only a small number of statements—it's best to keep them small and easy to read.
While a closure has a parameter named it by default, we can also explicitly designate a parameter. In Code Listing 23: A Single-Parameter Closure you'll see an extension to the curly brace syntax with the addition of an "arrow" (->). The parameter list is provided to the left of the arrow, and the body (statements) of the closure is provided to the right. A single parameter (num) is declared in the closure assigned to the triple variable.
Code Listing 23: A Single-Parameter Closure
def triple = { num -> num * 3 } |
Note: When a closure is defined with a parameter list, the default parameter (it) is no longer available.
A closure can have more than one parameter, and Code Listing 24: A Two-Parameter Closure demonstrates the use of the comma (,) to list the parameters. In the case of the max closure, the greater of the two arguments is returned.
Code Listing 24: A Two-Parameter Closure
def max = { arg1, arg2 -> |
Finally, a closure can be declared with no parameters to the left of the arrow so as to define a closure without access to any parameters, including the default it parameter.
Code Listing 25: A Closure with No Parameters
def noArgClosure = { -> println 'hello, world' } |
The previous examples have been short and simple—a sound approach to closures. However, closures can be more complicated, and Code Listing 26: A Quadratic Equation Closure accepts three arguments and returns a list containing the two possible solutions for the quadratic equation. You'll also note the explicit use of the return keyword to designate the value being returned from the closure.
Code Listing 26: A Quadratic Equation Closure
def quadratic = { a, b, c -> |
That implementation of the quadratic equation was rather step-by-step and could be rewritten in a number of ways. In Code Listing 27: A Revised Quadratic-Equation Closure, I've simplified the quadratic closure (now called quadratic2) to simply declaring a list containing the two results from the calculations. I also broke out part of the numerator calculation into its own closure (quadraticPartialNumerator).
Ordinarily, a single call to quadratic2 would cause two calls to quadraticPartialNumerator, but I've appended .memoize() to the latter's declaration. The memoize method[1] creates a cache which holds a list of results for previously submitted parameters. Once a result is calculated, it is stored, and any future call to quadraticPartialNumerator with the same values for a, b, and c will avoid the calculation and take the result from the cache. There's no reason you can’t also memoize quadratic2.
Code Listing 27: A Revised Quadratic-Equation Closure
def quadraticPartialNumerator = { a, b, c -> |
Tip: It's best to present readable (maintainable) code as the initial strategy. Certain efficiency gains will be obvious but don't go overboard too early—you can always profile and refactor your code later if it runs slowly.
Just as numbers and strings can be passed as arguments to methods and closures, Groovy lets you also pass closures as arguments. This is not only useful, but it can really help keep the code succinct.
We’ve seen that arguments can be optionally enclosed in parentheses, but how do we include a closure as a parameter? Groovy gives us a number of approaches, and we'll explore these through a call to the extremely useful each method that's available on lists and maps. The each method is passed as a closure that is called for each item in the list/map.
The first approach to passing a closure parameter is to simply pass in a variable to which a closure has been assigned. For example, in Code Listing 28: Closure Variable as Parameter, the printer variable is passed to the each method, which results in each item in dailyRainfall being displayed to the screen, one item per line.
Code Listing 28: Closure Variable as Parameter
def dailyRainfall = [ 0, 4, 3, 7, 2, 4, 8 ] |
Pre-assigning closures to variables can get annoying, especially if a closure is only to be used once. Luckily, we can provide the closure inline to the method call. Code Listing 29: Passing Parameters illustrates three ways in which the each method can be called:
Code Listing 29: Passing Parameters
def dailyRainfall = [ 0, 4, 3, 7, 2, 4, 8 ] |
When called against a list, the each method passes the supplied closure a single parameter (a list item), and this is accessible inside the closure through the it variable. However, some functions pass multiple parameters to the supplied closure, and we need to provide a closure with declared parameters. For example, when the each method is called against a map, the supplied closure can declare two parameters: one to hold the current map item's key and the other to hold the associated value.
In Code Listing 30: Calling the each Method on a Map, the closure has two parameters (key & value) that help produce an informative display. The parameters can be named in any manner that best describes their use, so the example could have used day and rainfall as parameter names.
Code Listing 30: Calling the each Method on a Map
def dailyRainfall = [ monday : 7, |
Note: You can call the each method on a map without providing a closure with two parameters. In such a case, the it variable will contain the key-value pair.
That was a whirlwind tour of a specific subset of the Groovy language, but it gives us enough information to produce some very useful code. We've seen how to use basic values (numbers, strings, lists, and maps) as well as declare our own types (classes). Groovy gives us optional typing so that we can take the dynamic approach, declaring variables using the def keyword, or by explicitly providing the required type. In fact, we can mix both approaches as we see fit. Closures play a major role in Groovy, and they're first-class citizens in the language, so we can pass them throughout our application as needed.
Now we'll build on this base knowledge by exploring the wealth of existing classes and their functionality. If you want to find out about the rest of the Groovy language, please dip into the Groovy Language Documentation.