Chapter 3. The simple Groovy datatypes

 

Do not worry about your difficulties in Mathematics. I can assure you mine are still greater.

 
 --Albert Einstein

Groovy supports a limited set of datatypes at the language level; that is, it offers means for literal declaration and specialized operators. This set contains the simple datatypes for strings, regular expressions, and numbers, as well as the collective datatypes for ranges, lists, and maps. This chapter covers the simple datatypes; the next chapter introduces the collective datatypes.

Before we go into details, you’ll learn about Groovy’s general approach to typing. With this in mind, you can appreciate Groovy’s approach of treating everything as an object and all operators as method calls. You will see how this improves the level of object orientation in the language compared to Java’s division between primitive types and reference types.

We then describe the natively supported datatypes individually. By the end of this chapter, you will be able to confidently work with Groovy’s simple datatypes and have a whole new understanding of what happens when you write 1+1.

Objects, objects everywhere

In Groovy, everything is an object. It is, after all, an object-oriented language. Groovy doesn’t have the slight “fudge factor” of Java, which is object-oriented apart from some built-in types. In order to explain the choices made by Groovy’s designers, we’ll first go over some basics of Java’s type system. We will then explain how Groovy addresses the difficulties presented, and finally examine how Groovy and Java can still interoperate with ease due to automatic boxing and unboxing where necessary.

Java’s type system—primitives and references

Java distinguishes between primitive types (such as int, double, char, and byte) and reference types (such as Object and String). There is a fixed set of primitive types, and these are the only types that have value semantics—where the value of a variable of that type is the actual number (or character, or true/false value). You cannot create your own value types in Java.

Reference types (everything apart from primitives) have reference semantics—the value of a variable of that type is only a reference to an object. Readers with a C/C++ background may wish to think of a reference as a pointer—it’s a similar concept. If you change the value of a reference type variable, that has no effect on the object it was previously referring to—you’re just making the variable refer to a different object, or to no object at all.

You cannot call methods on values of primitive types, and you cannot use them where Java expects objects of type java.lang.Object. This is particularly painful when working with collections that cannot handle primitive types, such as java.util.ArrayList. To get around this, Java has a wrapper type for each primitive type—a reference type that stores a value of the primitive type in an object. For example, the wrapper for int is java.lang.Integer.

On the other hand, operators such as * in 3*2 or a*b are not supported for arbitrary reference types, but only for primitive types (with the exception of +, which is also supported for strings). As an example of why this causes pain, let’s consider a situation where you have two lists of integers, and you want to come up with a third list where the first element is the sum of the first elements of the other two lists, and so on. The Java code would be something like this:

// Java code!
ArrayList results = new ArrayList();
for (int i=0; i < listOne.size(); i++)
{
    Integer first  = (Integer)listOne.get(i);
    Integer second = (Integer)listTwo.get(i);

    int sum = first.intValue()+second.intValue();
    results.add (new Integer(sum));
}

New features in Java 5 would make this simpler, but there would still be two types (int and Integer) involved, which adds conceptual complexity. There are good reasons for Java to follow this route: the heritage of C and performance optimization concerns. The Groovy answer puts more burden on the computer and less on the programmer.

Groovy’s answer—everything’s an object

Groovy makes the previous scenario easier in so many ways they’re almost hard to count. However, for the moment we’ll only look at why making everything an object helps to keep the code compact and readable. Looking at the code block in the previous section, you can see that the problem is in the last two lines of the loop. To add the numbers, you must convert them from Integers into ints. In order to then store the result in another list, you have to create a new Integer. Groovy adds the plus method to java.lang.Integer, letting you write this instead:

results.add (first.plus(second))

So far, there’s nothing that couldn’t have been done in Java if the library designers had thought to include a plus method. However, Groovy allows operators to work on objects, enabling the replacement of the last section of the loop body

// Java
int sum = first.intValue()+second.intValue();
results.add (new Integer(sum));

with the more readable Groovy solution[1]

results.add (first + second)

You’ll learn more about what operators are available and how you can specify your own implementations in section 3.3.

In order to make Groovy fully object-oriented, and because at the JVM level Java does not support object-oriented operations such as method calls on primitive types, the Groovy designers decided to do away with primitive types. When Groovy needs to store values that would have used Java’s primitive types, Groovy uses the wrapper classes already provided by the Java platform. Table 3.1 provides a complete list of these wrappers.

Table 3.1. Java’s primitive datatypes and their wrappers

Primitive type

Wrapper type

Description

byte

java.lang.Byte

8-bit signed integer

short

java.lang.Short

16-bit signed integer

int

java.lang.Integer

32-bit signed integer

long

java.lang.Long

64-bit signed integer

float

java.lang.Float

Single-precision (32-bit) floating-point value

double

java.lang.Double

Double-precision (64-bit) floating-point value

char

java.lang.Character

16-bit Unicode character

boolean

java.lang.Boolean

Boolean value (true or false)

Any time you see what looks like a primitive literal value (for example, the number 5, or the Boolean value true) in Groovy source code, that is a reference to an instance of the appropriate wrapper class. For the sake of brevity and familiarity, Groovy allows you to declare variables as if they were primitive type variables. Don’t be fooled—the type used is really the wrapper type. Strings and arrays are not listed in table 3.1 because they are already reference types, not primitive types—no wrapper is needed.

While we have the Java primitives under the microscope, so to speak, it’s worth examining the numeric literal formats that Java and Groovy each use. They are slightly different because Groovy allows instances of java.math.BigDecimal and java.math.BigInteger to be specified using literals in addition to the usual binary floating-point types. Table 3.2 gives examples of each of the literal formats available for numeric types in Groovy.

Table 3.2. Numeric literals in Groovy

Type

Example literals

java.lang.Integer

15, 0x1234ffff

java.lang.Long

100L, 200l[a]

java.lang.Float

1.23f, 4.56F

java.lang.Double

1.23d, 4.56D

java.math.BigInteger

123g, 456G

java.math.BigDecimal

1.23, 4.56, 1.4E4, 2.8e4, 1.23g, 1.23G

[a] The use of the lowercase l as a suffix indicating Long is discouraged, as it can look like a 1 (number one). There is no difference between the uppercase and lowercase versions of any of the suffixes.

Notice how Groovy decides whether to use a BigInteger or a BigDecimal to hold a literal with a “G” suffix depending on the presence or absence of a decimal point. Furthermore, notice how BigDecimal is the default type of non-integer literals—BigDecimal will be used unless you specify a suffix to force the literal to be a Float or a Double.

Interoperating with Java—automatic boxing and unboxing

