This article is based on Groovy in Action, Second Edition, to be published on Summer 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code 'java40beat' and get 40% discount on eBooks and pBooks ]
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 boolean, short, int, float, 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. The reverse is true too: changing the contents of an object doesn’t affect the value of a variable referring to that object.
You cannot call methods on values of primitive types, and you cannot use them where Java expects objects of type java.lang.Object. For each primitive type, Java has a wrapper 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 arbitrary1 reference types, but only for primitive types (with the notable exception of +, which is also supported for strings).
The Groovy code in listing 1 calls methods on seemingly primitive types (first, with a literal declaration and, then, on a variable), which is not allowed in Java, where you need to explicitly create the integer wrapper to convince the compiler. While calling + on strings is allowed in Java, calling the – (minus) operator is not. Groovy allows both.
Listing 1 Groovy allows methods to be called on types that are declared like primitive types in Java
(60 * 60 * 24 * 365).toString(); // invalid Java int secondsPerYear = 60 * 60 * 24 * 365; secondsPerYear.toString(); // invalid Java new Integer(secondsPerYear).toString(); assert "abc" - "a" == "bc" // invalid Java
The Groovy way looks more consistent and involves some language sophistication that we are going to explore next.
Groovy’s answer—everything’s an object
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, 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 1 provides a complete list of these wrappers.
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’s 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 1 because they are already reference types and 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 2 gives examples of each of the literal formats available for numeric types in Groovy.
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 1 shows the process of going from the Groovy world to the Java world and back.
Figure 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, you may be wondering whether those Integers unboxed to execute the plus operation on primitive types.
The answer is 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 passing3 the second Integer object as an argument. The method call returns an 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 shorthand for method calls.