left-icon

Groovy Succinctly®
by Duncan Dickinson

Previous
Chapter

of
A
A
A

CHAPTER 2

Language Essentials

Language Essentials


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.

Comments

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:

  • Line comments start with two forward slashes (//) followed by a very brief piece of text. They can appear on a line by themselves or at the end of a statement.
  • Block comments open with a forward slash and an asterisk (/*) and are closed by the reverse combination (*/). These comments usually span multiple lines, and it is customary to start each line with an asterisk (*) that lines up with the opening and closing asterisks.

Code Listing 2: Comments provides examples of line and block comments.

Code Listing 2: Comments

//This is a line comment

def answer = 1 + 1 //This is another line comment

/*
* This is a block comment.
* They can be useful for larger bodies of text
*/

Values

Groovy handles a range of data values:

  • Booleans
  • Numbers
  • Strings
  • Lists
  • Ranges
  • Maps

Booleans

A Boolean value can be either true or false.

Numbers

There are two primary types of number in Groovy:

  • Integers (whole numbers): 1, 83, 401, -56, -1000
  • Decimal numbers: 3.14, -273.15

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

'''\
groovy makes it easy
less boilerplate to create mess
solve the problem quick
'''

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 (,):

  • An empty list: []
  • A list of numbers: [ 0, 4, 3, 7, 2, 4, 8 ]
  • A list of strings: [ 'temperature', 'rainfall', 'humidity' ]

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:

  • Counting up: 0..10
  • Counting down: 10..0

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:

  • An empty map: [:]
  • A basic map: [ day: 'Monday', rainfall: 3.4, maxTemp: 31 ]
  • Using quoted keys: [ 'day': 'Monday', 'rainfall': 3.4, 'maxTemp': 31 ]

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.

Code Listing 4: Map Keys

[ 'Maximum temperature': 32,
  'Minimum temperature': 18 ]

Variables

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
def dailyRainfall
def personalDetails

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'
def groovyHaiku = """
run $language on jvm
a multiplatform solution
for fun and profit
"""
println groovyHaiku

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"
assert "Area: ${Math.PI * 3**2}" == "Area: 28.274333882308138"

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 ]
assert dailyRainfall[4] == 2
assert dailyRainfall[-2] == 4

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,
                      tuesday  : 5,
                      wednesday: 19,
                      thursday : 6,
                      friday   : 11,
                      saturday : 8,
                      sunday   : 0 ]

assert dailyRainfall.friday == 11

def day = 'friday'
assert dailyRainfall."$day" == 11

Types

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()
def lucky = '13'.toInteger()
pi = '3.14'.toBigDecimal()

def days = [ 'monday', 'tuesday', 'monday' ] as Set
assert days == [ 'monday', 'tuesday' ] as Set

In Code Listing 11: Casting to Another Type, we see this order of actions:

  1. The number 66 is converted to a String.
  2. The string '13' is converted to an Integer (an Integer is a "whole number").
  3. The string '3.14' is converted to a BigDecimal (the BigDecimal type is a type that makes it easy to deal with decimal numbers).
  4. A list of days is cast to a Set (this removes all duplicate list items).

Some of the vast library of types available for use will be discussed in the Classes and objects section.

Assertions

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
assert 10 * 2 == 20
assert 10 ** 2 == 100

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:

  • 1 + '1' mixed a number and a string, so Groovy concatenated the two into a single string ('11').
  • When '11' was compared for equality (==) with 2, the statement was found to be false.

Operators

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. 

Table 1: Numeric Operators

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:

  • Relational operators: used to compare two operands and return a Boolean (true or false) result—e.g., assert 10 > 9 == true.
  • Conditional operators: used to evaluate the "truth" of an expression, returning true if the condition is met or false if it is not.
  • The increment and decrement operators: used with numbers, they return the next or previous value respectively. These operators can be used as a prefix (evaluated before the expression) or as a postfix (evaluated after the expression).

Table 2 provides a list of these commonly used operators.

Table 2: Other 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:

  • If both operands are the same, zero (0) is returned.
  • If the left operand is less than the right operand, negative one (-1) is returned.
  • If the left operand is greater than the right operand, positive one (1) is returned.

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
assert 9 <=> 10 == -1
assert 11 <=> 10 == 1

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'
assert witness

assert 10 > 2 ? 'Ten is greater' :'Two is greater' == 'Ten is greater'

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:

  • Adding an item to a list:
  • [ 2, 4, 6 ] << 8 == [ 2, 4, 6, 8 ]
  • Adding an item to a map:
  • [ day: 'Sunday', rainfall: 8.1 ] << [ maxTemp: 32 ] == [ day: 'Sunday', rainfall: 8.1, maxTemp: 32 ]

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

def str1 = 'hello,'
def str2 = 'world'

assert "$str1 $str2" == "hello, world"

Classes and objects