Converting a primitive value into an instance of a wrapper type is called boxing in Java and other languages that support the same notion. The reverse action—taking an instance of a wrapper and retrieving the primitive value—is called unboxing. Groovy performs these operations automatically for you where necessary. This is primarily the case when you call a Java method from Groovy. This automatic boxing and unboxing is known as autoboxing.

You’ve already seen that Groovy is designed to work well with Java, so what happens when a Java method takes primitive parameters or returns a primitive return type? How can you call that method from Groovy? Consider the existing method in the java.lang.String class: int indexOf (int ch).

You can call this method from Groovy like this:

assert 'ABCDE'.indexOf(67) == 2

From Groovy’s point of view, we’re passing an Integer containing the value 67 (the Unicode value for the letter C), even though the method expects a parameter of primitive type int. Groovy takes care of the unboxing. The method returns a primitive type int that is boxed into an Integer as soon as it enters the world of Groovy. That way, we can compare it to the Integer with value 2 back in the Groovy script. Figure 3.1 shows the process of going from the Groovy world to the Java world and back.

Autoboxing in action: An Integer parameter is unboxed to an int for the Java method call, and an int return value is boxed into an Integer for use in Groovy.

Figure 3.1. Autoboxing in action: An Integer parameter is unboxed to an int for the Java method call, and an int return value is boxed into an Integer for use in Groovy.

All of this is transparent—you don’t need to do anything in the Groovy code to enable it. Now that you understand autoboxing, the question of how to apply operators to objects becomes interesting. We’ll explore this question next.

No intermediate unboxing

If in 1+1 both numbers are objects of type Integer, are those Integers unboxed to execute the plus operation on primitive types?

No. Groovy is more object-oriented than Java. It executes this expression as 1.plus(1), calling the plus() method of the first Integer object, and passing[2] the second Integer object as an argument. The method call returns a new Integer object of value 2.

This is a powerful model. Calling methods on objects is what object-oriented languages should do. It opens the door for applying the full range of object-oriented capabilities to those operators.

Let’s summarize. No matter how literals (numbers, strings, and so forth) appear in Groovy code, they are always objects. Only at the border to Java are they boxed and unboxed. Operators are a shorthand for method calls. Now that you have seen how Groovy handles types when you tell it what to expect, let’s examine what it does when you don’t give it any type information.

The concept of optional typing

So far, we haven’t used any typing in our sample Groovy scripts—or have we? Well, we haven’t used any explicit static typing in the way that you’re familiar with in Java. We assigned strings and numbers to variables and didn’t care about the type. Behind the scenes, Groovy implicitly assumes these variables to be of static type java.lang.Object. This section discusses what happens when a type is specified, and the pros and cons of static and dynamic typing.

Assigning types

Groovy offers the choice of assigning types explicitly just as you do in Java. Table 3.3 gives examples of optional static type declarations and the dynamic type used at runtime. The def keyword is used to indicate that no particular type is demanded.

Table 3.3. Example Groovy statements and the resulting runtime type

Statement

Type of value

Comment

def a = 1

java.lang.Integer

Implicit typing

def b = 1.0f

java.lang.Float

int c = 1

java.lang.Integer

Explicit typing using the Java primitive type names

float d = 1

java.lang.Float

Integer e = 1

java.lang.Integer

Explicit typing using reference type names

String f = '1'

java.lang.String

As we stated earlier, it doesn’t matter whether you declare or cast a variable to be of type int or Integer. Groovy uses the reference type (Integer) either way. If you prefer to be concise, and you believe your code’s readers understand Groovy well enough, use int. If you want to be explicit, or you wish to highlight to Groovy newcomers that you really are using objects, use Integer.

It is important to understand that regardless of whether a variable’s type is explicitly declared, the system is type safe. Unlike untyped languages, Groovy doesn’t allow you to treat an object of one type as an instance of a different type without a well-defined conversion being available. For instance, you could never treat a java.lang.String with value “1” as if it were a java.lang.Number, in the hope that you’d end up with an object that you could use for calculation. That sort of behavior would be dangerous—which is why Groovy doesn’t allow it any more than Java does.

Static versus dynamic typing

The choice between static and dynamic typing is one of the key benefits of Groovy. The Web is full of heated discussions of whether static or dynamic typing is “better.” In other words, there are good arguments for either position.

Static typing provides more information for optimization, more sanity checks at compile-time, and better IDE support; it also reveals additional information about the meaning of variables or method parameters and allows method overloading. Static typing is also a prerequisite for getting meaningful information from reflection.

Dynamic typing, on the other hand, is not only convenient for the lazy programmer who does some ad-hoc scripting, but also useful for relaying and duck typing. Suppose you get an object as the result of a method call, and you have to relay it as an argument to some other method call without doing anything with that object yourself:

def node = document.findMyNode()
log.info node
db.store node

In this case, you’re not interested in finding out what the heck the actual type and package name of that node are. You are spared the work of looking them up, declaring the type, and importing the package. You also communicate: “That’s just something.”

The second usage of dynamic typing is calling methods on objects that have no guaranteed type. This is often called duck typing, and we will explain it in more detail in section 7.3.2. This allows the implementation of generic functionality with a high potential of reuse.

For programmers with a strong Java background, it is not uncommon to start programming Groovy almost entirely using static types, and gradually shift into a more dynamic mode over time. This is legitimate because it allows everybody to use what they are confident with.

Note

Experienced Groovy programmers tend to follow this rule of thumb: As soon as you think about the static type of a reference, declare it; when thinking “just an object,” use dynamic typing.

Whether you specify your types statically or dynamically, you’ll find that Groovy lets you do a lot more than you may expect. Let’s start by looking at the ability to override operators.

Overriding operators

Overriding refers to the object-oriented concept of having types that specify behavior and subtypes that override this behavior to make it more specific. When a language bases its operators on method calls and allows these methods to be overridden, the approach is called operator overriding.

It’s more conventional to use the term operator overloading, which means almost the same thing. The difference is that overloading suggests that you have multiple implementations of a method (and thus the associated operator) that differ only in their parameter types.

We will show you which operators can be overridden, show a full example of how overriding works in practice, and give some guidance on the decisions you need to make when operators work with multiple types.

Overview of overridable operators

As you saw in section 3.1.2, 1+1 is just a convenient way of writing 1.plus(1). This is achieved by class Integer having an implementation of the plus method.

This convenient feature is also available for other operators. Table 3.4 shows an overview.

Table 3.4. Method-based operators

Operator

Name

Method

Works with

a + b

Plus

a.plus(b)

Number, string, collection

a – b

Minus

a.minus(b)

