Introduzione a Groovy Language

1. Panoramica

Groovy è un linguaggio di scripting dinamico per JVM . Si compila in bytecode e si fonde perfettamente con il codice e le librerie Java.

In questo articolo daremo un'occhiata ad alcune delle caratteristiche essenziali di Groovy, inclusa la sintassi di base, le strutture di controllo e le raccolte.

Quindi esamineremo alcune delle caratteristiche principali che lo rendono un linguaggio attraente, tra cui sicurezza nulla, verità implicita, operatori e stringhe.

2. Ambiente

Se vogliamo utilizzare Groovy nei progetti Maven, dobbiamo aggiungere quanto segue al pom.xml:

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

Il plugin Maven più recente può essere trovato qui e l'ultima versione di groovy-all qui.

3. Caratteristiche di base

Ci sono molte funzioni utili in Groovy. Ora, esaminiamo gli elementi costitutivi di base del linguaggio e come differisce da Java.

Ora, esaminiamo gli elementi costitutivi di base del linguaggio e come differisce da Java.

3.1. Digitazione dinamica

Una delle caratteristiche più importanti di Groovy è il supporto per la digitazione dinamica.

Le definizioni dei tipi sono facoltative ei tipi effettivi vengono determinati in fase di esecuzione. Diamo un'occhiata a queste due classi:

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

Queste due classi definiscono lo stesso metodo getName , ma non è definito esplicitamente in un contratto.

Ora, immagina di avere un elenco di oggetti contenenti anatre e gatti che hanno il metodo getName . Con Groovy, possiamo fare quanto segue:

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

Il codice verrà compilato e l'output del codice sopra sarebbe:

Duck Cat

3.2. Conversione implicita della verità

Come in JavaScript, Groovy valuta ogni oggetto come booleano se necessario, ad esempio quando lo si utilizza all'interno di un'istruzione if o quando si nega il valore:

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

Ci sono alcune semplici regole da ricordare su questa conversione:

  • Collezioni, array, mappe non vuote vengono valutate come true
  • Il matcher con almeno una corrispondenza restituisce true
  • Gli iteratori e le enumerazioni con ulteriori elementi vengono forzati a true
  • Le stringhe non vuote , GStrings e CharSequences , vengono forzate a true
  • I numeri diversi da zero vengono valutati come veri
  • I riferimenti a oggetti non nulli vengono forzati a true

Se vogliamo personalizzare la conversione veritiera implicita, possiamo definire il nostro metodo asBoolean () .

3.3. Importazioni

Alcuni pacchetti vengono importati per impostazione predefinita e non è necessario importarli esplicitamente:

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. Trasformazioni AST

Le trasformazioni AST ( Abstract Syntax Tree ) ci consentono di agganciarci al processo di compilazione di Groovy e personalizzarlo per soddisfare le nostre esigenze. Questa operazione viene eseguita al momento della compilazione, quindi non ci sono perdite di prestazioni durante l'esecuzione dell'applicazione. Possiamo creare le nostre trasformazioni AST, ma possiamo anche usare quelle integrate.

Possiamo creare le nostre trasformazioni o possiamo trarre vantaggio da quelle integrate.

Diamo un'occhiata ad alcune annotazioni che vale la pena conoscere.

4.1. Tipo di annotazione verificato

Questa annotazione viene utilizzata per forzare il compilatore a eseguire un rigoroso controllo del tipo per parti di codice annotate. Il meccanismo di controllo del tipo è estensibile, quindi possiamo anche fornire un controllo del tipo ancora più rigoroso di quello disponibile in Java quando lo si desidera.

Diamo un'occhiata all'esempio qui sotto:

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

Se proviamo a compilare questo codice, osserveremo il seguente errore:

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

L' annotazione @TypeChecked può essere applicata a classi e metodi.

4.2. Annotazione CompileStatic

Questa annotazione consente al compilatore di eseguire controlli in fase di compilazione come avviene con il codice Java. Dopodiché, il compilatore esegue una compilazione statica, aggirando così il protocollo metaobject Groovy.

Quando una classe viene annotata, tutti i metodi, proprietà, file, classi interne, ecc. Della classe annotata verranno controllati per tipo. Quando un metodo viene annotato, la compilazione statica viene applicata solo a quegli elementi (chiusure e classi interne anonime) che sono racchiusi da quel metodo.

5. Proprietà

In Groovy, possiamo creare POGO (Plain Old Groovy Objects) che funzionano allo stesso modo dei POJO in Java, sebbene siano più compatti perché getter e setter vengono generati automaticamente per le proprietà pubbliche durante la compilazione. È importante ricordare che verranno generati solo se non sono già definiti.

Questo ci dà la flessibilità di definire gli attributi come campi aperti pur mantenendo la possibilità di sovrascrivere il comportamento quando si impostano o si ottengono i valori.

Considera questo oggetto:

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. Conclusione

Questo articolo ha fornito una rapida introduzione al linguaggio Groovy e alle sue caratteristiche principali. Abbiamo iniziato introducendo concetti semplici come sintassi di base, istruzioni condizionali e operatori. Abbiamo anche dimostrato alcune funzionalità più avanzate come operatori e chiusure.

Se vuoi trovare maggiori informazioni sulla lingua e sulla sua semantica, puoi andare direttamente al sito ufficiale.