II Variables
12. Introduction
Variables are (perhaps unsurprisingly), items that can change. Essentially a variable is a “box” that can hold a value. Groovy is a “dynamic language” in that it allows you to easily store and manipulate variables regardless of their value. This places it in similar company to Python and Ruby but, as a child of Java, Groovy can also operate as a “typed language”. In typed languages we can specify the data type (e.g. a number or piece of text) of the variable.
Groovy lets us work in both language modes - dynamic and typed - and this flexibility makes it that much easier to use.
13. Declaring Variables
Groovy provides a few ways to create a variable but the best one to start with is to use the def keyword. In the example below I define (def) a new variable named score that can be used to hold a value later in my program:
def score
In the next example I assign score a value of 10 and ask Groovy to display the value of score using println:
def score
score = 10
println score
Instead of declaring score and then assigning it the value 10 I can do this on a single line using def score = 10. I do just this in the example below and then change the value of score (it is a variable after all) - try this in your Groovy Console and the printlns will show you the value of score after it’s been set.
def score = 10
println score
score = 11
println score
You’ll note that the second time I use score I don’t need the def prefix as I’ve already declared score and don’t need to redeclare it.
If we’re declaring a number of variables we could provide a def on each line:
def myNumber
def myName
Alternatively, the previous example could be represented on a single line in which each variable is separated by a comma (,):
def myNumber, myName
You can assign values to variables defined on a single line:
def number1 = 10, number2 = 20
A set of variables can be assigned values from a list (multiple assignment):
def number1, number2
(number1, number2) = [10, 20]
assert number1 == 10
assert number2 == 20
In the next example a third variable is introduced but the assignment list only provides two elements. This will result in number1 and number2 being set but number3 remaining without a value (null):
def number1, number2, number3
(number1, number2, number3) = [10, 20]
assert number1 == 10
assert number2 == 20
assert number3 == null
Finally, we can perform multiple assignment at the point of declaring the variables:
def (number1, number2, number3) = [10, 20, 30]
assert number1 == 10
assert number2 == 20
assert number3 == 30
Variable names
Variable names must meet the following criteria:
- Must start with a letter (upper-case [A-Z] or lower-case [a-z])
- The underscore (
_) is also allowed but this is very strongly discouraged - Must only contain letters, digits (0-9) or an underscore (
_)- The dollar-sign (
$) is also allowed but very strongly discouraged
- The dollar-sign (
- Must not match a keyword (reserved word)
The use of literate variable names that comply to the criteria is encouraged. For example, a variable named x provides little information as to its role whereas accountNumber is likely to be clear within the context of a broader system.
Data Types
Data types define the sort of data a variable can hold. Most programming language feature the following data types:
- Booleans
- A logical value of
trueorfalse
- A logical value of
- Characters and strings
- A character is a single letter, number or symbol (e.g.
#) - A piece of text is referred to as a “string”
- A character is a single letter, number or symbol (e.g.
- Numbers
- Integers (whole numbers) both positive and negative
- Decimals (fractional numbers) both positive and negative
- Dates and times
- You know, like dates and times
- Lists and sets
- A variable that holds a number of values (list)
- A variable that holds unique values (set)
- Maps
- A variable that holds a number of values, each referred to by a key
- Ranges
- A numeric sequence between a start and an end value - e.g. 1 to 10
Being an object-oriented programming language, Groovy lets you also define your own types of objects (called classes).
Groovy allows you to create and use variables without declaring a data type - often called dynamic typing. Java, on the other hand, uses static typing and you need to tell Java the data type you want to use when declaring a variable. Once again, Groovy is flexible and lets you use dynamic or static typing (or both) in your programs.
14. Objects
But what is an object? Well, an object is an encapsulation of properties and methods:
-
Properties and Fields are variables that hold data about the object
- For example, a person object may have properties such as
nameandemail - There is a difference between Properties and Fields but we’ll look into that later.
- For example, a person object may have properties such as
-
Methods are a means for accessing and manipulating the object’s properties
- For example a person object may have methods such as
getName()andsetName(name) - Methods can take parameters and/or return values. For example:
getName()would return the person’s name; andsetName(name)takes 1 parameter (name) and sets the person’s name to that value - Methods are sometimes called functions
- For example a person object may have methods such as
We use the class structure to define this assembly of properties and methods.
Declaring and using a class
Let’s look at a Groovy script that declares a new class:
class Person {
def name
def email
def getName() {
return this.name
}
def setName(name) {
this.name = name
}
def getEmail() {
return this.email
}
def setEmail(email) {
this.email = email
}
}
// Create a new variable to hold an instance of the Person class
def david = new Person(name: 'David', email: 'david@example.com')
// Change David's email address:
david.setEmail('dave@example.com')
// Print out David's information
println david.getName()
println david.getEmail()
A class is defined using the class keyword and it’s best practice to use an uppercase letter for the first character: class Person {
We declare the two properties in much the same way as we do for any variable:
def name
def email
A number of methods are declared to let us set and retrieve (get) the values of the object’s properties:
def getName() {
return this.name
}
def setName(name) {
this.name = name
}
def getEmail() {
return this.email
}
def setEmail(email) {
this.email = email
}
After we’ve declared the Person class we can now create instances of the class and assign values to the properties:
def david = new Person(name: 'David', email: 'david@example.com')
We use def david as we would for other variables and then use new Person to indicated that david will hold an instance of the Person class. Lastly we call a special method called a constructor that Groovy provides us for our objects: (name: 'David', email: 'david@example.com'). This sets up david with starting values for the properties.
At some point David changes his email address so we call the setEmail method:
david.setEmail('dave@example.com')
You can see that the method call uses dot-point notation of <variable name>.<method name> - the dot (.) separates the variable name (david) from the method (setEmail).
Lastly, we use the two get methods to display david’s information:
println david.getName()
println david.getEmail()
The example Person class has demonstrated a number of Groovy’s object-oriented programming syntax:
- Creating a new class with properties and methods
- Creating a new instance of the class and calling its constructor
- Changing (setting) and retrieving (getting) the instance’s properties
You can create lots of Person instances and each will exist in their own context. This means that david and sarah don’t get mixed up:
def david = new Person(name: 'David', email: 'david@example.com')
def sarah = new Person(name: 'Sarah', email: 'sarah@example.com')
Useful Methods
In the Groovy/Java family tree, java.lang.Object is the grand-daddy of all classes. Using a system called “inheritance”, each new class inherits attributes such as methods and properties from their forebears. Even the Person class I described above inherits from java.lang.Object and the Groovy developers have enhanced that class further! This means that all classes have built-in features that we can access. Let’s look at a few of them.
class
The class property is used to access the Class that defines the object. This can be really useful when we want to check what sort of object we’re dealing with.
class Person {
def name
def email
}
def david = new Person(name: 'David', email: 'david@example.com')
println david.class.name
dump()
This will return a String that describes the object instance’s internals. Try out the following code to see what gets dumped:
class Person {
def name
def email
}
def david = new Person(name: 'David', email: 'david@example.com')
println david.dump()
with()
This method works with closures (we’ll cover them later) to give us an easy format for accessing a object’s properties in methods. In the example below I wrap some code using with and don’t have to use david.name and david.email to access those properties:
class Person {
def name
def email
}
def david = new Person(name: 'David', email: 'david@example.com')
david.with {
println name
println email
}
Existing classes
The great strength/benefit/bonus of an object-oriented programming platform such as Java is the vast array of existing libraries of objects that you can reuse in your code. In Groovy and Java the listing of these available objects are referred to as the Application Programming Interface (API).
If we were going to create a variable to hold a string (a piece of text) we would do something like:
def quote = 'Well may we say "God save the Queen", because nothing will save the\
Governor-General!'
We could also use the following code to do exactly the same thing as the code above:
def quote = new String('Well may we say "God save the Queen", because nothing wi\
ll save the Governor-General!')
This looks similar to the way we created an instance of the Person class - we create a new instance of String and pass the text into the constructor.
Now that we have our quote string we actually also get a number of methods that help us handle our variable:
def quote = 'Well may we say "God save the Queen", because nothing will save the\
Governor-General!'
//Display the quote in upper case letters
println quote.toUpperCase()
//Display the quote backwards
println quote.reverse()
//Display the number of characters in the quote
println quote.size()
The example above demonstrates how we can call methods on an object instance and you’ll see this used in the rest of the tutorials. Be sure to try out the code above to see what it does!
Classes and literal values
Literal values are best thought of the value you would write down:
- Boolean:
- true
- false
- Numbers:
- 42
- 3.14
- Strings (text):
- ‘hi there’
We can call methods directly on literal values as Groovy will create an appropriate object instance for us:
assert 1.plus(1) == 2
This definitely looks a bit odd but think of it this way:
- Groovy sees the literal value
1followed by a method call - Groovy creates a number object instance for
1 - Groovy then calls the
plusmethod against the new number instance
This can start to be very useful when you look at lists and ranges - something we’ll get to soon.
Lastly, as the literal is put into an object we can access methods and properties for the object. In the example below I can see what data type Groovy is actually using when I use 3.14:
println 3.14.class.name
15. Booleans
Boolean variables are perhaps the simplest and can hold a value of either true or false.
def truth = true
def lies = false
Useful Methods
Booleans have a small number of methods that you generally won’t find yourself using as they (mostly) have equivalent operators that are more “natural” to read and write.
The and(right) method performs a logical ‘and’
def truth = true
def lies = false
assert truth.and(lies) == false
The conditional And operator (&&) is equivalent to the and method and the assertion above could also be written as assert truth && lies == false
The or(right) method performs a logical ‘or’
def truth = true
def lies = false
assert truth.or(lies) == true
The conditional Or operator (||) is equivalent to the or method and the assertion above could also be written as assert truth || lies == true
16. Numbers
There are two main types of numbers you’re likely to need:
- Integers (whole numbers)
- Decimals
Groovy also gives us scientific notation and other number systems and we’ll take a look at how you use them.
Integers
Integers are whole numbers and can be negative or positive:
def age = 27
def coldDay = -8
Groovy will also handle very large numbers:
// 1 astronomical unit (au)
def distanceEarthToSun = 149597870700
def distanceNeptuneToSun = distanceEarthToSun * 30
Decimals
Decimal numbers provide a fraction and can be negative or positive:
def pi = 3.14159
// Measured in celsius
def absoluteZero = -273.15
Scientific notation
Base-10 (decimal) scientific notation (
) can also be used by placing an e or E before the exponent:
def atomicMass = 1.67e-27
The next example sets the au variable to
and then checks to make sure I haven’t messed up the exponent:
def au = 1.49597870700e11
assert au == 149597870700
In the previous two examples you can see a signed (positive or negative) integer as the exponent:
-
e-27is negatively signed -
e11can also be written ase+11and is positively signed
Number Systems
Most of the time we deal with decimal (base-10) numbers but there are other number systems out there. If we want to use the number 15 in base-10 we just type 15 but we can also use:
- Binary (base-2) by prefixing
0b- That’s a zero followed by lower-case “b”
- Octal (base-8) by prefixing
0- That’s just zero
- Hexadecimal (base-16) by prefixing
0x- That’s a zero followed by lower-case “x”
The code below illustrates the many faces of the number 15 (base-10):
println 0b1111 //Binary
println 15 //Decimal
println 017 //Octal
println 0xf //Hexadecimal
To help you deal with long numbers Groovy lets you use underscores (_) to visually break up the number without changing its value:
assert 1_000_000 == 1000000
assert 0b0001_0110_1101 == 365
Let’s close with a joke:
def value = 0b10
println "There are only $value types of people in the world - those who know bin\
ary and those who don't"
Useful Methods and Properties
Groovy (Java) numbers trace their lineage (inherit) back to java.lang.Number. The Number class provides methods to covert between different types of numbers (integer, decimal etc) - we’ll cover this in the chapter on Data Types.
Most numerical classes (e.g. Integer) provide the handy max and min methods that let you compare two numbers of the same numerical type:
assert Integer.max(10, 2) == 10
assert Integer.min(10, 2) == 2
17. Strings
There are two main ways in which you can declare a string in Groovy: single or double quotes
| Method | Usage |
|---|---|
Single quotes ('...') |
These are fixed strings and tell Groovy that the string is as we’ve written it (e.g. def pet = 'dog'). |
Double quotes ("...") |
These are called GStrings and let us interpolate (insert) variables into our string. (e.g. def petDescription = "My pet is a $pet") |
Three single quotes ('''...''') |
A multi-line fixed string |
Three double quotes ("""...""") |
A multi-line GString |
Here’s a quick example of a fixed string and a GString in action:
def pet = 'dog'
def petDescription = "My pet is a $pet"
println petDescription
Escape sequences
Strings can contain escape sequences, allowing you to use non-printable characters in your text.
| Sequence | Character |
|---|---|
| \n | line feed |
| \f | form feed |
| \r | carriage return |
| \t | horizontal tab |
| \’ | single quote |
| \” | double quote |
| \\ | backslash |
The line feed (\n) is often used to move to a new line:
print 'Hi \n there\n'
You’ll notice the use of print in the example above - the final \n performs the same as println and moves to a new line.
The form feed (\f) and carriage return (\r) aren’t often used. Form feed indicates a new page and carriage return goes back to the start of the line.
The horizontal tab (\t) is essentially the same as the tab key on your keyboard. It’s useful for formatting things like tables of information:
println 'name\tage\tcolour'
println 'Sam\t12\tblue'
println 'Alice\t8\tgreen'
If you wish to use a quote within your string that matches the quote type you’re using to surround your string then you need to escape the internal quote using the \ character. In the code below you can see the quotes being escaped (\' and \"):
println 'That\'s mine'
println "I said \"NO!\""
As the backslash (\) is used to escape characters, it needs an escape of its own. In order to use a backslash in a string you need to double it up (\\) as in the example below:
println 'c:\\documents\\report.doc'
GStrings
In order to have Groovy interpolate the value of a variable we use the $ symbol in front of the variable name - as you can see with $pet below:
def pet = 'dog'
println "I own a $pet"
This can be handy if you have a number of variables that you’d like to use in a string:
def name = 'Sally'
def hobby = 'surfing'
println "Did you know that $name likes $hobby?"
GStrings also let you interpolate more complicated expressions into a string by using ${...}. In the following example we perform a calculation within the GString:
println "10 to the power of 6 is ${10**6}"
We can also access information about a variable in the same manner:
def word = 'Supercalifragilisticexpialidocious'
println "$word has ${word.length()} letters"
Multiline Strings
The examples given so far use short strings but longer strings would be cumbersome to type using \n all over the place. Instead, Groovy provides multiline strings - the code below declares a multiline fixed string:
def poem = '''But the man from Snowy River let the pony have his head,
And he swung his stockwhip round and gave a cheer,
And he raced him down the mountain like a torrent down its bed,
While the others stood and watched in very fear.'''
print poem
If you run the code above you’ll see that new lines are used at the correct points in the display but the first line is not quite right. You can modify this slightly and place a backslash (\) at the start of the string - using statement continuation for readability:
def poem = '''\
But the man from Snowy River let the pony have his head,
And he swung his stockwhip round and gave a cheer,
And he raced him down the mountain like a torrent down its bed,
While the others stood and watched in very fear.'''
print poem
GStrings can also be defined using the multiline format:
def animal = 'velociraptor'
println """But the man from Snowy River let the ${animal} have his head,
And he swung his stockwhip round and gave a cheer,
And he raced him down the mountain like a torrent down its bed,
While the others stood and watched in very fear."""
Building Strings
Working with basic strings is fine but if you need to build up a large piece of text throughout a program they can become very inefficient. We’ll look into this in the tutorial on Operators.
Useful Methods
Strings (text) are important aspects to human-based systems so most programming languages provide a number of methods for modifying, search, slicing and dicing strings. Groovy provides a number of helpful methods you can use with strings and we’ll look at just a few of them here:
-
length(): returns the number of characters in a string -
reverse(): returns the mirrored version of the string -
toUpperCase()andtoLowerCase(): returns the string with all of the characters converted to upper or lower case.
def str = 'Hello, World'
println str.length()
println str.reverse()
println str.toUpperCase()
println str.toLowerCase()
The trim() method returns the string with any leading and trailing whitespace removed:
def str = ' Hello, World '
println str.trim()
The substring method returns a subsection of a string and can be used in two possible ways:
- Provide a start index (e.g.
substring(7)) to get the subsection that includes that index (i.e. the 7th character in the string) through to the end of the string - Provide a start and an end index (e.g.
substring(7, 9)) to get the subsection that includes that start index through to the end index of the string
def str = 'Hello, World'
println str.substring(7)
println str.substring(7,9)
A number of methods are provided to help you with basic searching:
- The
indexOfandlastIndexOfmethods return the index (location) of the specified character in the string -
contains,startsWith, andendsWithreturntrueorfalseif the supplied parameter is located within the string
def str = 'Hello, World'
//These methods return the index of the requested character
println str.indexOf(',')
println str.lastIndexOf('o')
//These methods check if the string contains another string
println str.contains('World')
println str.startsWith('He')
println str.endsWith('rld')
The replace method lets us provide a string that we want to change to a new value:
def str = 'Hello, World'
println str.replace('World', 'Fred')
Lastly, and a favourite of mine, is toURL(). This converts a String to a URL object which, in Groovy has a great text
property that lets us load the text of our favourite web page:
println 'http://www.example.com/'.toURL().text
18. Collections
Collections group a number of values in a single container. The Java Collections Framework provides a really extensible and unified approach to handling collections. Groovy makes these even easier to use and focusses on two key collection types:
- Lists: provide a container for several values
- Maps: use keys as a method for indexing a set of values
Lists
List variables contain several items and are declared using square brackets ([...]).
The example below declares a variable (temperatures) as an empty list:
def temperatures = []
The next examples declares the temperatures list with some initial values:
def temperatures = [10, 5, 8, 3, 6]
In the temperatures example the list contains just numbers but Groovy lists can contain a mix of data types:
def mixed = [1, true, 'rabbit', 3.14]
println mixed[2]
println mixed[-3]
println mixed.get(3)
The square brackets [] are used to create a list but are also used to refer to indexes in the list (e.g. mixed[2]) - this is often referred to as subscript notation. In the example above you’ll notice I’ve printed out mixed[2] - the list item with index (subscript) 2. Somewhat confusingly this causes rabbit to be displayed. This is because lists are zero-based and the first item is at index 0, not index 1. Where we use mixed[2] we’re asking for the third item in the list.
It may surprise some programmers that println mixed[-3] is valid - it’s a very handy approach to accessing list items from the end of the list. Item -1 is the last in the list so mixed[-3] will be the value true.
The get() method can also be used to access a list element by its index - e.g. mixed.get(3) gives us 3.14.
I can provide multiple indexes in the subscript notation and grab the specified elements from the list. In the example below I grab elements 0 and 2 (temperatures[0, 2]) and then elements 1, 3 and 4 (temperatures[1, 3, 4]):
def temperatures = [10, 5, 8, 3, 6]
assert temperatures[0, 2] == [10, 8]
assert temperatures[1, 3, 4] == [5, 3, 6]
Ranges can also be used in the subscript notation and, as demonstrated in the example below, return a list containing the items whose indexes are included in the range:
def temperatures = [10, 5, 8, 3, 6]
assert temperatures[1..3] == [5, 8, 3]
We can also use a mix of individual indexes and ranges as we see fit:
def temperatures = [10, 5, 8, 3, 6]
assert temperatures[0..1, 3] == [10, 5, 3]
assert temperatures[0..1, 1..3] == [10, 5, 5, 8, 3]
assert temperatures[0..1, 1..3, 4] == [10, 5, 5, 8, 3, 6]
What? Let’s take a look:
-
temperatures[0..1, 3]returns a list containing the elements oftemperatureswith the indexes 0, 1 and 3 -
temperatures[0..1, 1..3]returns a list using two ranges to select the indexes. As index item1is requested twice, the returned list features the item (5) twice. -
temperatures[0..1, 1..3, 4]does the same as the previous statement but adds in the item at index4
Adding elements
To add an element to a list we use the add() method or the << operator:
def mixed = [1, true, 'rabbit', 3.14]
mixed << 'biscuit'
mixed.add(101)
println mixed
Sets
Sets are much like lists but each element in a set is unique:
def names = ['sally', 'bob', 'sally', 'jane'] as Set
println names
If you try the code above you’ll get [sally, bob, jane] - the set just ignores the repeated element.
Useful List Methods
The size() method returns the number of elements in the list:
def periodic = ['hydrogen', 'helium', 'lithium']
println periodic.size()
The first() and last() methods return the first and last elements in a list. The head() method is synonymous with first().
def periodic = ['hydrogen', 'helium', 'lithium']
println periodic.first()
println periodic.head()
println periodic.last()
The tail() method returns the list minus the first (head) element and the init() method returns the list minus the last element:
def periodic = ['hydrogen', 'helium', 'lithium']
assert periodic.tail() == ['helium', 'lithium']
assert periodic.init() == ['hydrogen', 'helium']
The contains() method returns true if the requested element is contained in the list:
def periodic = ['hydrogen', 'helium', 'lithium']
assert periodic.contains('helium') == true
The reverse() method returns the mirror of the list:
def periodic = ['hydrogen', 'helium', 'lithium']
println periodic.reverse()
The sort() will sort the elements in a “natural” order. Basically, this relies on the list elements being comparable in some manner. The sort method is best used when the list contents are all of the same type (e.g. strings or numbers):
def periodic = ['hydrogen', 'helium', 'lithium']
periodic.sort()
The asImmutable() method is a handy way to set the list contents in stone - “Immutable” essentially means “unchanging”.
def friends = ['fred', 'sally', 'akbar'].asImmutable()
//This next line will cause an exception:
friends << 'jake'
Maps
Maps allow us to build up a type of look-up table using keys and values. Other languages call these dictionaries or associated arrays.
An empty map is declared using [:] and the example below shows this in use when declaring the periodic variable.
def periodic = [:]
Each key in a map is unique and points to a value in the map. In the example below we see the start of a basic periodic table by declaring a variable (periodic) with a set of key-value pairs (key: value) each separated by a comma (,) and held within square brackets ([...]):
def periodic = ['h': 'hydrogen',
'he': 'helium',
'li': 'lithium']
println periodic['li']
println periodic.li
println periodic.get('li')
You should also note that we can access map items using:
- The key in square brackets (
[])- Much as we did with lists:
println periodic['li']. - This is often referred to as subscript notation.
- Much as we did with lists:
- We can also use the period (
.) followed by the key:- As in
println periodic.li. - This is often referred to as dot-point notation
- As in
- Lastly, the
get()method is passed a key and returns the associated value
The keys in a map can be names provided they adhere to the same rules we follow for variable names. That means that the keys in periodic don’t have to be written as strings:
def periodic = [h: 'hydrogen',
he: 'helium',
li: 'lithium']
Adding elements
To add an element to a map we can use the square bracket, dot-point notation, << operator, or put() method to add on a new key/value pair:
def periodic = ['h': 'hydrogen',
'he': 'helium',
'li': 'lithium']
periodic['be'] = 'Beryllium'
periodic.b = 'Boron'
periodic << ['c': 'Carbon']
periodic.put('n', 'Nitrogen')
println periodic
Keys
Map keys don’t have to be strings - they can be a mix of strings, numbers or other objects. Let’s look at an example then go through the various bits of code:
class Chicken {
def name
String toString() {
return "I am $name".toString()
}
}
def cluckers = new Chicken(name: 'Cluckers')
def mixedMap = [
12 : 'Eggs in a carton',
'chicken' : 'Egg producer',
(cluckers): 'Head chicken'
]
println mixedMap[12]
println mixedMap.get(12)
println mixedMap.chicken
println mixedMap['chicken']
println mixedMap.get('chicken')
println mixedMap[(cluckers)]
println mixedMap.get(cluckers)
println mixedMap
In the example above:
- I create a new class (
Chicken)- … and store a new instance of
Chickenin the variablecluckers
- … and store a new instance of
- I then create a map variable called
mixedMapwith different types of keys:-
12is a number -
'chicken'is a string -
(cluckers)indicates that the key is a variable value
-
- I use the square-bracket notation and
getmethod to access the value aligned to the key12-
mixedMap.12won’t work
-
- I use the square-bracket, dot-point and
getmethod to access the value aligned to the key'chicken' - I use the square-bracket notation and
getmethod to access the value aligned to the key(cluckers)mixedMap.cluckers
-
println mixedMapis called to display the map contents
For those interested in such things, the (cluckers) key isn’t affected if I change the value of cluckers later on. If you append the code below to the chicken example you’ll see that mixedMap.get(cluckers) will now return null as the match fails. You’ll also notice that println mixedMap is the same output you get before changing cluckers:
cluckers = new Chicken(name: 'Bill')
println mixedMap.get(cluckers)
println mixedMap
Useful Map Methods
As with lists, the size() methods returns the number of elements in a map.
The get method can be used to get the value for the requested key. A second optional parameter can be provided and is returned if the map does not contain the requested key:
def periodic = ['h': 'hydrogen',
'he': 'helium',
'li': 'lithium']
println periodic.get('he')
println periodic.get('he', 'Unknown element')
println periodic.get('x', 'Unknown element')
The keySet() method returns a list containing all of the keys in a map and values() returns a list of the values in a map:
def periodic = ['h': 'hydrogen',
'he': 'helium',
'li': 'lithium']
println periodic.keySet()
println periodic.values()
The containsKey() and containsValue() methods are useful for checking on map contents:
def periodic = ['h': 'hydrogen',
'he': 'helium',
'li': 'lithium']
println periodic.containsKey('li')
println periodic.containsValue('carbon')
The asImmutable() method works for maps in the same manner as it does for lists:
def periodic = ['h': 'hydrogen',
'he': 'helium',
'li': 'lithium'].asImmutable()
//This will cause an exception:
periodic.x = 'new element'
19. Arrays
For my money, the collections we’ve just looked at (lists, sets, maps) are more versatile than arrays and collections are my preferred approach. However, there’s a lot of code out there using arrays so let’s take a quick look.
Arrays contain a fixed number of elements of a specified data type. Let’s look at an example of array declaration and usage:
Number[] point = new Number[2]
point[0] = 27
point[1] = -153
assert point.length == 2
So let’s dissect that chunk of code:
- The
pointvariable is declared usingNumber[] point = new Number[2]-
Number[]indicates that we want an array of Numbers -
[]indicates that the variable is an array, not just a single Number value -
new Number[2]setspointto be an empty array that can contain two (2) elements of theNumberclass (or a subtype thereof). - Don’t use
defas we’re specifying the data type
-
- Arrays are zero-based, meaning that the first element is at index 0
-
point[0]is the first element -
point[1]is the second
-
-
point.lengthreturns the number of elements in the array- Note that the range of indexes for an array is
0..(point.length - 1) -
point.size()would also work and provides the same result aspoint.length
- Note that the range of indexes for an array is
If I’d tried something like point[2] = 99 I would get a java.lang.ArrayIndexOutOfBoundsException as the array can only hold 2 elements.
It’s important to note that the size of an array is fixed at declaration. If you decide that you need to expand the array then you’ll slap your forehead and ask “Why didn’t I use collections?”. If you dig your heels in and stay with arrays you might check out the java.lang.System.arraycopy method and learn the gentle art of copying and resizing arrays. Then, you’ll start using collections.
We can be more direct in creating the array and provide the values up-front. In the example below I create an array that can hold two elements and I load the values into the array:
Number[] point = [27, -153]
So, why did I pick Number? Well, I want an array of numerical values but perhaps wasn’t sure which type of numbers. Provided the values I put into the array are subtypes of Number, all will be well. That means the following will work fine and nothing will be truncated:
Number[] point = [27.9, -153]
If I really wanted to be specific about the type of number I could have declared point as an array of Integer values:
Integer[] point = [27, -153]
Arrays can also be declared to be of a primitive type such as int1:
int[] point = [27, -153]
Going further with subtypes etc, arrays can be of any type and the Object class provides a flexible type when your array needs to hold a mixture of values (e.g. numbers, strings, various types):
Object[] bag = new Object[4]
bag[0] = true
bag[1] = 'Rabbit'
bag[2] = 3.14
bag[3] = null
Without wanting to be repetitive, the example above would probably be easier to work with if we used a collection such as a list.
Manipulating arrays
We’ve seen the size() method and length property - both indicating how many elements the array can hold.
Sorting an array is easy with the sort() method:
Number[] nums = [99, 10.2, -7, 99.1]
nums.sort()
println nums
Of course sort() works well if the element types have a meaningful sorting order but try out the following code and you’ll see that the sort() perhaps isn’t overly useful on mixed values:
Object[] bag = new Object[4]
bag[0] = true
bag[1] = 'Rabbit'
bag[2] = 3.14
bag[3] = null
println bag.sort()
Use the Arrays.asList() static method to get a copy of an array into a list (collection):
asListNumber[] nums = [99, 10.2, -7, 99.1]
def list = Arrays.asList(nums)
Alternatively, you can use the as operator to cast the array to a List.
asNumber[] nums = [99, 10.2, -7, 99.1]
def list = nums as List
Check out the java.util.Arrays class for more array methods.
- Primitive types are discussed in the Data Types chapter.↩
20. Ranges
Ranges define a starting point and an end point. Let’s look at a well-known type of range:
def countdown = 10..0
println countdown.getFrom()
println countdown.getTo()
The countdown range starts at 10 and goes down to 0. The notation should be easy to decipher: <start>..<end>.
Printing out a range variable will display that a range is rather like a list of values - in the case of countdown they’re numbers:
def countdown = 10..0
println countdown
Whilst my examples so far all go down, you can just as easily have a range that goes up:
def floors = 1..10
println floors
You can also use decimals but note that it is only the integer (whole-number) component that is stepped through:
def countdown = 10.1..1.1
println countdown
Half-Open Ranges
Ranges aren’t just limited to inclusive ranges such as 1..10. You can also declare a half-open range using ..< - that’s two periods and a less-than. This denotes that the range ends prior to the number to the right. In the example below I setup a grading criteria that avoids an overlap between the grades:
def gradeA = 90..100
def gradeB = 80..<90
def gradeC = 65..<80
def gradeD = 50..<65
def gradeF = 0..<50
I could tweak the above code if I want to get fancy:
def gradeA = 90..100
def gradeB = 80..<gradeA.getFrom()
def gradeC = 65..<gradeB.getFrom()
def gradeD = 50..<gradeC.getFrom()
def gradeF = 0..<gradeD.getFrom()
Ranges of Objects
Ranges are primarily used with numbers but they can be of any object type that can be iterated through. This basically means that Groovy needs to know what object comes next in the range - these objects provide a next and previous method to determine this sequence. Over time you’ll discover various options for use in ranges but numbers really are the main type.
Apart from numbers, individual characters (letters) can be used in ranges. In the example below I create a range of lower-case letters:
def alphabet = 'a'..'z'
println alphabet
Ranges and Enums
Ranges can be handy when dealing with enums as they give us the ability to set a subset of enum values. In the example below I create a handy helpdesk tool:
- Setup an
enumlisting the possible ticket priorities - Create a new
classto describe helpdesk tickets - Setup a
helpdeskQueuecontaining a list of tickets - Set the
focusvariable as a range ofPriorityvalues - Go through the list of tickets and pick up any that are set to the
priorityI care about.
enumenum Priority {
LOW,MEDIUM,HIGH,URGENT
}
class Ticket {
def priority
def title
}
def helpdeskQueue = [
new Ticket(priority: Priority.HIGH, title: 'My laptop is on fire'),
new Ticket(priority: Priority.LOW, title: 'Where is the any key'),
new Ticket(priority: Priority.URGENT, title: 'I am the CEO and I need a coff\
ee'),
new Ticket(priority: Priority.MEDIUM, title: 'I forgot my password')
]
def focus = Priority.HIGH..Priority.URGENT
for (ticket in helpdeskQueue) {
if (ticket.priority in focus) {
println "You need to see to: ${ticket.title}"
}
}
Try the example above out with various settings for the focus variable:
-
def focus = Priority.MEDIUM..Priority.URGENT- Gives us more tickets to see to :(
-
def focus = Priority.HIGH..Priority.LOW- Is actually similar to
4..1and leaves out the tickets markedURGENT
- Is actually similar to
Ranges and List Indexes
You can access a subset of a list using a range subscript. In the example below I use the subscript [1..3] to grab a new list containing elements 1 through 3 of the temperatures list.
def temperatures = [10, 5, 8, 3, 6]
def subTemp = temperatures[1..3]
assert subTemp == [5, 8, 3]
Ranges and Loops
Ranges are most often see when we’re using loops - we’ll get to them in a later tutorial but here’s an example of a launch sequence:
def countdown = 10..0
for (i in countdown) {
println "T minus $i and counting"
}
In the above example I store the range in the countdown variable in case I need it again later. If I don’t really need to re-use the range I can put the range’s literal value directly into the loop:
for (i in 10..1) {
println "T minus $i and counting"
}
Useful Methods
We can use the size() method to find out how many elements are in the range:
def dalmations = 1..101
println dalmations.size()
As seen earlier, the getFrom() and getTo() methods return the start and final values respectively:
def intRange = 1..10
println intRange.getFrom()
println intRange.getTo()
The isReverse() method returns true if a range iterates downwards (backwards):
def countdown = 10..0
assert countdown.isReverse() == true
You can can use the reverse() method to flip the range:
def floors = 1..10
println floors.reverse()
In order to check if a value is contained within a range we use the containsWithinBounds method and pass it the value we’re checking on:
def countdown = 10..0
assert countdown.containsWithinBounds(5) == true
The step method returns a list based on going through the range via the specified increment (step). In the example below I step through the range one at a time (step(1)) and then two at a time (step(2)):
def countdown = 5..1
assert countdown.step(1) == [5, 4, 3, 2, 1]
assert countdown.step(2) == [5, 3, 1]
As step returns a list I can use it to populate a list variable that has too many numbers for me to be bothered typing out:
def dalmations = (1..101).step(1)
println dalmations
As we’re about to see the step method is very effective when used with closures.
Ranges and Closures
Closures are a method (function) that can be handled in a manner similar to variables. A closure is described within curly brackets {..} and can be passed as method parameters. Closure have a default variable named it and this holds a value passed to the closure by its caller.
We’ll look into closures much more thoroughly in a later tutorial but, for now, take in the following examples and refer back to them when you get to know closures a little better.
The step method will call a closure for each item in a range. In the example below I step through countdown one number at a time and, for each number, I display a message:
def countdown = 10..1
countdown.step(1) {
println "T minus $it and counting"
}
I can use the range literal but need to place it within (..):
(10..1).step(1) {
println "T minus $it and counting"
}
You can change the size of each step - in the case below I step down by 2 each time. Run the code and notice that launch never happens!
(10..1).step(2) {
println "T minus $it and counting"
}
21. Regular Expressions
Regular expressions (RegEx’s) get entire books devoted to them and you’ll find some developers are RegEx ninjas and others (like myself) are RegEx numpties. This chapter will introduce the basics but the Java Tutorial’s Regular Expression trail is a useful reference as is Wikipedia for those seeking RegEx glory. There are also a number of online tools such as RegExr that come in very handy when trying to debug that elusive RegEx pattern.
To define the regular expression pattern we use the ~/ / syntax:
def regex = ~/\n/
Once stored as a variable, this regular expression can be used in a variety of ways. The example below sets up three string variables and tests them against the regex pattern by using the matches method - which returns true if the string matches the pattern:
def regex = ~/https?:\/\/.*/
def httpUrl = 'http://www.example.com/'
def httpsUrl = 'https://secure.example.com/'
def ftpUrl = 'ftp://ftp.example.com/'
assert httpUrl.matches(regex)
assert httpsUrl.matches(regex)
assert ! ftpUrl.matches(regex)
In the code above, ~/https?:\/\/.*/ is the regular expression pattern that’s essentially looking for any string starting with http or https. The s? will match 0 or 1 occurrence of s in the pattern. You’ll notice the odd-looking \/\/ - I need to escape the forward slashes in http:// so that Groovy doesn’t confuse them with the slashes used to define the regular expression pattern (~/../).
We’ll also look at the special operators for regular expressions in the section on Operators.
Underpinning Groovy’s regular expression functionality is the Java class java.util.regex.Pattern. Groovy handles the compiling of the pattern and this helps you focus on the struggle of getting the regular expression correct :)
Regular Expression Syntax
Regular expressions use a number of syntactic elements to define a pattern of text. We’ll take a brief look at them here.
Characters
These elements are used to match specific literal characters.
| Element | Matches |
|---|---|
g |
The character g
|
\\ |
The backslash character |
\t |
Tab character |
\n |
Newline character |
\f |
Formfeed character |
\r |
Carriage-return character |
In the example below I take a section of a poem and use the split method to get a list whose elements contain a single line from the poem.
// The Ballad of the Drover by Henry Lawson
def poem = '''\
Across the stony ridges,
Across the rolling plain,
Young Harry Dale, the drover,
Comes riding home again.
And well his stock-horse bears him,
And light of heart is he,
And stoutly his old pack-horse
Is trotting by his knee.'''
def regex = ~/\n/
def lines = regex.split(poem)
def i = 1
for (line in lines) {
println "Line $i: $line"
i++
}
Character Classes
Character classes are used to define character sets and sequences.
| Element | Matches |
|---|---|
[xyz] |
x, y or z
|
[^xyz] |
Not x, y or z
|
[a-zA-Z] |
Range of characters (all letters) |
[0-9] |
Range of characters (all numbers) |
[a-zA-Z_0-9] |
Range of characters |
Predefined Character Classes
The predefined character classes save you from having to define the class specifically and are handy for seeking out words and whitespace.
| Element | Matches |
|---|---|
. |
Any character |
\d |
Digits [0-9]
|
\D |
Non-digits |
\s |
Whitespace |
\S |
Not whitespace |
\w |
Word character [a-zA-Z_0-9]
|
\W |
Not a word character |
Boundaries
Boundaries, to state the obvious, mark the edge of something - specifically a line or a word.
| Element | Matches |
|---|---|
^ |
Start of a line |
$ |
End of a line |
\b |
Word boundary |
\B |
Non-word boundary |
Quantifiers
These determine how many matches are acceptable. For example s? matches the character s zero or one time - meaning that I expect that character to be an s or, if it’s not, move to the next part of the pattern. s+ means that I really want at least one s at that point.
| Element | Matches |
|---|---|
? |
Single match |
* |
Zero or more matches |
+ |
One or more matches |
{n}? |
Exactly n matches |
{n, }? |
At least n matches |
{n,m}? |
At least n but not more that m matches |
Useful Methods
A number of String methods can accept a regular expression and these are my preferred approach to checking text against regular expressions. Most of them take the pattern as the first parameter.
We saw the matches() method at the beginning of the chapter:
def regex = ~/https?:\/\/.*/
def httpUrl = 'http://www.example.com/'
assert httpUrl.matches(regex)
The find() method returns the first match against the pattern within the string. In the example below the find() will return the match against the port number in the URL:
def regex = ~/:[0-9]+/
def httpUrl = 'http://www.example.com:8080/'
println httpUrl.find(regex)
The findAll() method returns a list of matches for the pattern. In the example below I am returned all words in speech that start with like:
findAlldef speech = '''This like guy like I know but like don\'t really like
was like so mean but likely to be nice when you know him better.'''
println speech.findAll(~/\blike\w*\b/)
Like, wow!
The example below provides a very basic word counter by seeking out the \b\w+\b pattern and displaying the size of the list returned by findAll:
def poem = '''\
Across the stony ridges,
Across the rolling plain,
Young Harry Dale, the drover,
Comes riding home again.'''
def regex = ~/\b\w+\b/
println poem.findAll(regex).size()
The replaceFirst() and replaceAll() methods seek out matches and replace them in a manner that their names implies:
def speech = '''This like guy like I know but like don\'t really like
was like so mean but likely to be a nice guy when you know him better.'''
println speech.replaceAll(~/\blike\b/, 'um')
println speech.replaceFirst(~/\bguy\b/, 'marmoset')
The splitEachLine() method is very handy when handling structured files such as comma-separated files. You can see in the example below that the first parameter is the pattern that will match commas (~/,/) and the second parameter is a closure that will do something for each line. Within the closure, the it variable is a list with each element being the delimited segment of the text with the line:
def csv = '''\
Bill,555-1234,cats
Jane,555-7485,dogs
Indira,555-0021,birds'''
csv.splitEachLine(~/,/) {
println "Name: ${it[0]}"
}
Pattern Methods
The java.util.regex.Pattern class provides a number of useful methods. I prefer to use the String methods but maybe I’m just lazy.
The static matches method is called against Pattern to evaluate a pattern against a piece of text. You’ll note that the first parameter is the pattern but represented as a string so you drop the ~/../ notation:
Pattern//Note the import
import java.util.regex.Pattern
assert Pattern.matches('https?://.*/', 'http://www.example.com/') == true
The matcher() method is called against a regular expression pattern and is passed the text that is to be checked. A Matcher variable is returned and these give you a whole heap of regular expression functionality. In my example I just check for the match by calling matches():
Matcherdef regex = ~/https?:\/\/.*/
def httpUrl = 'http://www.example.com/'
def matcher = regex.matcher(httpUrl)
assert matcher.matches() == true
The split() method uses a pattern as a delimiter and returns the elements of the parameter broken up by the delimiter. In my example below I split the domain up based on the period (.) delimiter:
def regex = ~/\./
def domain = 'www.example.com'
println regex.split(domain)
That last example is simple but you can use some pretty funky patterns to split up a string.
22. Data types
Groovy does not preclude the programmer from explicitly declaring a data type, particularly when it would be pertinent to constrain the values being managed. Furthermore, knowledge of data types is very useful for a number of reasons:
- Use of JVM-compatible libraries may require knowledge of the data types required by method calls.
- Important if you want to mine the rich collection of existing Java libraries
- Conversion between different data types (such as decimal numbers to whole numbers) can cause truncation and other (perhaps unexpected) results.
- Essential knowledge if your program relies on calculations
Most of Java’s “core” classes (types) are defined in the java.lang package. Groovy enhances some of these in the GDK to give you extra flexibility.
Groovy’s use of types
The table below illustrates Groovy’s selection of a data type based on a literal value:
| Value | Assigned Type |
|---|---|
true |
java.lang.Boolean |
'a' |
java.lang.String |
"This is a String" |
java.lang.String |
"Hello ${Larry}" |
org.codehaus.groovy.runtime.GStringImpl |
127 |
java.lang.Integer |
32767 |
java.lang.Integer |
2147483647 |
java.lang.Integer |
9223372036854775807 |
java.lang.Long |
92233720368547758070 |
java.math.BigInteger |
3.14 |
java.math.BigDecimal |
3.4028235E+38 |
java.math.BigDecimal |
1.7976931348623157E+308 |
java.math.BigDecimal |
It is important to note that the type is selected at each assignment - a variable that is assigned a string such as "Hello" is typed as java.lang.String but changes to java.lang.Integer when later assigned the value 101.
Using a specific type
A variable can be declared as being of a specific data type. When using a type, drop the def keyword:
Integer myNum = 1
String myName = "Fred nurk"
Suffixes can also be used if you want to be really specific about the data type Groovy is to use for a number. When using suffixes you use the def keyword to define the variable: def dozen = 12i
| Suffix | Type | Example |
|---|---|---|
I or i
|
Integer | 12i |
L or l
|
Long | 23423l |
F or f
|
Float | 3.1415f |
D or d
|
Double | 3.1415d |
G or g
|
BigInteger | 1_000_000g |
G or g
|
BigDecimal | 3.1415g |
You may have noticed that BigInteger and BigDecimal have the same suffix - this isn’t a typo - Groovy works out which one you need simply by determining if the number is a whole number (BigInteger) or a decimal (BigDecimal).
If you’re going to use explicit types then you need to know limitations of that type. For example, the following code will fail:
assert 3.1415926535f == 3.1415926535d
This failure occurs because Float will shorten (narrow) the value to 3.1415927 - not a mistake you’d want to make when measuring optics for your space telescope! You can see which type Groovy will use automatically by running this snippet of code:
println 3.1415926535.class.name
The null Value
Variables that are not assigned a value at declaration are provided a null value by default. This is a special reference that indicates the variable is devoid of a value.
Variables can be explicitly assigned null:
def id = null
Available data types
As Groovy imports the java.lang package as well as the java.math.BigDecimal and java.math.BigInteger classes by default, a range of data types are available for immediate use:
-
Boolean: to store a logical value oftrueorfalse - Numbers (based on
java.lang.Number):ByteShortIntegerLongFloatDoubleBigDecimalBigInteger
-
Character: A single character such as a letter or non-printing character -
String: A regular Java-esque piece of text -
GString: A Groovy string that allows for interpolation -
Object: This is the base class for all other classes -
Closure: The class that holds closure values
The types listed above are often referred to as reference types, indicating that they relate to a class definition. Groovy also provides a set of primitive types that are more closely aligned to the C programming language than an object-oriented language such as Java and Groovy.
Primitive types
The table below maps the types defined in java.lang against their equivalent primitive types:
| Type | Primitive type | Value range | Size (bits) |
|---|---|---|---|
| Boolean | boolean |
true or false
|
- |
| Byte | byte |
-128 to 127, inclusive | 8 |
| Short | short |
-32768 to 32767, inclusive | 16 |
| Character | char |
‘\u0000’ to ‘\uffff’ inclusive | 16 |
| Integer | int |
-2147483648 to 2147483647, inclusive | 32 |
| Long | long |
-9223372036854775808 to 9223372036854775807, inclusive | 64 |
| Float | float |
32-bit IEEE 754 floating-point numbers | 32 |
| Double | double |
64-bit IEEE 754 floating-point numbers | 64 |
You can check those value ranges by using the MIN_VALUE and MAX_VALUE constants available on the various classes representing numbers:
println Integer.MIN_VALUE
println Integer.MAX_VALUE
println Float.MIN_VALUE
println Float.MAX_VALUE
As an object-oriented language Groovy also provides a mechanism for declaring new data types (objects) that extend and encapsulate information to meet a range of requirements. These implicitly extend the java.lag.Object class.
Type Conversions
Groovy will convert values assigned to variables into the variable’s declared data type. For example, the code below declares a variable of type “String” and then assigns it 3.14 (a number). The assertion that the variable remains of type “String” will succeed, indicating that 3.14 was converted to a String value by Groovy before being assigned to the myName variable.
String myName = "Fred nurk"
myName = 3.14
assert myName.class == java.lang.String
Care must be taken to not rely totally on this automatic conversion. In the example below the assertion will fail as the myPi variable is declared as an Integer and the assignment drops the fractional component of 3.14:
def pi = 3.14
Integer myPi = pi
assert myPi == pi
Casting
The as operator can be used to cast (change) a value to another class.
def pi = 3.1415926535 as Integer
assert 3 == pi
This will be discussed further in the Object Operators tutorial.
Converting Numbers
java.lang.Number provides a number of methods for converting numbers between the various numerical data types:
byteValue()-
doubleValue()- also
toDouble()
- also
-
floatValue()- also
toFloat()
- also
-
intValue()- also
toInteger()
- also
-
longValue()- also
toLong()
- also
shortValue()toBigInteger()toBigDecimal()
Here’s a small example of grabbing the whole (integer) component from a number:
def pi = 3.1415926535
assert 3 == pi.intValue()
assert 3 == pi.toInteger()