Number, string, collection

a * b

Star

a.multiply(b)

Number, string, collection

a / b

Divide

a.div(b)

Number

a % b

Modulo

a.mod(b)

Integral number

a++

++a

Post increment

Pre increment

a.next()

Number, string, range

a--

--a

Post decrement

Pre decrement

a.previous()

Number, string, range

a**b

Power

a.power(b)

Number

a | b

Numerical or

a.or(b)

Integral number

a & b

Numerical and

a.and(b)

Integral number

a ^ b

Numerical xor

a.xor(b)

Integral number

~a

Bitwise complement

a.negate()

Integral number, string (the latter returning a regular expression pattern)

a[b]

Subscript

a.getAt(b)

Object, list, map, String, Array

a[b] = c

Subscript assignment

a.putAt(b, c)

Object, list, map, StringBuffer, Array

a << b

Left shift

a.leftShift(b)

Integral number, also used like “append” to StringBuffers, Writers, Files, Sockets, Lists

a >> b

Right shift

a.rightShift(b)

Integral number

a >>> b

Right shift unsigned

a.rightShiftUnsigned(b)

Integral number

switch(a){ case b:

}

Classification

b.isCase(a)

Object, range, list, collection, pattern, closure; also used with collection c in c.grep(b), which returns all items of c where b.isCase(item)

a == b

Equals

a.equals(b)

Object; consider hashCode()[a]

a != b

Not equal

! a.equals(b)

Object

a <=> b

Spaceship

a.compareTo(b)

java.lang.Comparable

a > b

Greater than

a.compareTo(b) > 0

 

a >= b

Greater than or equal to

a.compareTo(b) >= 0

 

a < b

Less than

a.compareTo(b) < 0

 

a <= b

Less than or equal to

a.compareTo(b) <= 0

 

a as type

Enforced coercion

a.asType(typeClass)

Any type

[a] When overriding the equals method, Java strongly encourages the developer to also override the hashCode() method such that equal objects have the same hashcode (whereas objects with the same hashcode are not necessarily equal). See the API documentation of java.lang.Object#equals.

You can easily use any of these operators with your own classes. Just implement the respective method. Unlike in Java, there is no need to implement a specific interface.

Note

Strictly speaking, Groovy has even more operators in addition to those in table 3.4, such as the dot operator for referencing fields and methods. Their behavior can also be overridden. They come into play in chapter 7.

This is all good in theory, but let’s see how they work in practice.

Overridden operators in action

Listing 3.1 demonstrates an implementation of the equals == and plus + operators for a Money class. It is a low-level implementation of the Value Object pattern.[3] We allow money of the same form of currency to be added up but do not support multicurrency addition.

We implement equals such that it copes with null comparison. This is Groovy style. The default implementation of the equals operator doesn’t throw any NullPointerExceptions either. Remember that == (or equals) denotes object equality(equal values), not identity (same object instances).

Example 3.1. Operator override

Operator override

Overriding equals is straightforward, as we show at Operator override. We also provide a hashCode method to make sure equal Money objects have the same hashcode. This is required by Java’s contract for java.lang.Object. The use of this operator is shown at Operator override, where one dollar becomes equal to any other dollar.

At Operator override, the plus operator is not overridden in the strict sense of the word, because there is no such operator in Money’s superclass (Object). In this case, operator implementing is the best wording. This is used at Operator override, where we add two Money objects.

To explain the difference between overriding and overloading, here is a possible overload for Money’s plus operator. In listing 3.1, Money can only be added to other Money objects. In case, we would like to add Money as

assert buck + 1 == new Money(2, 'USD')

We can provide the additional method

Money plus (Integer more) {
    return new Money(amount + more, currency)
}

that overloads the plus method with a second implementation that takes an Integer parameter. The Groovy method dispatch finds the right implementation at runtime.

Note

Our plus operation on the Money class returns Money objects in both cases. We describe this by saying that Money’s plus operation is closed under its type. Whatever operation you perform on an instance of Money, you end up with another instance of Money.

This example leads to the general issue of how to deal with different parameter types when implementing an operator method. We will go through some aspects of this issue in the next section.

Making coercion work for you

Implementing operators is straightforward when both operands are of the same type. Things get more complex with a mixture of types, say

1 + 1.0

This adds an Integer and a BigDecimal. What is the return type? Section 3.6 answers this question for the special case of numbers, but the issue is more general. One of the two arguments needs to be promoted to the more general type. This is called coercion.

When implementing operators, there are three main issues to consider as part of coercion.

Supported argument types

You need to decide which argument types and values will be allowed. If an operator must take a potentially inappropriate type, throw an IllegalArgumentException where necessary. For instance, in our Money example, even though it makes sense to use Money as the parameter for the plus operator, we don’t allow different currencies to be added together.

Promoting more specific arguments

If the argument type is a more specific one than your own type, promote it to your type and return an object of your type. To see what this means, consider how you might implement the plus operator if you were designing the BigDecimal class, and what you’d do for an Integer argument.

Integer is more specific than BigDecimal: Every Integer value can be expressed as a BigDecimal, but the reverse isn’t true. So for the BigDecimal.plus(Integer) operator, we would consider promoting the Integer to BigDecimal, performing the addition, and then returning another BigDecimal—even if the result could accurately be expressed as an Integer.

Handling more general arguments with double dispatch

If the argument type is more general, call its operator method with yourself (“this,” the current object) as an argument. Let it promote you. This is also called double dispatch,[4] and it helps to avoid duplicated, asymmetric, possibly inconsistent code. Let’s reverse our previous example and consider Integer.plus (BigDecimal operand).

We would consider returning the result of the expression operand.plus(this), delegating the work to BigDecimal’s plus(Integer) method. The result would be a BigDecimal, which is reasonable—it would be odd for 1+1.5 to return an Integer but 1.5+1 to return a BigDecimal.

Of course, this is only applicable for commutative[5] operators. Test rigorously, and beware of endless cycles.

Groovy’s conventional behavior

Groovy’s general strategy of coercion is to return the most general type. Other languages such as Ruby try to be smarter and return the least general type that can be used without losing information from range or precision. The Ruby way saves memory at the expense of processing time. It also requires that the language promote a type to a more general one when the operation would generate an overflow of that type’s range. Otherwise, intermediary results in a complex calculation could truncate the result.

Now that you know how Groovy handles types in general, we can delve deeper into what it provides for each of the datatypes it supports at the language level. We begin with the type that is probably used more than any other non-numeric type: the humble string.

Working with strings

