Introduction à Groovy Language

1. Vue d'ensemble

Groovy est un langage de script dynamique pour la JVM . Il se compile en bytecode et se fond parfaitement avec le code et les bibliothèques Java.

Dans cet article, nous allons examiner certaines des fonctionnalités essentielles de Groovy, notamment la syntaxe de base, les structures de contrôle et les collections.

Ensuite, nous examinerons certaines des principales caractéristiques qui en font un langage attrayant, notamment la sécurité nulle, la vérité implicite, les opérateurs et les chaînes.

2. Environnement

Si nous voulons utiliser Groovy dans les projets Maven, nous devons ajouter ce qui suit au pom.xml:

  // ...  org.codehaus.gmavenplus gmavenplus-plugin 1.5     // ...  org.codehaus.groovy groovy-all 2.4.10  

Le plugin Maven le plus récent peut être trouvé ici et la dernière version du groovy-all ici.

3. Fonctionnalités de base

Il existe de nombreuses fonctionnalités utiles dans Groovy. Maintenant, regardons les blocs de construction de base du langage et en quoi il diffère de Java.

Voyons maintenant les éléments de base du langage et en quoi il diffère de Java.

3.1. Typage dynamique

L'une des fonctionnalités les plus importantes de Groovy est sa prise en charge de la saisie dynamique.

Les définitions de type sont facultatives et les types réels sont déterminés au moment de l'exécution. Jetons un coup d'œil à ces deux classes:

class Duck { String getName() { 'Duck' } } class Cat { String getName() { 'Cat' } } 

Ces deux classes définissent la même méthode getName , mais elle n'est pas définie explicitement dans un contrat.

Maintenant, imaginez que nous ayons une liste d'objets contenant des canards et des chats qui ont la méthode getName . Avec Groovy, nous pouvons faire ce qui suit:

Duck duck = new Duck() Cat cat = new Cat() def list = [duck, cat] list.each { obj -> println obj.getName() }

Le code se compilera et la sortie du code ci-dessus serait:

Duck Cat

3.2. Conversion implicite de vérité

Comme en JavaScript, Groovy évalue chaque objet en un booléen si nécessaire, par exemple lors de son utilisation dans une instruction if ou lors de la négation de la valeur:

if("hello") {...} if(15) {...} if(someObject) {...}

Il y a quelques règles simples à retenir concernant cette conversion:

  • Les collections, tableaux et cartes non vides évaluent à vrai
  • La correspondance avec au moins une correspondance est évaluée à vrai
  • Les itérateurs et les énumérations avec d'autres éléments sont forcés à true
  • Les chaînes non vides , GStrings et CharSequences , sont forcées à true
  • Les nombres non nuls sont évalués à vrai
  • Les références d'objet non nulles sont forcées à true

Si nous voulons personnaliser la conversion de vérité implicite, nous pouvons définir notre méthode asBoolean () .

3.3. Importations

Certains packages sont importés par défaut, et nous n'avons pas besoin de les importer explicitement:

import java.lang.* import java.util.* import java.io.* import java.net.* import groovy.lang.* import groovy.util.* import java.math.BigInteger import java.math.BigDecimal

4. Transformations AST

Les transformations AST ( Abstract Syntax Tree ) nous permettent de nous connecter au processus de compilation Groovy et de le personnaliser pour répondre à nos besoins. Cela se fait au moment de la compilation, il n'y a donc aucune pénalité de performances lors de l'exécution de l'application. Nous pouvons créer nos transformations AST, mais nous pouvons également utiliser celles intégrées.

Nous pouvons créer nos transformations, ou nous pouvons bénéficier de celles intégrées.

Jetons un coup d'œil à quelques annotations à connaître.

4.1. Type d' annotation vérifié

Cette annotation est utilisée pour forcer le compilateur à effectuer une vérification de type stricte pour les morceaux de code annotés. Le mécanisme de vérification de type est extensible, nous pouvons donc même fournir une vérification de type encore plus stricte que celle disponible en Java lorsque vous le souhaitez.

Jetons un œil à l'exemple ci-dessous:

class Universe { @TypeChecked int answer() { "forty two" } }

Si nous essayons de compiler ce code, nous observerons l'erreur suivante:

[Static type checking] - Cannot return value of type java.lang.String on method returning type int

L' annotation @TypeChecked peut être appliquée aux classes et aux méthodes.

4.2. Annotation CompileStatic

Cette annotation permet au compilateur d'exécuter des vérifications au moment de la compilation comme cela est fait avec du code Java. Après cela, le compilateur effectue une compilation statique, contournant ainsi le protocole du méta-objet Groovy.

