Message-ID: <1871314836.9556.1422622000330.JavaMail.haus-conf@codehaus02.managed.contegix.com> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_9555_910750715.1422622000330" ------=_Part_9555_910750715.1422622000330 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location:
The newly released Groovy 2.0 brings key static features to the language= with static type checking and static compilation, adopts JDK 7 related imp= rovements with Project Coin syntax enhancements and the support of the new = =E2=80=9Cinvoke dynamic=E2=80=9D JVM instruction, and becomes more modular = than before. In this article, we=E2=80=99re going to look into those new fe= atures in more detail.
Groovy, by nature, is and will always be a dynamic language. However, Gr=
oovy is often used as a "Java scripting language", or as a "=
better Java" (ie. a Java with less boilerplate and more power features=
). A lot of Java developers actually use and embed Groovy in their Java app=
lications as an extension language, to author more expressive business rule=
s, to further customize the application for different customers, etc. For s=
uch Java-oriented use cases, developers don't need all the dynamic capabili=
ties offered by the language, and they usually expect the same kind of feed=
back from the Groovy compiler as the one given by javac. In particular, the=
y want to get compilation errors (rather than runtime errors) for things li=
ke typos on variable or method names, incorrect type assignments and the li=
ke. That's why Groovy 2 features static type checking support.
The static type checker is built using Groovy=E2=80=99s existing powerfu= l AST (Abstract Syntax Tree) transformation mechanisms but for those not fa= miliar with these mechanisms you can think of it as an optional compiler pl= ugin triggered through an annotation. Being an optional feature, you are no= t forced to use it if you don=E2=80=99t need it. To trigger static type che= cking, just use the @TypeChecked annotation on a method or on a class to tu= rn on checking at your desired level of granularity. Let=E2=80=99s see that= in action with a first example:
import groovy.transform.TypeChecked void someMethod() {} @TypeChecked void test() { // compilation error: // cannot find matching method sommeeMethod() sommeeMethod() def name =3D "Marion" // compilation error: // the variable naaammme is undeclared println naaammme }=20
We annotated the test() method with the @TypeChecked annotation, which i=
nstructs the Groovy compiler to run the static type checking for that parti=
cular method at compilation time. We=E2=80=99re trying to call someMethod()=
with some obvious typos, and to print the name variable again with another=
typo, and the compiler will throw two compilation errors because respectiv=
ely, the method and variable are not found or declared.
The static type checker also verifies that the return types and values o= f your assignments are coherent:
import groovy.transform.TypeChecked @TypeChecked Date test() { // compilation error: // cannot assign value of Date // to variable of type int int object =3D new Date() String[] letters =3D ['a', 'b', 'c'] =20 // compilation error: // cannot assign value of type String // to variable of type Date Date aDateVariable =3D letters[0] // compilation error: // cannot return value of type String // on method returning type Date return "today" }=20
In this example, the compiler will complain about the fact you cannot as=
sign a Date in an int variable, nor can you return a String instead of a Da=
te value specified in the method signature. The compilation error from the =
middle of the script is also interesting, as not only does it complain of t=
he wrong assignment, but also because it shows type inference at play, beca=
use the type checker, of course, knows that letters[0] is of type String, b=
ecause we=E2=80=99re dealing with an array of Strings.
Since we=E2=80=99re mentioning type inference, let=E2=80=99s have a look= at some other occurrences of it. We mentioned the type checker tracks the = return types and values:
import groovy.transform.TypeChecked =20 @TypeChecked int method() { if (true) { // compilation error: // cannot return value of type String // on method returning type int 'String' } else { 42 } }=20
Given a method returning a value of primitive type int, the type c=
hecker.
The static type checker, however, won=E2=80=99t complain for certain aut= omatic type conversions that Groovy supports. For instance, for method sign= atures returning String, boolean or Class, Groovy converts return values to= these types automatically:
import groovy.transform.TypeChecked @TypeChecked boolean booleanMethod() { "non empty strings are evaluated to true" } assert booleanMethod() =3D=3D true @TypeChecked String stringMethod() { // StringBuilder converted to String calling toString() new StringBuilder() << "non empty string" } assert stringMethod() instanceof String @TypeChecked Class classMethod() { // the java.util.List class will be returned "java.util.List" } assert classMethod() =3D=3D List=20
The static type checker is also clever enough to do type inference:
import groovy.transform.TypeChecked @TypeChecked void method() { def name =3D " Guillaume " =20 // String type inferred (even inside GString) println "NAME =3D ${name.toUpperCase()}" // Groovy GDK method support // (GDK operator overloading too) println name.trim() int[] numbers =3D [1, 2, 3] // Element n is an int for (int n in numbers) { println n } }=20
Although the name variable was defined with def, the type checker unders=
tands it is of type String. Then, when this variable is used in the interpo=
lated string, it knows it can call String=E2=80=99s toUpperCase() method, o=
r the trim() method later one, which is a method added by the Groovy Develo=
pment Kit decorating the String class. Last, when iterating over the elemen=
ts of an array of primitive ints, it also understands that an element of th=
at array is obviously an int.
An important aspect to have in mind is that using the static type checki=
ng facility restricts what you are allowed to use in Groovy. Most runtime d=
ynamic features are not allowed, as they can=E2=80=99t be statically type c=
hecked at compilation time. So adding a new method at runtime through the t=
ype=E2=80=99s metaclasses is not allowed. But when you need to use some par=
ticular dynamic feature, like Groovy=E2=80=99s builders, you can opt out of=
static type checking should you wish to.
The @TypeChecke= d annotation can be put at the class level or at the method level. So if yo= u want to have a whole class type checked, put the annotation on the class,= and if you want only a few methods type checked, put the annotation on jus= t those methods. Also, if you want to have everything type checked, except = a specific method, you can annotate the latter with @TypeChecked(TypeChecki= ngMode.SKIP) =E2=80=94 or @TypeChecked(SKIP) for short, if you statically i= mport the associated enum. Let=E2=80=99s =3D new StringWriter() new MarkupBuilder(sw).html { body { div name } } sw.toString() } assert greeting("Cédric").contains("CÉDRIC&quo= t;)=20
Current production releases of Java don=E2=80=99t support general type i= nference; hence we find today many places where code is often quite verbose= and cluttered with boilerplate constructs. This obscures the intent of the= code and without the support of powerful IDEs is also harder to write. Thi= s is the case with instanceof checks: You often check the class of a value = with instanceof inside an if condition, and afterwards in the if block, you= must still use casts to be able to use methods of the value at hand. In pl= ain Groovy, as well as in the new static type checking mode, you can comple= tely') =3D=3D 'ABC' assert test(123) =3D=3D '246'=20
In the above example, the static type checker knows that the val paramet=
er is of type String inside the if block, and of type Number in the else if=
block, without requiring any cast.
=
The static type checker goes a bit further in terms of type inference in= the sense that it has a more granular understanding of the type of your ob= jects. Consider the following code:
import groovy.transform.TypeChecked // inferred return type: // a list of numbers which are comparable and serializable @TypeChecked test() { // an integer and a BigDecimal return [1234, 3.14] }=20
In this example, we return, intuitively, a list of numbers: an Integer a=
nd a BigDecimal. But the static type checker computes what we call a =E2=80=
=9Clowest upper bound=E2=80=9D, which is actually a list of numbers which a=
re also serializable and comparable. It=E2=80=99s not possible to denote th=
at type with the standard Java type notation, but if we had some kind of in=
tersection operator like an ampersand, it could look like List<Number &a=
mp; Serializable & Comparable>.
Although this is not really recommended as a good practice, sometimes de= velopers use the same untyped variable to store values of different types. = Look at this method body:
import groovy.transform.TypeChecked @TypeChecked test() { def var =3D 123 // inferred type is int var =3D "123" // assign var with a String println var.toInteger() // no problem, no need to cast var =3D 123 println var.toUpperCase() // error, var is int! }=20
The var variable is initialized with an int. Then, a String is assigned.=
The =E2=80=9Cflow typing=E2=80=9D algorithm follows the flow of assignment=
and understands that the variable now holds a String, so the static type c=
hecker will be happy with the toInteger() method added by Groovy on top of =
String. Next, a number is put back in the var variable, but then, when call=
ing toUpperCase(), the type checker will throw a compilation error, as ther=
e=E2=80=99s no toUpperCase() method on Integer.
There are = some special cases for the flow typing algorithm when a variable is shared = with a closure which are interesting. What happens when a local variable is= referenced in a closure inside a method where that variable is defined? Le= t=E2=80=99s have a look at this example:
import groovy.transform.TypeChecked @TypeChecked test() { def var =3D "abc" def cl =3D { if (new Random().nextBoolean()) var =3D new Date() } cl() var.toUpperCase() // compilation error! }=20
The var local variable is assigned a String, but then, var might be assi= gned a Date if some random value is true. Typically, it=E2=80=99s only at r= untime that we really know if the condition in the if statement of the clos= ure is made or not. Hence, at compile-time, there=E2=80=99s no chance the c= ompiler can know if var now contains a String or a Date. That=E2=80=99s why= the compiler will actually complain about the toUpperCase() call, as it is= not able to infer that the variable contains a String or not. This example= is certainly a bit contrived, but there are some more interesting cases:= p>
import groovy.transform.TypeChecked class A { void foo() {} } class B extends A { void bar() {} } @TypeChecked test() { def var =3D new A() def cl =3D { var =3D new B() } cl() // var is at least an instance of A // so we are allowed to call method foo() var.foo() }=20
In the test() method above, var is assigned an instance of A, and then a=
n instance of B in the closure which is call afterwards, so we can at least=
infer that var is of type A.
All those checks added to th= e Groovy compiler are done at compile-time, but the generated bytecode is s= till the same dynamic code as usual =E2=80=94 no changes in behavior at all= .
Since the compiler now knows a lot more about your prog= ram in terms of types, it opens up some interesting possibilities: what abo= ut compiling that type checked code statically? The obvious advantage will = be that the generated bytecode will more closely resemble the bytecode crea= ted by the javac compiler itself, making statically compiled Groovy code as= fast as plain Java, among other advantages. In the next section, we=E2=80= =99ll learn more about Groovy=E2=80=99s static compilation.=
As we shall see in the following chapter about the JDK 7 alignments, Gro=
ovy 2.0 supports the new =E2=80=9Cinvoke dynamic=E2=80=9D instruction of th=
e JVM and its related APIs, facilitating the development of dynamic languag=
es on the Java platform and bringing some additional performance to Groovy=
=E2=80=99s dynamic calls. However, unfortunately shall I say, JDK 7 is not =
widely deployed in production at the time of this writing, so not everybody=
has the chance to run on the latest version. So developers looking for per=
formance improvements would not see much changes in Groovy 2.0, if they are=
n=E2=80=99t able to run on JDK 7. Luckily, the Groovy development team thou=
ght those developers could get interesting performance boost, among other a=
dvantages, by allowing type checked code to be compiled statically.
&n= bsp;
Without further ado, let=E2=80=99s dive in and use the new @Compi= leStatic transform:
import groovy.transform.CompileStatic @CompileStatic int squarePlusOne(int num) { num * num + 1 } assert squarePlusOne(3) =3D=3D 10=20
This time, instead of using @TypeChecked, use @CompileStatic, and your c=
ode will be statically compiled, and the bytecode generated here will look =
like javac=E2=80=99s bytecode, running just as fast. Like the @TypeChecked =
annotation, @CompileStatic can annotate classes and methods, and @CompileSt=
atic(SKIP) can bypass static compilation for a specific method, when its cl=
ass is marked with @CompileStatic.
Another advantage of th= e javac-like bytecode generation is that the size of the bytecode for those= annotated methods will be smaller than the usual bytecode generated by Gro= ovy for dynamic methods, since to support Groovy=E2=80=99s dynamic features= , the bytecode in the dynamic case contains additional instructions to call= into Groovy=E2=80=99s runtime system.
Last but not least,= static compilation can be used by framework or library code writers to hel= p avoid adverse interactions when dynamic metaprogramming is in use in seve= ral parts of the codebase. The dynamic features available in languages like= Groovy are what give developers incredible power and flexibility but if ca= re is not taken, different assumptions can exist in different parts of the = system with regards to what metaprogramming features are in play and this c= an have unintended consequences. As a slightly contrived example, consider = what happens if you are using two different libraries, both of which add a = similarly named but differently implemented method to one of your core clas= ses. What behaviour is expected? Experienced users of dynamic languages wil= l have seen this problem before and probably heard it referred to as =E2=80= =9Cmonkey patching=E2=80=9D. Being able to statically compile parts of your= code base =E2=80=94 those parts that don=E2=80=99t need dynamic features = =E2=80=94 shields you from the effects of monkey patching, as the staticall= y compiled code doesn=E2=80=99t go through Groovy=E2=80=99s dynamic runtime= system. Although dynamic runtime aspects of the language are not allowed i= n a static compilation context, all the usual AST transformation mechanisms= work just as well as before, since most AST transforms perform their magic= at compilation time.
In terms of performance, Groovy=E2= =80=99s statically compiled code is usually more or less as fast as javac= =E2=80=99s. In the few micro-benchmarks the development team used, performa= nce is identical in several cases, and sometimes it=E2=80=99s slightly slow= er.
Historically, thanks to the transparent and seamless i= ntegration of Java and Groovy, we used to advise developers to optimize som= e hotspot routines in Java for further performance gains, but now, with thi= s static compilation option, this is no longer the case, and people wishing= to develop their projects in full Groovy can do so.
<= /strong>
The grammar of the Groovy programming language actually derives from the=
Java grammar itself, but obviously, Groovy provides additional nice shortc=
uts to make developers more productive. This familiarity of syntax for Java=
developers has always been a key selling point for the project and its wid=
e adoption, thanks to a flat learning curve. And of course, we expect Groov=
y users and newcomers to also want to benefit from the few syntax refinemen=
ts offered by Java 7 with its =E2=80=9CProject Coin=E2=80=9D additions.
Beyond the syntax aspects, JDK 7 also brings interesting nove= lties to its APIs, and for a first time in a long time, even a new bytecode= instruction called =E2=80=9Cinvoke dynamic=E2=80=9D, which is geared towar= ds helping implementors develop their dynamic languages more easily and ben= efit from more performance.
Since day 1 (that was back in 2003 already!) Groovy has had several synt= ax enhancements and features on top of Java. One can think of closures, for= example, but also the ability to put more than just discrete values in swi= tch / case statements, where Java 7 only allows Strings in addition. So som= e of the Project Coin syntax enhancements, like Strings in switch, were alr= eady present in Groovy. However, some of the enhancements are new, such as = binary literals, underscore in number literals, or the multi catch block, a= nd Groovy 2 supports them. The sole omission from the Project Coin enhancem= ents is the =E2=80=9Ctry with resources=E2=80=9D construct, for which Groov= y already provides various alternatives through the rich API of the Groovy = Development Kit.
In Java 6 and before, as well as in Groovy, numbers could be represented= in decimal, octal and hexadecimal bases, and with Java 7 and Groovy 2, you= can use a binary notation with the =E2=80=9C0b=E2=80=9D prefix:
int x =3D 0b10101111 assert x =3D=3D 175 byte aByte =3D 0b00100001 assert aByte =3D=3D 33 int anInt =3D 0b1010000101000101 assert anInt =3D=3D 41285=20
When writing long literal numbers, it=E2=80=99s harder on the eye to fig= ure out how some numbers are grouped together, for example with groups of t= housands, of words, etc. By allowing you to place underscore in number lite= rals, it=E2=80=99s easier to spot those groups:
long creditCardNumber =3D 1234_5678_9012_3456L long socialSecurityNumbers =3D 999_99_9999L double monetaryAmount =3D 12_345_132.12 long hexBytes =3D 0xFF_EC_DE_5E long hexWords =3D 0xFFEC_DE5E long maxLong =3D 0x7fff_ffff_ffff_ffffL long alsoMaxLong =3D 9_223_372_036_854_775_807L long bytes =3D 0b11010010_01101001_10010100_10010010=20, Thro= wable. With the multi catch block, we=E2=80=99re able to define several exc= eptions to be catch and treated by the same catch block:
try { /* ... */ } catch(IOException | NullPointerException e) { /* one block to handle 2 exceptions */ }=20
As we mentioned earlier in this article, JDK 7 came with a new bytecode =
instruction called =E2=80=9Cinvoke dynamic=E2=80=9D, as well as with its as=
sociated APIs. Their goal is to help dynamic language implementors in their=
job of crafting their languages on top of the Java platform, by simplifyin=
g the wiring of dynamic method calls, by defining =E2=80=9Ccall sites=E2=80=
=9D where dynamic method call section can be cached, =E2=80=9Cmethod handle=
s=E2=80=9D as method pointers, =E2=80=9Cclass values=E2=80=9D to store any =
kind of metadata along class objects, and a few other things. One caveat th=
ough, despite promising performance improvements, =E2=80=9Cinvoke dynamic=
=E2=80=9D hasn=E2=80=99t yet fully been optimized inside the JVM, and doesn=
=E2=80=99t yet always deliver the best performance possible, but update aft=
er update, the optimizations are coming.
Groovy brought it= s own implementation techniques, to speed up method selection and invocatio= n with =E2=80=9Ccall site caching=E2=80=9D, to store metaclasses (the dynam= ic runtime equivalent of classes) with its metaclass registry, to perform n= ative primitive calculations as fast as Java, and much more. But with the a= dvent of =E2=80=9Cinvoke dynamic=E2=80=9D, we can rebase the implementation= of Groovy on top of these APIs and this JVM bytecode instruction, to gain = performance improvements and to simplify our code base.
If= you=E2=80=99re lucky to run on JDK 7, you=E2=80=99ll be able to use a new = version of the Groovy JARs which has been compiled with the =E2=80=9Cinvoke= dynamic=E2=80=9D support. Those JARs are easily recognizable as they use t= he =E2=80=9C-indy=E2=80=9D classifier in their names.=
Using the =E2=80=9Cindy=E2=80=9D JARs is not enough, however, to compile=
your Groovy code so that it leverages the =E2=80=9Cinvoke dynamic=E2=80=9D=
support. For that, you=E2=80=99ll have to use the --indy flag when using t=
he =E2=80=9Cgroovyc=E2=80=9D compiler or the =E2=80=9Cgroovy=E2=80=9D comma=
nd. This also means that even if you=E2=80=99re using the indy JARs, you ca=
n still target JDK 5 or 6 for compilation.
Similarly, if = you=E2=80=99re using the groovyc Ant task for compiling your projects, you = can also specify the indy attribute:
... <taskdef name=3D"groovyc" classname=3D"org.codehaus.groovy.ant.Groovyc" classpathref=3D"cp"/> ... <groovyc srcdir=3D"${srcDir}" destdir=3D"${destDir}"= indy=3D"true"> <classpath> ... </classpath> </groovyc> ...=20
The Groovy Eclipse Maven compiler plugin hasn=E2=80=99t yet been updated=
with the support of Groovy 2.0 but this will be the case shortly. For GMav=
en plugin users, although it=E2=80=99s possible to configure the plugin to =
use Groovy 2.0 already, there=E2=80=99s currently no flag to enable the inv=
oke =3D new CompilerConfiguration(); config.getOptimizationOptions().put("indy", true); config.getOptimizationOptions().put("int", false); GroovyShell shell =3D new GroovyShell(config);=20
As invokedynamic is supposed to be a full replacement to dynamic method = dispatch, it is also necessary to disable the primitive optimizations which= generate extra bytecode that is here to optimize edge cases. Even if it is= in some cases slower than with primitive optimizations activated, future v= ersions of the JVM will feature an improved JIT which will be capable of in= lining most of the calls and remove unnecessary boxings.
In our testing, we noticed some interesting performance gains in some ar=
eas, whereas other programs could run slower than when not using the invoke=
dynamic support. The Groovy team has further performance improvements in t=
he pipeline for Groovy 2.1 however, but we noticed the JVM isn=E2=80=99t ye=
t finely tuned and still has a long way to go to be fully optimized. But fo=
rtunately, upcoming JDK 7 updates (in particular update 8) should already c=
ontain such improvements, so the situation can only improve. Furthermore, a=
s invoke dynamic is used for the implementation of JDK 8 Lambdas, we can be=
sure more improvements are forthcoming.
We=E2=80=99ll finish our journey through the new features of Groovy 2.0 =
by speaking about modularity. Just like Java, Groovy is not just a language=
, but it=E2=80=99s also a set of APIs serving various purposes: templating,=
Swing UI building, Ant scripting, JMX integration, SQL access, servlet ser=
ving, and more. The Groovy deliverables were bundling all these features an=
d APIs inside a single big JAR. However, not everybody needs everything at =
all times in their own applications: you might be interested in the templat=
e engine and the servlets if you=E2=80=99re writing some web application, b=
ut you might only need the Swing builder when working on a rich desktop cli=
ent program.
So the first goal of the modularity aspect of this release is to actuall=
y split the original Groovy JAR into smaller modules, smaller JARs. The cor=
e Groovy JAR is now twice as small, and we have the following feature modul=
es available:
With Groovy 2= , you=E2=80=99re now able to just pick up the modules you=E2=80=99re intere= sted in, rather than bringing everything on your classpath. However, we sti= ll provide the =E2=80=9Call=E2=80=9D JAR which contains everything, if you = don=E2=80=99t want to complicate your dependencies for just a few megabytes= of saved space. We also provide those JARs compiled with the =E2=80=9Cinvo= ke dynamic=E2=80=9D support as well, for those running on JDK 7.
The work on making Groovy more modular also yielded an interesting new f=
eature: extension modules. By splitting Groovy into smaller modules, a mech=
anism for modules to contribute extension methods has been created. That wa=
y, extension modules can provide instance and static methods to other class=
es, including the ones from the JDK or third-party libraries. Groovy uses t=
his mechanism to decorate classes from the JDK, to add new useful methods t=
o classes like String, File, streams, and many more =E2=80=94 for example, =
a getText() method on URL allows you to retrieve the content of a remote UR=
L through an HTTP get. Notice also that those extension methods in your mod=
ules are also understood by the static type checker and compiler. But let=
=E2=80=99s now have a look at how you can add new methods to existing types=
.
To add new methods to an existing type, you=E2=80=99ll have to create a =
helper class that will contain those methods. Inside that helper class, all=
the extension methods will actually be public (the default for Groovy but =
required if implementing in Java) and static (although they will be availab=
le on instances of that class). They will always take a first parameter whi=
ch is actually the instance on which this method will be called. And then f=
ollowing parameters will be the parameters passed when calling the method. =
This is the same convention use for Groovy categories.
Say= we want to add a greets() method on String, that would greet the name of t= he person passed in parameters, so that you could that method as follow:
assert "Guillaume".greets("Paul") =3D=3D = "Hi Paul, I'm Guillaume"=20
To accomplish that, you will create a helper class with an extension met= hod like so:
package com.acme class MyExtension { static String greets(String self, String name) { "Hi ${name}, I'm ${self}" } }=20
Static extension methods are defined using the same mechanism, but have = to be declared in a separate class. The extension module descriptor then de= termines whether the class provides instance or static methods. Let=E2=80= =99s } }=20
That way, you are able to use that extension method as follows:
Random.between(3, 4)=20
Once you=E2=80=99ve coded your helper classes (in Groovy or even in Java= ) containing the extension methods, you need to create a descriptor for you= r module. You must create a file called org.codehaus.groovy.runtime.Extensi= onModule in the META-INF/services directory of your module archive. Four es= sential fields can be defined, to tell the Groovy runtime about the name an= d version of your module, as well as to point at your helper classes for ex= tension methods with a comma-separated list of class names. Here is what ou= r final module descriptor looks like:
moduleName =3D MyExtension moduleVersion =3D 1.0 extensionClasses =3D com.acme.MyExtension staticExtensionClasses =3D com.acme.MyStaticExtension=20
With this extension module descriptor on the classpath, you are now able=
to use those extension methods in your code, without needing an import or =
anything else, as those extension methods are automatically registered.
With the @Grab annotation in your scripts, you can fetch dependencies fr=
om Maven repositories like Maven Central. With the addition of the @GrabRes=
olver annotation, you can specify your own location for your dependencies a=
s well. If you are =E2=80=9Cgrabbing=E2=80=9D an extension module dependenc=
y through this mechanism, the extension method will also be installed autom=
atically. Ideally, for consistency, your module name and version should be =
coherent with the artifact id and version of your artifact.=
Groovy is very popular among Java developers and offers them a mature pl=
atform and ecosystem for their application needs. But without resting still=
, the Groovy development team continues to further improve the language and=
its APIs to help its users increase their productivity on the Java platfor=
m.
Groovy 2.0 responds to three key themes: