Behind the fun of Groovy looms the world of Java. We will examine how Groovy classes enter the Java environment to start with, how Groovy augments the existing Java class library, and finally how Groovy gets its groove: a brief explanation of the dynamic nature of Groovy classes.
My class is your class:
“Mi casa es su casa.” My home is your home. That’s the Spanish way of expressing hospitality. Groovy and Java are just as generous with each other’s classes. All Groovy code runs inside the Java Virtual Machine (JVM) and is therefore bound to Java’s object model. Regardless of whether you write Groovy classes or scripts, they run as Java classes inside the JVM. You can run Groovy classes inside the JVM two ways:
These two methods of converting *.groovy files into Java classes are illustrated in figure 2.7. Either way, the resulting classes have the same format as classic Java classes. Groovy enhances Java at the source code level but stays identical at the byte-code level.
GDK: the Groovy library
Groovy’s strong connection to Java makes using Java classes from Groovy and vice versa exceptionally easy. Because they are both the same thing, there is no gap to bridge. In our code examples, every Groovy object is instantly a Java object. This has an enormous benefit for Java programmers, who can fully leverage their knowledge of the Java libraries. Consider a sample string in Groovy:
- 'Hello World!'
- if ('Hello World!'.startsWith('Hello')) {
- // Code to execute if the string starts with 'Hello'
- }
One example is the size method as used in the GDK. It is available on everything that is of some size: strings, arrays, lists, maps, and other collections. Behind the scenes, they are all JDK classes. This is an improvement over the JDK, where you determine an object’s size in a number of different ways, as listed in table 2.1.
Groovy can play this trick by funneling all method calls through a device called MetaClass. This allows a dynamic approach to object orientation, only part of which involves adding methods to existing classes. You’ll learn more about MetaClass in the next section.
The Groovy lifecycle:
Although the Java runtime understands compiled Groovy classes without any problem, it doesn’t understand .groovy source files. More work has to happen behind the scenes if you want to load .groovy files dynamically at runtime. Let’s dive under the hood to see what’s happening.
Groovy syntax is line oriented, but the execution of Groovy code is not. Unlike other scripting languages, Groovy code is not processed line-by-line in the sense that each line is interpreted separately. Instead, Groovy code is fully parsed, and a class is generated from the information that the parser has built. The generated class is the binding device between Groovy and Java, and Groovy classes are generated such that their format is identical to Java bytecode.
Inside the Java runtime, classes are managed by a classloader. When a Java classloader is asked for a certain class, it loads the class from the *.class file, stores it in a cache, and returns it. Because a Groovy-generated class is identical to a Java class, it can also be managed by a classloader with the same behavior. The difference is that the Groovy classloader can also load classes from *.groovy files (and do parsing and class generation before putting it in the cache).
Groovy can at runtime read *.groovy files as if they were *.class files. The class generation can also be done before runtime with the groovyc compiler. The compiler simply takes *.groovy files and transforms them into *.class files using the same parsing and class-generation mechanics.
Groovy class generation at work
Suppose we have a Groovy script stored in a file named MyScript.groovy, and we run it via groovy MyScript.groovy . The following are the class-generation steps, as shown previously in figure 2.7:
Figure 2.8 shows a second variant, when groovyc is used instead of groovy . This time, the classes are written into *.class files. Both variants use the same class-generation mechanism. Classes are always fully constructed before runtime and do not change while running.
Groovy is dynamic
What makes dynamic languages so powerful is the ability to seemingly modify classes at runtime—for example to add new methods. But as you just learned, Groovy generates classes once and cannot change the bytecode after it has been loaded. How can you add a method without changing the class? The answer is simple but delicate.
The bytecode that the Groovy class generator produces is necessarily different from what the Java compiler would generate—not in format but in content. Suppose a Groovy file contains a statement like foo . Groovy doesn’t generate byte-code that reflects this method call directly, but does something like:
- getMetaClass().invokeMethod(this, "foo", EMPTY_PARAMS_ARRAY)
A second option of dynamic code is putting the code in a string and having Groovy evaluate it. You will see how this works in chapter 11. Such a string can be constructed literally or through any kind of logic. But beware: You can easily get overwhelmed by the complexity of dynamic code generation.
Here is an example of concatenating two strings and evaluating the result:
- def code = '1 + '
- code += System.getProperty('os.version')
- println code // Prints “1 + 5.1”
- println evaluate(code) // Prints “6.1”
Wait—didn’t we claim that line-by-line execution isn’t possible, and code has to be fully constructed as a class? How can code then be executed? The answer is simple. Remember the left-hand path in figure 2.8? Class generation can transparently happen at runtime. The only news is that the class-generation input can also be a string like code rather than a *.groovy file.
沒有留言:
張貼留言