When a class is annotated, all methods, properties, files, inner classes, etc. of the annotated class will be type-checked. When a method is annotated, static compilation is applied only to those items (closures and anonymous inner classes) that are enclosed by that method.

5. Properties

In Groovy, we can create POGOs (Plain Old Groovy Objects) that work the same way as POJOs in Java, although they’re more compact because getters and setters are automatically generated for public properties during compilation. It's important to remember that they will be generated only if they’re not already defined.

This gives us the flexibility of defining attributes as open fields while retaining the ability to override the behavior when setting or getting the values.

Consider this object:

class Person { String name String lastName }

Since the default scope for classes, fields, and methods is public – this is a public class, and the two fields are public.

The compiler will convert these into private fields and add getName(), setName(), getLastName() and setLasfName() methods. If we define the setter and getter for a particular field, the compiler will not create a public method.

5.1. Shortcut Notations

Groovy offers a shortcut notation for getting and setting properties. Instead of the Java-way of calling getters and setters, we can use a field-like access notation:

resourceGroup.getResourcePrototype().getName() == SERVER_TYPE_NAME resourceGroup.resourcePrototype.name == SERVER_TYPE_NAME resourcePrototype.setName("something") resourcePrototype.name = "something"

6. Operators

Let's now take a look at new operators added on top of those known from plain Java.

6.1. Null-Safe Dereference

The most popular one is the null-safe dereference operator “?” which allows us to avoid a NullPointerException when calling a method or accessing a property of a null object. It’s especially useful in chained calls where a null value could occur at some point in the chain.

For example, we can safely call:

String name = person?.organization?.parent?.name

In the example above if a person, person.organization, or organization.parent are null, then null is returned.

6.2. Elvis Operator

The Elvis operator “?:” lets us condense ternary expressions. These two are equivalent:

String name = person.name ?: defaultName

and

String name = person.name ? person.name : defaultName

They both assign the value of person.name to the name variable if it is Groovy true (in this case, not null and has a non-zero length).

6.3. Spaceship Operator

The spaceship operator “” is a relational operator that performs like Java’s compareTo() which compares two objects and returns -1, 0, or +1 depending on the values of both arguments.

If the left argument is greater than the right, the operator returns 1. If the left argument is less than the right, the operator returns −1. If the arguments are equal, 0 is returned.

The greatest advantage of using the comparison operators is the smooth handling of nulls such that x y will never throw a NullPointerException:

println 5  null

The above example will print 1 as a result.

7. Strings

There are multiple ways for expressing string literals. The approach used in Java (double-quoted strings) is supported, but it is also allowed to use single quotes when preferred.

Multi-line strings, sometimes called heredocs in other languages, are also supported, using triple quotes (either single or double).

Multi-line strings, sometimes called heredocs in other languages, are also supported, using triple quotes (either single or double).

Strings defined with double quotes support interpolation using the ${} syntax:

def name = "Bill Gates" def greeting = "Hello, ${name}"

In fact, any expression can be placed inside the ${}:

def name = "Bill Gates" def greeting = "Hello, ${name.toUpperCase()}"

A String with double quotes is called a GString if it contains an expression ${}, otherwise, it is a plain String object.

The code below will run without failing the test:

def a = "hello" assert a.class.name == 'java.lang.String' def b = 'hello' assert b.class.name == 'java.lang.String' def c = "${b}" assert c.class.name == 'org.codehaus.groovy.runtime.GStringImpl'

8. Collections and Maps

Let's take a look at how some basic data structures are handled.

8.1. Lists

Here’s some code to add a few elements to a new instance of ArrayList in Java:

List list = new ArrayList(); list.add("Hello"); list.add("World");

And here’s the same operation in Groovy:

List list = ['Hello', 'World']

Lists are by default of type java.util.ArrayList and can also be declared explicitly by calling the corresponding constructor.

There isn’t a separate syntax for a Set, but we can use type coercion for that. Either use:

Set greeting = ['Hello', 'World']

or:

def greeting = ['Hello', 'World'] as Set

8.2. Map

The syntax for a Map is similar, albeit a bit more verbose, because we need to be able to specify keys and values delimited with colons:

def key = 'Key3' def aMap = [ 'Key1': 'Value 1', Key2: 'Value 2', (key): 'Another value' ]

After this initialization, we will get a new LinkedHashMap with the entries: Key1 -> Value1, Key2 -> Value 2, Key3 -> Another Value.

We can access entries in the map in many ways:

println aMap['Key1'] println aMap[key] println aMap.Key1

9. Control Structures

