Pengantar Bahasa Groovy

1. Ikhtisar

Groovy adalah bahasa skrip yang dinamis untuk JVM . Ia mengkompilasi ke bytecode dan menyatu secara mulus dengan kode dan pustaka Java.

Pada artikel ini, kita akan melihat beberapa fitur penting Groovy, termasuk sintaks dasar, struktur kontrol, dan koleksi.

Kemudian kita akan melihat beberapa fitur utama yang membuatnya menjadi bahasa yang menarik, termasuk keamanan null, kebenaran implisit, operator, dan string.

2. Lingkungan

Jika kita ingin menggunakan Groovy di proyek Maven, kita perlu menambahkan yang berikut ini ke pom.xml:

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

Plugin Maven terbaru dapat ditemukan di sini dan versi terbaru dari semua groovy di sini.

3. Fitur Dasar

Ada banyak fitur berguna di Groovy. Sekarang, mari kita lihat blok bangunan dasar dari bahasa dan perbedaannya dari Java.

Sekarang, mari kita lihat blok bangunan dasar dari bahasa dan perbedaannya dari Java.

3.1. Pengetikan Dinamis

Salah satu fitur terpenting Groovy adalah dukungannya untuk pengetikan dinamis.

Definisi tipe bersifat opsional dan tipe aktual ditentukan saat runtime. Mari kita lihat dua kelas ini:

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

Kedua kelas tersebut mendefinisikan metode getName yang sama , tetapi tidak didefinisikan secara eksplisit dalam kontrak.

Sekarang, bayangkan kita memiliki daftar objek yang berisi bebek dan kucing yang memiliki metode getName . Dengan Groovy, kita dapat melakukan hal berikut:

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

Kode akan dikompilasi, dan keluaran dari kode di atas adalah:

Duck Cat

3.2. Konversi Kebenaran Implisit

Seperti di JavaScript, Groovy mengevaluasi setiap objek ke boolean jika diperlukan, misalnya saat menggunakannya di dalam pernyataan if atau saat meniadakan nilai:

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

Ada beberapa aturan sederhana yang perlu diingat tentang konversi ini:

  • Koleksi, larik, peta yang tidak kosong bernilai true
  • Pencocokan dengan setidaknya satu kecocokan mengevaluasi ke benar
  • Iterator dan Enumerasi dengan elemen lebih lanjut dipaksa menjadi benar
  • String yang tidak kosong , GString , dan CharSequences , dipaksa menjadi true
  • Angka bukan nol dinilai benar
  • Referensi objek bukan nol dipaksa menjadi true

Jika kita ingin menyesuaikan konversi kebenaran implisit, kita bisa mendefinisikan metode asBoolean () kita .

3.3. Impor

Beberapa paket diimpor secara default, dan kami tidak perlu mengimpornya secara eksplisit:

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

Transformasi AST ( Abstract Syntax Tree ) memungkinkan kita untuk terhubung ke proses kompilasi Groovy dan menyesuaikannya untuk memenuhi kebutuhan kita. Ini dilakukan pada waktu kompilasi, jadi tidak ada penalti performa saat menjalankan aplikasi. Kami dapat membuat transformasi AST kami, tetapi kami juga dapat menggunakan yang sudah ada di dalamnya.

Kita bisa menciptakan transformasi kita, atau kita bisa mendapatkan keuntungan dari yang sudah ada di dalamnya.

Mari kita lihat beberapa anotasi yang perlu diketahui.

4.1. Jenis Anotasi Dicentang

Anotasi ini digunakan untuk memaksa kompilator melakukan pemeriksaan tipe ketat untuk bagian kode yang dianotasi. Mekanisme pemeriksaan jenis dapat diperluas, sehingga kami bahkan dapat menyediakan pemeriksaan jenis yang lebih ketat daripada yang tersedia di Java jika diinginkan.

Mari kita lihat contoh di bawah ini:

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

Jika kami mencoba mengompilasi kode ini, kami akan mengamati kesalahan berikut:

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

The @TypeChecked penjelasan dapat diterapkan untuk kelas dan metode.

4.2. Annotation CompileStatic

Anotasi ini memungkinkan kompilator untuk menjalankan pemeriksaan waktu kompilasi seperti yang dilakukan dengan kode Java. Setelah itu, kompilator melakukan kompilasi statis, sehingga melewati protokol metaobject 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. Kesimpulan

Artikel ini memberikan pengenalan singkat tentang bahasa Groovy dan fitur utamanya. Kami mulai dengan memperkenalkan konsep sederhana seperti sintaks dasar, pernyataan bersyarat, dan operator. Kami juga mendemonstrasikan beberapa fitur yang lebih canggih seperti operator dan closure.

Jika Anda ingin mencari informasi lebih lanjut tentang bahasa dan semantiknya, Anda dapat pergi langsung ke situs resminya.