Everything we interact with in Groovy is an object, so let's sort out a few key concepts:

  • A class describes a structure (type) for use in programs. For example, Integer, String, and BigDecimal are classes provided as part of the Java library and can be used by Groovy programmers.
  • A class declaration includes the data and methods that are available to individual instances of a class.
  • These instances are referred to as "objects," and the act of creating a new object is called "instantiation."
  • An object's data consists of variables that keep track of the object's state and are commonly referred to as "fields" or "properties."
  • An object's methods provide functionality to interact with the object's data.
  • Classes can also provide data and methods that can be accessed directly.
  • These are commonly referred to as "class fields" and "class methods" and are marked with the static modifier.
  • For example, the Math class provides a field called PI which contains a value for the mathematical concept of Pi (𝜋).
  • Class and object fields can be configured as constants to prevent their value from being changed.
  • These are marked with the final modifier.
  • For example, the Math.PI field is marked as final so that we can't decide on a different interpretation of this well-known value.

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
println Math.max(8, 9)

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()
println random.nextInt(100)

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:

  • The Math.max 8, 9 call is the same as Math.max(8, 9).
  • println is actually a method that can be passed a string and is often called without parentheses.
  • A method call with no arguments (such as println()) requires the parentheses.

Code Listing 19: Arguments without Parentheses

def highest = Math.max 8, 9
println 'hello, world'
println()

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:

  • id, name and url properties are declared using def but could be given a specific type (e.g., String).
  • latitude, longitude, and height properties are each declared as BigDecimal.
  • tempReadings is actually defined with def, but this can be omitted when using the final keyword. By using final, I indicate the tempReadings will be set to a list but can't be changed. This means that other code can't change tempReadings to hold another value, but the contents of the list can be changed.

Code Listing 20: A Basic Groovy Class

class WeatherStation {
    def id
    def name, url
    BigDecimal latitude, longitude, height
    final tempReadings = []

    String toString() {
        "$name(#$id): $latitude lat, $longitude long, $height height"
    }

    def addTempReading(temp) {
        tempReadings << temp
    }

    def getAverageTemperature() {
        tempReadings.sum() / tempReadings.size()
    }
}

def davisStation = new WeatherStation(id: 300000, name: 'DAVIS',
        latitude: -68.57, longitude: 77.97, height: 18.0,
        url: 'http://www.bom.gov.au/products/IDT60803/IDT60803.89571.shtml')

davisStation.addTempReading 5
davisStation.addTempReading 3
davisStation.addTempReading 7
davisStation.tempReadings << 2

println davisStation.toString()
println davisStation
println "$davisStation"
println "Station url: ${davisStation.getUrl()}"
println "Average temperature: ${davisStation.averageTemperature}"

WeatherStation also has a number of instance (object) methods:

  • The toString() method customizes the standard method provided on all Groovy objects. It returns a String value (hence String appears before the method name) and has no parameters. This method returns a human-readable description of the object. Groovy methods return the result of the last statement which, in this case, is a Groovy String.
  • The addTempReading(temp) method declares a single parameter (temp) and appends it to the tempReadings list.
  • The getAverageTemperature() method has no parameters but returns the average of the tempReadings list.

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.

Code Listing 21: A Basic Enum

enum ReadingType {
    RAINFALL, TEMPERATURE
}

def readingType = ReadingType.RAINFALL

Closures

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 }
assert square(4) == 16

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 }
assert triple(3) == 9

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 ->
    arg1 > arg2 ? arg1 :arg2
}
assert max(8, 9) == 9

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' }
noArgClosure()

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 ->
    def denominator = 2 * a
    def partialNumerator = Math.sqrt((b**2) - (4 * a * c))
    def answer1 = ((-1 * b) + partialNumerator) / denominator
    def answer2 = ((-1 * b) - partialNumerator) / denominator
    return [ answer1, answer2 ]
}

assert quadratic(1, 3, -4) == [ 1, -4 ]

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 ->
    Math.sqrt((b**2) - (4 * a * c))
}.memoize()

def quadratic2 = { a, b, c ->
    [ ((-1 * b) + quadraticPartialNumerator(a, b, c)) / 2 * a,
      ((-1 * b) - quadraticPartialNumerator(a, b, c)) / 2 * a ]
}

assert quadratic2(1, 3, -4) == [ 1, -4 ]

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.

Closures as arguments

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 ]
def printer = { println it }
dailyRainfall.each printer

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:

  1. The first approach passes the closure block within the method call's parentheses: ({ println it }).
  2. The second approach places the closure block outside the method call's parentheses: (){ println it }. This is useful if the method call takes other arguments.
  3. The final approach is to declare the closure inline. This is the idiomatic approach used in most Groovy code.

Code Listing 29: Passing Parameters

def dailyRainfall = [ 0, 4, 3, 7, 2, 4, 8 ]
dailyRainfall.each ({ println it })
dailyRainfall.each (){ println it }
dailyRainfall.each { println it }

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,
                      tuesday  : 5,
                      wednesday: 19,
                      thursday : 6,
                      friday   : 11 ]

dailyRainfall.each { key, value ->
    println "$key had ${value}mm of rain"
}

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.

Summary

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.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.