9.1. Conditionals: if-else

Groovy supports the conditional if/else syntax as expected:

if (...) { // ... } else if (...) { // ... } else { // ... } 

9.2. Conditionals: switch-case

The switch statement is backward compatible with Java code so that we can fall through cases sharing the same code for multiple matches.

The most important difference is that a switch can perform matching against multiple different value types:

def x = 1.23 def result = "" switch ( x ) { case "foo": result = "found foo" break case "bar": result += "bar" break case [4, 5, 6, 'inList']: result = "list" break case 12..30: result = "range" break case Number: result = "number" break case ~/fo*/: result = "foo regex" break case { it < 0 }: // or { x < 0 } result = "negative" break default: result = "default" } println(result)

The example above will print number.

9.3. Loops: while

Groovy supports the usual while loops like Java does:

def x = 0 def y = 5 while ( y-- > 0 ) { x++ }

9.4. Loops: for

Groovy embraces this simplicity and strongly encourages for loops following this structure:

for (variable in iterable) { body }

The for loop iterates over iterable. Frequently used iterables are ranges, collections, maps, arrays, iterators, and enumerations. In fact, any object can be an iterable.

Braces around the body are optional if it consists of only one statement. Below are examples of iterating over a range, list, array, map, and strings:

def x = 0 for ( i in 0..9 ) { x += i } x = 0 for ( i in [0, 1, 2, 3, 4] ) { x += i } def array = (0..4).toArray() x = 0 for ( i in array ) { x += i } def map = ['abc':1, 'def':2, 'xyz':3] x = 0 for ( e in map ) { x += e.value } x = 0 for ( v in map.values() ) { x += v } def text = "abc" def list = [] for (c in text) { list.add(c) }

Object iteration makes the Groovy for-loop a sophisticated control structure. It’s a valid counterpart to using methods that iterate over an object with closures, such as using Collection’s each method.

The main difference is that the body of a for loop isn’t a closure, this means this body is a block:

for (x in 0..9) { println x }

whereas this body is a closure:

(0..9).each { println it }

Even though they look similar, they’re very different in construction.

A closure is an object of its own and has different features. It can be constructed in a different place and passed to the each method. However, the body of the for-loop is directly generated as bytecode at its point of appearance. No special scoping rules apply.

10. Exception Handling

The big difference is that checked exceptions handling is not enforced.

To handle general exceptions, we can place the potentially exception-causing code in a try/catch block:

try { someActionThatWillThrowAnException() } catch (e) // log the error message, and/or handle in some way }

By not declaring the type of exception we catch, any exception will be caught here.

11. Closures

Simply put, a closure is an anonymous block of executable code which can be passed to variables and has access to data in the context where it was defined.

They’re also similar to anonymous inner classes, although they don’t implement an interface or extend a base class. They are similar to lambdas in Java.

Interestingly, Groovy can take full advantage of the JDK additions that have been introduced to support lambdas, especially the streaming API. We can always use closures where lambda expressions are expected.

Let's consider the example below:

def helloWorld = { println "Hello World" }

The variable helloWorld now holds a reference to the closure, and we can execute it by calling its call method:

helloWorld.call()

Groovy lets us use a more natural method call syntax – it invokes the call method for us:

helloWorld()

11.1. Parameters

Like methods, closures can have parameters. There are three variants.

In the latter example, because there’s nothing declpersistence_startared, there is only one parameter with the default name it. The modified closure that prints what it is sent would be:

def printTheParam = { println it }

We could call it like this:

printTheParam('hello') printTheParam 'hello'

We can also expect parameters in closures and pass them when calling:

def power = { int x, int y -> return Math.pow(x, y) } println power(2, 3)

The type definition of parameters is the same as variables. If we define a type, we can only use this type, but can also it and pass in anything we want:

def say = { what -> println what } say "Hello World"

11.2. Optional Return

The last statement of a closure may be implicitly returned without the need to write a return statement. This can be used to reduce the boilerplate code to a minimum. Thus a closure that calculates the square of a number can be shortened as follows:

def square = { it * it } println square(4)

This closure makes usage of the implicit parameter it and the optional return statement.

12. Conclusion

Cet article a fourni une introduction rapide au langage Groovy et à ses principales fonctionnalités. Nous avons commencé par introduire des concepts simples tels que la syntaxe de base, les instructions conditionnelles et les opérateurs. Nous avons également démontré des fonctionnalités plus avancées telles que les opérateurs et les fermetures.

Si vous souhaitez trouver plus d'informations sur le langage et sa sémantique, vous pouvez vous rendre directement sur le site officiel.