Considering how widely used strings are, many languages—including Java—provide few language features to make them easier to use. Scripting languages tend to fare better in this regard than mainstream application languages, so Groovy takes on board some of those extra features. This section examines what’s available in Groovy and how to make the most of the extra abilities.

Groovy strings come in two flavors: plain strings and GString s. Plain strings are instances of java.lang.String, and GStrings are instances of groovy.lang.GString. GStrings allow placeholder expressions to be resolved and evaluated at runtime. Many scripting languages have a similar feature, usually called string interpolation, but it’s more primitive than the GString feature of Groovy. Let’s start by looking at each flavor of string and how they appear in code.

Varieties of string literals

Java allows only one way of specifying string literals: placing text in quotes “like this.” If you want to embed dynamic values within the string, you have to either call a formatting method (made easier but still far from simple in Java 1.5) or concatenate each constituent part. If you specify a string with a lot of backslashes in it (such as a Windows file name or a regular expression), your code becomes hard to read, because you have to double the backslashes. If you want a lot of text spanning several lines in the source code, you have to make each line contain a complete string (or several complete strings).

Groovy recognizes that not every use of string literals is the same, so it offers a variety of options. These are summarized in table 3.5.

Table 3.5. Summary of the string literal styles available in Groovy

Start/end characters

Example

GString aware?

Backslash escapes?

Single quote

'hello Dierk'

No

Yes

Double quote

"hello $name"

Yes

Yes

