You saw that Groovy classes are Java classes at the bytecode level, and consequently, Groovy objects are Java objects in memory. At the source-code level, Groovy class and object handling is almost a superset of the Java syntax, with the exception of nested classes that are currently not supported by the Groovy syntax and some slight changes to the way arrays are defined. We will examine the organization of classes and source files, and the relationships between the two. We will also consider Groovy’s use of packages and type aliasing, as well as demystify where Groovy can load classes from in its classpath.
File to class relationship:
The relationship between files and class declarations is not as fixed as in Java. Groovy files can contain any number of public class declarations:
When not compiling explicitly, Groovy finds a class by matching its name to a corresponding *.groovy source file. At this point, naming becomes important. Groovy only finds classes where the class name matches the source filename. When such a file is found, all declared classes in that file are parsed and become known to Groovy.
Listing 7.11 shows a sample script with two simple classes, Vendor and Address . For the moment, they have no methods, only public fields.
- Listing 7.11 Multiple class declarations in one file
- class Vendor {
- public String name
- public String product
- public Address address = new Address()
- }
- class Address {
- public String street, town, state
- public int zip
- }
- def canoo = new Vendor()
- canoo.name = 'Canoo Engineering AG'
- canoo.product = 'UltraLightClient (ULC)'
- canoo.address.street = 'Kirschgartenst. 7'
- canoo.address.zip = 4051
Listing 7.11 illustrates a convenient convention supported by Groovy’s source file to class mapping rules, which we discussed earlier. This convention allows small helper classes that are used only with the current main class or current script to be declared within the same source file. Compare this with Java, which allows you to use nested classes to introduce locally used classes without cluttering up your public class namespace or making navigation of the codebase more difficult by requiring a proliferation of source code files. Although it isn’t exactly the same, this convention has similar benefits for Groovy developers.
Organizing classes in packages:
Groovy follows Java’s approach of organizing files in packages of hierarchical structure. The package structure is used to find the corresponding class files in the filesystem’s directories. Because *.groovy source files are not necessarily compiled to *.class files, there is also a need to look up *.groovy files. When doing so, the same strategy is used: The compiler looks for a Groovy class Vendor in the business package in the file business/Vendor.groovy.
In listing 7.12, we separate the Vendor and Address classes from the script code, as shown in listing 7.11, and move them to the inaction.ch7.business package.
- Listing 7.12 Vendor and Address classes moved to the inaction.ch7.business package
- package inaction.ch7.business
- class Vendor {
- public String name
- public String product
- public Address address = new Address()
- }
- class Address {
- public String street, town, state
- public int zip
- }
The lookup has to start somewhere, and Java uses its classpath for this purpose. The classpath is a list of possible starting points for the lookup of *.class files. Groovy reuses the classpath for looking up *.groovy files. When looking for a given class, if Groovy finds both a *.class and a *.groovy file, it uses whichever is newer; that is, it will recompile source files into *.class files if they have changed since the previous class file was compiled.
Packages
Exactly like in Java, Groovy classes must specify their package before the class definition. When no package declaration is given, the default package is assumed. Listing 7.12 shows the file business/Vendor.groovy, which has a package statement as its first line. To reference Vendor in the inaction.ch7.business package, you can either useinaction.ch7.business.Vendor within the code or use imports for abbreviation.
Imports
Groovy follows Java’s notion of allowing import statements before any class declaration to abbreviate class references.
Note.
Listing 7.13 shows the use of the import statement, with the .* notation advising the compiler to try resolving all unknown class references against all classes in the inaction.ch7.business package.
- Listing 7.13 Using import to access Vendor in the business package
- import inaction.ch7.business.*
- def canoo = new Vendor()
- canoo.name = 'Canoo Engineering AG'
- canoo.product = 'UltraLightClient (ULC)'
- assert canoo.dump() =~ /ULC/
Type aliasing
The import statement has another nice twist: together with the as keyword, it can be used for type aliasing. Whereas a normal import allows a fully qualified class to be referred to by its base name, a type alias allows a fully qualified class to be referred to by a name of your choosing. This feature resolves naming conflicts and supports local changes or bug fixes to a third-party library.
Consider the following library class:
- package thirdparty
- class MathLib {
- Integer twice(Integer value) {
- return value * 3 // intentionally wrong!
- }
- Integer half(Integer value) {
- return value / 2
- }
- }
- assert 10 == new MathLib().twice(5)
- Listing 7.14 Using import as for local library modifications
- import thirdparty.MathLib as OrigMathLib
- class MathLib extends OrigMathLib {
- Integer twice(Integer value) {
- return value * 2
- }
- }
- // nothing changes below here: Usage code for library remains unchanged
- def mathlib = new MathLib()
- assert 10 == mathlib.twice(5) // Invoke fixed method
- assert 2 == mathlib.half(5) // Invoke original method
- package thirdparty2
- class MathLib {
- Integer increment(Integer value) {
- return value + 1
- }
- }
- Listing 7.15 Using import as for avoiding name clashes
- import thirdparty.MathLib as TwiceHalfMathLib
- import thirdparty2.MathLib as IncMathLib
- def math1 = new TwiceHalfMathLib()
- def math2 = new IncMathLib()
- assert 3 == math1.half(math2.increment(5))
Further classpath considerations:
Finding classes in *.class and *.groovy files is an important part of working with Groovy, and unfortunately a likely source of problems. If you installed the J2SDK including the documentation, you will find the classpath explanation under %JAVA_HOME%/docs/tooldocs/windows/classpath.html under Windows, or under a similar directory for Linux and Solaris. Everything the documentation says equally applies to Groovy.
A number of contributors can influence the effective classpath in use. The overview in table 7.1 may serve as a reference when you’re looking for a possible bad guy that’s messing up your classpath.
Groovy defines its classpath in a special configuration file under %GROOVY_ HOME%/conf. Looking at the file groovy-starter.conf reveals the following lines (beside others):
- # Load required libraries
- load ${groovy.home}/lib/*.jar
- # load user specific libraries
- # load ${user.home}/.groovy/lib/*
If you have problems finding your user.home, open a command shell and execute:
Chances are, you are in this directory by default anyway.
Chapter 11 goes through more advanced classpath issues that need to be respected when embedding Groovy in environments that manage their own classloading infrastructure—for example an application server.
Supplement:
* User Guide > Scripts and Classes