Triple single quote (''')

'''------------ Total: $0.02 ------------'''

No

Yes

Triple double quote (""")

"""first line second line third line"""

Yes

Yes

Forward slash

/x(d*)y/

Yes

Occasionally[a]

[a] The main point of this type of literal is to avoid escaping, so the language avoids it where possible. There are remaining cases with u for unicode support and $ unless $ denotes the end of the pattern. See the Groovy Language Specification for the exact rules.

The aim of each form is to specify the text data you want with the minimum of fuss. Each of the forms has a single feature that distinguishes it from the others:

  • The single-quoted form is never treated as a GString, whatever its contents. This is closely equivalent to Java string literals.

  • The double-quoted form is the equivalent of the single-quoted form, except that if the text contains unescaped dollar signs, it is treated as a GString instead of a plain string. GStrings are covered in more detail in the next section.

  • The triple-quoted form (or multiline string literal) allows the literal to span several lines. New lines are always treated as regardless of the platform, but all other whitespace is preserved as it appears in the text file. Multiline string literals may also be GStrings, depending on whether single quotes or double quotes are used. Multiline string literals act similar to HERE-documents in Ruby or Perl.

  • The slashy form of string literal allows strings with backslashes to be specified simply without having to escape all the backslashes. This is particularly useful with regular expressions, as you’ll see later. Only when a backslash is followed by a u does it need to be escaped[6]—at which point life is slightly harder, because specifying u involves using a GString or specifying the Unicode escape sequence for a backslash.

As we hinted earlier, Groovy uses a similar mechanism for specifying special characters, such as linefeeds and tabs. In addition to the Java escapes, dollar signs can be escaped in Groovy to allow them to be easily specified without the compiler treating the literal as a GString. The full set of escaped characters is specified in table 3.6.

Table 3.6. Escaped characters as known to Groovy

Escaped special character

Meaning



Backspace

Tab

Carriage return

Line feed

f

Form feed

\

Backslash

$

Dollar sign

uabcd

Unicode character U+ abcd (where a, b, c and d are hex digits)

abc[a]

Unicode character U+ abc (where a, b, and c are octal digits, and b and c are optional)

'

Single quote

"

Double quote

[a] Octal escapes are error-prone and should rarely be used. They are provided for the sake of compatibility. Problems occur when a string with an octal escape is changed. If the person changing the string doesn’t notice the octal escape, a change that looks harmless can have unexpected consequences. For instance, consider “My age is12Twenty” changing to “My age is 1220” via a search and replace of “Twenty” for “20”. It sounds like a harmless thing to do, but the consequence is dramatic.

Note that in a double-quoted string, single quotes don’t need to be escaped, and vice versa. In other words, 'I said, "Hi."' and "don't" both do what you hope they will. For the sake of consistency, both still can be escaped in each case. Likewise, dollar signs can be escaped in single-quoted strings, even though they don’t need to be. This makes it easier to switch between the forms.

Note that Java uses single quotes for character literals, but as you have seen, Groovy cannot do so because single quotes are already used to specify strings. However, you can achieve the same as in Java when providing the type explicitly:

char a = 'x'

or

Character b = 'x'

The java.lang.String 'x' is coerced into a java.lang.Character. If you want to coerce a string into a character at other times, you can do so in either of the following ways:

'x' as char

or

'x'.toCharacter()

As a GDK goody, there are more to* methods to convert a string, such as toInteger, toLong, toFloat, and toDouble.

Whichever literal form is used, unless the compiler decides it is a GString, it ends up as an instance of java.lang.String, just like Java string literals. So far, we have only teased you with allusions to what GStrings are capable of. Now it’s time to spill the beans.

Working with GStrings

GStrings are like strings with additional capabilities.[7] They are literally declared in double quotes. What makes a double-quoted string literal a GString is the appearance of placeholders. Placeholders may appear in a full ${expression} syntax or an abbreviated $reference syntax. See the examples in listing 3.2.

Example 3.2. Working with GStrings

Working with GStrings

Within a GString, simple references to variables can be dereferenced with the dollar sign. This simplest form is shown at Working with GStrings, whereas Working with GStrings shows this being extended to use property accessors with the dot syntax. You will learn more about accessing properties in chapter 7.

The full syntax uses dollar signs and curly braces, as shown at Working with GStrings. It allows arbitrary Groovy expressions within the curly braces. The curly braces denote a closure.

In real life, GStrings are handy in templating scenarios. A GString is used in Working with GStrings to create the string for an SQL query. Groovy provides even more sophisticated templating support, as shown in chapter 8. If you need a dollar character within a template (or any other GString usage), you must escape it with a backslash as shown in Working with GStrings.

Although GStrings behave like java.lang.String objects for all operations that a programmer is usually concerned with, they are implemented differently to capture the fixed and the dynamic parts (the so-called values) separately. This is revealed by the following code:

me      = 'Tarzan'
you     = 'Jane'
line    = "me $me - you $you"
assert line == 'me Tarzan - you Jane'
assert line instanceof GString
assert line.strings[0] == 'me '
assert line.strings[1] == ' - you '
assert line.values[0]  == 'Tarzan'
assert line.values[1]  == 'Jane'

For the Geeks

Each value of a GString is bound at declaration time. By the time the GString is converted into a java.lang.String (its toString method is called explicitly or implicitly[8]), each value gets written[9] to the string. Because the logic of how to write a value can be elaborate for certain value types, this behavior can be used in advanced ways. See chapter 13.

You have seen the Groovy language support for declaring strings. What follows is an introduction to the use of strings in the Groovy library. This will also give you a first impression of the seamless interplay of Java and Groovy. We start in typical Java style and gradually slip into Groovy mode, carefully watching each step.

From Java to Groovy

Now that you have your strings easily declared, you can have some fun with them. Because they are objects of type java.lang.String, you can call String’s methods on them or pass them as parameters wherever a string is expected, such as for easy console output:

System.out.print("Hello Groovy!");

This line is equally valid Java and Groovy. You can also pass a literal Groovy string in single quotes:

System.out.print('Hello Groovy!'),

Because this is such a common task, the GDK provides a shortened syntax:

print('Hello Groovy!'),

You can drop parentheses and semicolons, because they are optional and do not help readability in this case. The resulting Groovy style boils down to

print 'Hello Groovy!'

Looking at this last line only, you cannot tell whether this is Groovy, Ruby, Perl, or one of several other line-oriented scripting languages. It may not look sophisticated, but in a way it is. It shows expressiveness—the art of revealing intent in the simplest possible way.

Listing 3.3 presents more of the mix-and-match between core Java and additional GDK capabilities. How would you judge the expressiveness of each line?

Example 3.3. What to do with strings

greeting = 'Hello Groovy!'

assert greeting.startsWith('Hello')

assert greeting.getAt(0) == 'H'
assert greeting[0]       == 'H'

assert greeting.indexOf('Groovy') >= 0
assert greeting.contains('Groovy')

assert greeting[6..11]  == 'Groovy'

assert 'Hi' + greeting - 'Hello' == 'Hi Groovy!'

assert greeting.count('o') == 3

assert 'x'.padLeft(3)      == '  x'
assert 'x'.padRight(3,'_') == 'x__'
assert 'x'.center(3)       == ' x '
assert 'x' * 3             == 'xxx'

These self-explanatory examples give an impression of what is possible with strings in Groovy. If you have ever worked with other scripting languages, you may notice that a useful piece of functionality is missing from listing 3.3: changing a string in place. Groovy cannot do so because it works on instances of java.lang.String and obeys Java’s invariant of strings being immutable.

Before you say “What a lame excuse!” here is Groovy’s answer to changing strings: Although you cannot work on String, you can still work on StringBuffer![10] On a StringBuffer, you can work with the << left shift operator for appending and the subscript operator for in-place assignments. Using the left shift operator on String returns a StringBuffer. Here is the StringBuffer equivalent to listing 3.3:

What to do with strings

Note

Although the expression stringRef << string returns a StringBuffer, that StringBuffer is not automatically assigned to the stringRef (see Note). When used on a String, it needs explicit assignment; on StringBuffer it doesn’t. With a StringBuffer, the data in the existing object changed (see Note)—with a String we can’t change the existing data, so we have to return a new object instead.

Throughout the next sections, you will gradually add to what you have learned about strings as you discover more language features. String has gained several new methods in the GDK. You’ve already seen a few of these, but you’ll see more as we talk about working with regular expressions and lists. The complete list of GDK methods on strings is listed in appendix C.

Working with strings is one of the most common tasks in programming, and for script programming in particular: reading text, writing text, cutting words, replacing phrases, analyzing content, search and replace—the list is amazingly long. Think about your own programming work. How much of it deals with strings?

Groovy supports you in these tasks with comprehensive string support. But this is not the whole story. The next section introduces regular expressions, which cut through text like a chainsaw: difficult to operate but extremely powerful.

Working with regular expressions

 

Once a programmer had a problem. He thought he could solve it with a regular expression. Now he had two problems.

 
 --from a fortune cookie

Suppose you had to prepare a table of contents for this book. You would need to collect all headings like “3.5 Working with regular expressions”—paragraphs that start with a number or with a number, a dot, and another number. The rest of the paragraph would be the heading. This would be cumbersome to code naïvely: iterate over each character; check whether it is a line start; if so, check whether it is a digit; if so, check whether a dot and a digit follow. Puh—lots of rope, and we haven’t even covered numbers that have more than one digit.

Regular expressions come to the rescue. They allow you to declare such a pattern rather than programming it. Once you have the pattern, Groovy lets you work with it in numerous ways.

Regular expressions are prominent in scripting languages and have also been available in the Java library since JDK 1.4. Groovy relies on Java’s regex (regular expression) support and adds three operators for convenience:

  • The regex find operator =~

  • The regex match operator ==~

  • The regex pattern operator ~String

An in-depth discussion about regular expressions is beyond the scope of this book. Our focus is on Groovy, not on regexes. We give the shortest possible introduction to make the examples comprehensible and provide you with a jump-start.

Regular expressions are defined by patterns. A pattern can be anything from a simple character, a fixed string, or something like a date format made up of digits and delimiters, up to descriptions of balanced parentheses in programming languages. Patterns are declared by a sequence of symbols. In fact, the pattern description is a language of its own. Some examples are shown in table 3.7. Note that these are the raw patterns, not how they would appear in string literals. In other words, if you stored the pattern in a variable and printed it out, this is what you’d want to see. It’s important to make the distinction between the pattern itself and how it’s represented in code as a literal.

Table 3.7. Simple regular expression pattern examples

Pattern

Meaning

some text

Exactly “some text”.

somes+text

The word “some” followed by one or more whitespace characters followed by the word “text”.

^d+(.d+)? (.*)

Our introductory example: headings of level one or two. ^ denotes a line start, d a digit, d+ one or more digits. Parentheses are used for grouping. The question mark makes the first group optional. The second group contains the title, made of a dot for any character and a star for any number of such characters.

dd/dd/dddd

A date formatted as exactly two digits followed by slash, two more digits followed by a slash, followed by exactly four digits.

A pattern like one of the examples in table 3.7 allows you to declare what you are looking for, rather than having to program how to find something. Next, you will see how patterns appear as literals in code and what can be done with them. We will then revisit our initial example with a full solution, before examining some performance aspects of regular expressions and finally showing how they can be used for classification in switch statements and for collection filtering with the grep method.

Specifying patterns in string literals

How do you put the sequence of symbols that declares a pattern inside a string?

In Java, this causes confusion. Patterns use lots of backslashes, and to get a backslash in a Java string literal, you need to double it. This makes for difficulty reading patterns in Java strings. It gets even worse if you need to match an actual backslash in your pattern—the pattern language escapes that with a backslash too, so the Java string literal needed to match the pattern a is "a\\b".

Groovy does much better. As you saw earlier, there is the slashy form of string literal, which doesn’t require you to escape the backslash character and still works like a normal GString. Listing 3.4 shows how to declare patterns conveniently.

Example 3.4. Regular expression GStrings

assert "abc" == /abc/
assert "\d" == /d/

def reference = "hello"
assert reference == /$reference/

assert "$" == /$/

The slashy syntax doesn’t require the dollar sign to be escaped. Note that you have the choice to declare patterns in either kind of string.

Tip

Sometimes the slashy syntax interferes with other valid Groovy expressions such as line comments or numerical expressions with multiple slashes for division. When in doubt, put parentheses around your pattern like (/pattern/). Parentheses force the parser to interpret the content as an expression.

Symbols

The key to using regular expressions is knowing the pattern symbols. For convenience, table 3.8 provides a short list of the most common ones. Put an earmark on this page so you can easily look up the table. You will use it a lot.

Table 3.8. Regular expression symbols (excerpt)

Symbol

Meaning

.

Any character

^

Start of line (or start of document, when in single-line mode)

$

End of line (or end of document, when in single-line mode)

d

Digit character

D

Any character except digits

s

Whitespace character

S

Any character except whitespace

w

Word character

W

Any character except word characters



Word boundary

()

Grouping

(x|y)

x or y, as in (Groovy|Java|Ruby)

1

Backmatch to group one: for example, find doubled characters with (.)1

x*

Zero or more occurrences of x

x+

One or more occurrences of x

x?

Zero or one occurrence of x

x{m,n}

At least m and at most n occurrences of x

x{m}

Exactly m occurrences of x

[a-f]

Character class containing the characters a, b, c, d, e, f

[^a]

Character class containing any character except a

(?is:x)

Switches mode when evaluating x; i turns on ignoreCase, s means single-line mode

Tip

Symbols tend to have the same first letter as what they represent: for example, digit, space, word, and boundary. Uppercase symbols define the complement; think of them as a warning sign for no.

More to consider:

  • Use grouping properly. The expanding operators such as star and plus bind closely; ab+ matches abbbb. Use (ab)+ to match ababab.

  • In normal mode, the expanding operators are greedy, meaning they try to match the longest substring that matches the pattern. Add an additional question mark after the operator to put them into restrictive mode. You may be tempted to extract the href from an HTML anchor element with this regex: href="(.*)". But href="(.*?)" is probably better. The first version matches until the last double quote in your text; the latter matches until the next double quote.[11]

We have provided only a brief description of the regex pattern format, but a complete specification comes with your JDK. It is located in the Javadoc of class java.util.regex.Pattern and may change marginally between JDK versions. For JDK 1.4.2, it can be found online at http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html.

See the Javadoc to learn more about different evaluation modes, positive and negative lookahead, backmatches, and posix characters.

It always helps to test your expressions before putting them into code. There are online applications that allow interactive testing of regular expressions: for example, http://www.nvcc.edu/home/drodgers/ceu/resources/test_regexp.asp. You should be aware that not all regular expression pattern languages are exactly the same. You may get unexpected results if you take a regular expression designed for use in .NET and apply it in a Java or Groovy program. Although there aren’t many differences, the differences that do exist can be hard to spot. Even if you take a regular expression from a book or a web site, you should still test that it works in your code.

Now that you have the pattern declared, you need to tell Groovy how to apply it. We will explore a whole variety of usages.

Applying patterns

Applied to a given string, Groovy supports the following tasks for regular expressions:

  • Tell whether the pattern fully matches the whole string.

  • Tell whether there is an occurrence of the pattern in the string.

  • Count the occurrences.

  • Do something with each occurrence.

  • Replace all occurrences with some text.

  • Split the string into multiple strings by cutting at each occurrence.

Listing 3.5 shows how Groovy sets patterns into action. Unlike most other examples, this listing contains some comments. This reflects real life and is not for illustrative purposes. The use of regexes is best accompanied by this kind of comment for all but the simplest patterns.

Example 3.5. Regular expressions

Regular expressions

Regular expressions and Regular expressions have an interesting twist. Although the regex find operator evaluates to a Matcher object, it can also be used as a Boolean conditional. We will explore how this is possible when examining the “Groovy Truth” in chapter 6.

Tip

To remember the difference between the =~ find operator and the ==~ match operator, recall that match is more restrictive, because the pattern needs to cover the whole string. The demanded coverage is “longer” just like the appearance of its operator.

See your Javadoc for more information about the java.util.regex.Matcher object: how to walk through all matches and how to work with groupings at each match.

Common regex pitfalls

You do not need to fall into the regex trapdoors yourself. We have already done this for you. We have learned the following:

  • When things get complex (note, this is when, not if), comment verbosely.

  • Use the slashy syntax instead of the regular string syntax, or you will get lost in a forest of backslashes.

  • Don’t let your pattern look like a toothpick puzzle. Build your pattern from subexpressions like WORD in listing 3.5.

  • Put your assumptions to the test. Write some assertions or unit tests to test your regex against static strings. Please don’t send us any more flowers for this advice; an email with the subject “assertion saved my life today” will suffice.

Patterns in action

You’re now ready to do everything you wanted to do with regular expressions, except we haven’t covered “do something with each occurrence.” Something and each sounds like a cue for a closure to appear, and that’s the case here. String has a method called eachMatch that takes a regex as a parameter along with a closure that defines what to do on each match.

By the Way

The match is not a simple string but a list of strings, containing the whole match at position 0. If the pattern contains groupings, they are available as match[n] where n is group number n. Groups are numbered by the sequence of their opening parentheses.

The match gets passed into the closure for further analysis. In our musical example in listing 3.6, we append each match to a result string.

Example 3.6. Working on each match of a pattern

Working on each match of a pattern

There are two different ways to iterate through matches with identical behavior: use Working on each match of a pattern String.eachMatch(Pattern), or use Working on each match of a pattern Matcher.each(), where the Matcher is the result of applying the regex find operator to a string and a pattern. Working on each match of a pattern shows a special case for replacing each match with some dynamically derived content from the given closure. The variable it refers to the matching substring. The result is to replace “ain” with underscores, but only where it forms part of a rhyme.

In order to fully understand how the Groovy regular expression support works, we need to look at the java.util.regex.Matcher class. It is a JDK class that encapsulates knowledge about

  • How often and at what position a pattern matches

  • The groupings for each match

The GDK enhances the Matcher class with simplified array-like access to this information. This is what happens in the following (already familiar) example that matches all non-whitespace characters:

matcher = 'a b c' =~ /S/

assert matcher[0]    == 'a'
assert matcher[1..2] == 'bc'
assert matcher.count == 3

The interesting part comes with groupings in the match. If the pattern contains parentheses to define groups, the matcher returns not a single string for each match but an array, where the full match is at index 0 and each extracted group follows. Consider this example, where each match finds pairs of strings that are separated by a colon. For later processing, the match is split into two groups, for the left and the right string:

matcher = 'a:1 b:2 c:3' =~ /(S+):(S+)/

assert matcher.hasGroup()
assert matcher[0] == ['a:1', 'a', '1']

In other words, what matcher[0] returns depends on whether the pattern contains groupings.

This also applies to the matcher’s each method, which comes with a convenient notation for groupings. When the processing closure defines multiple parameters, the list of groups is distributed over them:

('xy' =~ /(.)(.)/).each { all, x, y  ->
    assert all == 'xy'
    assert x == 'x'
    assert y == 'y'
}

This matcher matches only one time but contains two groups with one character each.

Note

Groovy internally stores the most recently used matcher (per thread). It can be retrieved with the static method Matcher.getLastMatcher. You can also set the index property of a matcher to make it look at the respective match with matcher.index = x. Both can be useful in some exotic corner cases. See Matcher’s API documentation for details.

Matcher and Pattern work in combination and are the key abstractions for regexes in Java and Groovy. You have seen Matcher, and we’ll have a closer look at the Pattern abstraction next.

Patterns and performance

Finally, let’s look at performance and the pattern operator ~String.

The pattern operator transforms a string into an object of type java.util.regex.Pattern. For a given string, this pattern object can be asked for a matcher object.

The rationale behind this construction is that patterns are internally backed by a so-called finite state machine that does all the high-performance magic. This machine is compiled when the pattern object is created. The more complicated the pattern, the longer the creation takes. In contrast, the matching process as performed by the machine is extremely fast.

The pattern operator allows you to split pattern-creation time from pattern-matching time, increasing performance by reusing the finite state machine. Listing 3.7 shows a poor-man’s performance comparison of the two approaches. The precompiled pattern version is at least 20% faster (although these kinds of measurements can differ wildly).

Example 3.7. Increase performance with pattern reuse.

Increase performance with pattern reuse.

To find words that start and end with the same character, we used the 1 backmatch to refer to that character. We prepared its usage by putting the word’s first character into a group, which happens to be group 1.

Note the difference in spelling in Increase performance with pattern reuse.. This is not a =~ b but a = ~b. Tricky.

By the Way

The observant reader may spot a language issue: What happens if you write a=~b without any whitespace? Is that the =~ find operator, or is it an assignment of the ~b pattern to a? For the human reader, it is ambiguous. Not so for the Groovy parser. It is greedy and will parse this as the find operator.

It goes without saying that being explicit with whitespace is good programming style, even when the meaning is unambiguous for the parser. Do it for the next human reader, which will likely be you.

Don’t forget that performance should usually come second to readability—at least to start with. If reusing a pattern means bending your code out of shape, you should ask yourself how critical the performance of that particular area is before making the change. Measure the performance in different situations with each version of the code, and balance ease of maintenance with speed and memory requirements.

Patterns for classification

Listing 3.8 completes our journey through the domain of patterns. The Pattern object, as returned from the pattern operator, implements an isCase(String) method that is equivalent to a full match of that pattern with the string. This classification method is a prerequisite for using patterns conveniently with the grep method and in switch cases.

The example classifies words that consist of exactly four characters. The pattern therefore consists of four dots. This is not an ellipsis!

Example 3.8. Patterns in grep() and switch()

assert (~/.... /).isCase('bear')

switch('bear'){
    case ~/..../ : assert true; break
    default      : assert false
}

beasts = ['bear','wolf','tiger','regex']

assert beasts.grep(~/..../) == ['bear','wolf']

Tip

Classifications read nicely in switch and grep. The direct use of classifier.isCase(candidate) happens rarely, but when it does, it is best read from right to left: “candidate is a case of classifier”.

Regular expressions are difficult beasts to tame, but mastering them adds a new quality to all text-manipulation tasks. Once you have a grip on them, you’ll hardly be able to imagine having programmed (some would say lived) without them. Groovy makes regular expressions easily accessible and straightforward to use.

This concludes our coverage of text-based types, but of course computers have always dealt with numbers as well as text. Working with numbers is easy in most programming languages, but that doesn’t mean there’s no room for improvement. Let’s see how Groovy goes the extra mile when it comes to numeric types.

Working with numbers

The available numeric types and their declarations in Groovy were introduced in section 3.1.

You have seen that for decimal numbers, the default type is java.math.BigDecimal. This is a feature to get around the most common misconceptions about floating-point arithmetic. We’re going to look at which type is used where and what extra abilities have been provided for numbers in the GDK.

Coercion with numeric operators

It is always important to understand what happens when you use one of the numeric operators.

Most of the rules for the addition, multiplication, and subtraction operators are the same as in Java, but there are some changes regarding floating-point behavior, and BigInteger and BigDecimal also need to be included. The rules are straightforward. The first rule to match the situation is used.

For the operations +, -, and *:

  • If either operand is a Float or a Double, the result is a Double. (In Java, when only Float operands are involved, the result is a Float too.)

  • Otherwise, if either operand is a BigDecimal, the result is a BigDecimal.

  • Otherwise, if either operand is a BigInteger, the result is a BigInteger.

  • Otherwise, if either operand is a Long, the result is a Long.

  • Otherwise, the result is an Integer.

Table 3.9 depicts the scheme for quick lookup. Types are abbreviated by uppercase letters.

Table 3.9. Numerical coercion

+ - *

B

S

I

C

L

BI

BD

F

D

Byte

I

I

I

I

L

BI

BD

D

D

Short

I

I

I

I

L

BI

BD

D

D

Integer

I

I

I

I

L

BI

BD

D

D

Character

I

I

I

I

L

BI

BD

D

D

Long

L

L

L

L

L

BI

BD

D

D

BigInteger

BI

BI

BI

BI

BI

BI

BD

D

D

BigDecimal

BD

BD

BD

BD

BD

BD

BD

D

D

Float

D

D

D

D

D

D

D

D

D

Double

D

D

D

D

D

D

D

D

D

Other aspects of coercion behavior:

  • Like Java but unlike Ruby, no coercion takes place when the result of an operation exceeds the current range, except for the power operator.

  • For division, if any of the arguments is of type Float or Double, the result is of type Double; otherwise the result is of type BigDecimal with the maximum precision of both arguments, rounded half up. The result is normalized—that is, without trailing zeros.

  • Integer division (keeping the result as an integer) is achievable through explicit casting or by using the intdiv() method.

  • The shifting operators are only defined for types Integer and Long. They do not coerce to other types.

  • The power operator coerces to the next best type that can take the result in terms of range and precision, in the sequence Integer, Long, Double.

  • The equals operator coerces to the more general type before comparing.

Rules can be daunting without examples, so this behavior is demonstrated in table 3.10.

Table 3.10. Numerical expression examples

Expression

Result type

Comments

1f*2f

Double

In Java, this would be Float.

(Byte)1+(Byte)2

Integer

As in Java, integer arithmetic is always performed in at least 32 bits.

1*2L

Long

 

1/2

BigDecimal (0.5)

In Java, the result would be the integer 0.

(int)(1/2)

Integer (0)

This is normal coercion of BigDecimal to Integer.

1.intdiv(2)

Integer (0)

This is the equivalent of the Java 1/2.

Integer.MAX_VALUE+1

Integer

Non-power operators wrap without promoting the result type.

2**31

Integer

 

2**33

Long

The power operator promotes where necessary.

2**3.5

Double

 

2G+1G

BigInteger

 

2.5G+1G

BigDecimal

 

1.5G==1.5F

Boolean (true)

The Float is promoted to a BigDecimal before comparison.

1.1G==1.1F

Boolean (false)

1.1 can’t be exactly represented as a Float (or indeed a Double), so when it is promoted to BigDecimal, it isn’t equal to the exact BigDecimal 1.1G but rather 1.100000023841858G.

The only surprise is that there is no surprise. In Java, results like in the fourth row are often surprising—for example, (1/2) is always zero because when both operands of division are integers, only integer division is performed. To get 0.5 in Java, you need to write (1f/2).

This behavior is especially important when using Groovy to enhance your application with user-defined input. Suppose you allow super-users of your application to specify a formula that calculates an employee’s bonus, and it gets specified as businessDone * (1/3). With Java semantics, this will be a bad year for the poor employees.

GDK methods for numbers

The GDK defines all applicable methods from table 3.4 to implement overridable operators for numbers such as plus, minus, power, and so forth. They all work without surprises. In addition, the abs, toInteger, and round methods do what you’d expect.

More interestingly, the GDK also defines the methods times, upto, downto, and step. They all take a closure argument. Listing 3.9 shows these methods in action: times is just for repetition, upto is for walking a sequence of increasing numbers, downto is for decreasing numbers, and step is the general version that walks until the end value by successively adding a step width.

Example 3.9. GDK methods on numbers

GDK methods on numbers

Calling methods on numbers can feel unfamiliar at first when you come from Java. Just remember that numbers are objects and you can treat them as such.

You have seen that in Groovy, numbers work the natural way and even guard you against the most common errors with floating-point arithmetic. In most cases, there is no need to remember all details of coercion. When the need arises, this section may serve as a reference.

The strategy of making objects available in unexpected places starts to become an ongoing theme. You have seen it with numbers, and section 4.1 shows the same principle applied to ranges.

Summary

The simple datatypes form a big portion of your everyday programming work with Groovy. After all, working with strings and numbers is the bread and butter of software development.

Making common activities more convenient is one of Groovy’s main promises. Consequently, Groovy promotes even the simple datatypes to first-class objects and implements operators as method calls to make the benefits of object orientation ubiquitously available.

Developer convenience is further enhanced by allowing a variety of means for string literal declarations, whether through flexible GString declarations or with the slashy syntax for situations where extra escaping is undesirable, such as regular expression patterns. GStrings contribute to another of Groovy’s central pillars: concise and expressive code. This allows the reader a clearer insight into the runtime string value, without having to wade through reams of string concatenation or switch between format strings and the values replaced in them.

Regular expressions are well represented in Groovy, again confirming its comfortable place among other scripting languages. Leveraging regular expressions is common in the scripting world, and a language that treated them as second-class citizens would be severely hampered. Groovy effortlessly combines Java’s libraries with language support, retaining the regular expression dialect familiar to Java programmers with the ease of use found in scripting.

The Groovy way of treating numbers with respect to type conversion and precision handling leads to intuitive usage, even for non-programmers. This becomes particularly important when Groovy scripts are used for smart configurations of larger systems where business users may provide formulas—for example, to define share-valuation details.

Strings, regular expressions, and numbers alike profit from numerous methods that the GDK introduces on top of the JDK. The pattern is clear by now—Groovy is a language designed for the ease of those developing in it, concentrating on making repetitive tasks as simple as they can be without sacrificing the power of the Java platform.

You shall soon see that this focus on ease of use extends far beyond the simple types Java developers are used to having built-in language support for. The Groovy designers are well aware of other concepts that are rarely far from a programmer’s mind. The next chapter shows how intuitive operators, enhanced literals, and extra GDK methods are also available with Groovy’s collective data types: ranges, lists, and maps.



[1] In fact, there is an idiomatic Groovy solution that replaces the full Java example with a two-liner. However, you need to learn a bit more before you can value such a solution.

[2] The phrase “passing an object” is short for “passing a reference of an object.” In Groovy and Java alike, objects are passed as references when they are arguments of a method call.

[4] Double dispatch is usually used with overloaded methods: a.method(b) calls b.method(a) where method is overloaded with method(TypeA) and method(TypeB).

[5] Commutative means that the sequence of operators can be exchanged without changing the result of the operation. For example, plus is usually required to be commutative (a+b==b+a) but minus is not (a-b!=b-a).

[6] This is slightly tricky in a slashy string and involves either using a GString such as /${''}/ or using the Unicode escape sequence. A similar issue occurs if you want to use a dollar sign. This is a small (and rare) price to pay for the benefits available, however.

[7] groovy.lang.GString isn’t actually a subclass of java.lang.String, and couldn’t be, because String is final. However, GStrings can usually be used as if they are strings—Groovy coerces them into strings when it needs to.

[8] Implicit calls happen when a GString needs to be coerced into a java.lang.String.

[9] See Writer.write(Object) in section 8.2.4.

[10] Future versions may use a StringBuilder instead. StringBuilder was introduced in Java 1.5 to reduce the synchronization overhead of StringBuffers. Typically, StringBuffers are used only in a single thread and then discarded—but StringBuffer itself is thread-safe, at the expense of synchronizing each method call.

[11] This is only to explain the greedy behavior of regular expression, not to explain how HTML is parsed correctly, which would involve a lot of other topics such as ordering of attributes, spelling variants, and so forth.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.54.168