In this article, we’ll have a quick look at some of the most interesting new features in Groovy 3.
Identity Operator
In Groovy 2 to express equal-identity we use the is() expression and == and != to express just equality(through equals() method). This created some confusion, hence in Groovy 3 introduce the operators === and !== like in Javascript for expressing respectively identity equal and non identity equal.
Example
import groovy.transform.EqualsAndHashCode
import groovy.transform.TupleConstructor
@TupleConstructor
@EqualsAndHashCode
class Book { String isbn }
def effectiveJava = new Book(isbn: '0134685997')
def refToEffectiveJava = effectiveJava
def copyOfEffectiveJava = new Book(isbn: '0134685997')
assert refToEffectiveJava === effectiveJava
assert refToEffectiveJava.is(effectiveJava)
assert copyOfEffectiveJava == effectiveJava
assert copyOfEffectiveJava !== effectiveJava
Elvis assignment
In Java, the Elvis operator is used only in a ternary operation(it’s function is equivalent to a java method that tests a condition and depending of the outcome, returns a value)
def variable = book.isbn == null ? '0000000000' : book.isbn
In the example above the variable take 0000000000 if condition is success, otherwise 0134685997
Groovy support a shortcut of this operation in providing a default value:
def variable = book.isbn ?: '0000000000'
We can also use this operator for safe navigation between object in order to easily avoid null pointer exception
def isbn = book?.isbn // return isbn value if book is not null
In Groovy 3 we can use this operator for safe indexing arrays. this allows to access an index of an array, but if the array is null, it will return null instead of throwing a java.lang.NullPointerException.
List<Book> books = null
println books?[1] // now return null
In Groovy 3 also added a new feature for this operator, it’s called Elvis assignement
def book = new Book('0134685997')
book ?= new Book('0000000000')
assert book.isbn == '0134685997'
book = null
book ?= new Book('0000000000')
assert book.isbn == '0000000000'
Negative relational Operator
This is a little shorthand of the negation of the opertaors instanceof and in
// before Groovy 3
assert !('abc' instanceof Integer)
assert !(2 in [1, 3, 5])
// In Groovy 3
assert 'abc' !instanceof Integer
assert 2 !in [1, 3, 5]
@NullCheck annotation
This new annotation automaticaly check all your method parameters, constructor parameters are not null
Example
import groovy.transform.NullCheck
import static groovy.test.GroovyAssert.shouldFail
@NullCheck
class Book {
String isbn
Book(String isbn) {
this.isbn = isbn
}
void write(OutputStream output) {
println("Write book on an output stream")
}
}
shouldFail(IllegalArgumentException) { new Book(null) }
shouldFail(IllegalArgumentException) { new Book('0134685997').write(null) }
Java alignments
Before Groovy 3, some java syntaxes are not supported with Groovy like:
do/while loop
int count = 0;
do {
count++
} while (count < 10)
assert count == 10
Lambda expressions
assert 9 == [1, 2, 3].stream()
.map(n -> n + 1)
.reduce(0, (total, n) -> total + n)
Method reference
assert ['1', '2', '3'] == [1, 2, 3].stream()
.map(Integer::toString)
.collect(toList())
try-with-resources
class Resource implements Closeable {
def logs
void execute() {
logs << 'Executing Resource'
}
void close() {
logs << 'Closing Resource'
}
}
def logs = []
try (def resource = new Resource(logs: logs)) {
resource.execute()
}
assert logs == ['Executing Resource', 'Closing Resource']
// but may be, it's better in Groovy with the extension method withCloseable()
logs = []
new Resource(logs: logs).withCloseable {
it.execute()
}
assert logs == ['Executing Resource', 'Closing Resource']
default method interface
// Note: In Groovy this feature is already implemented with traits
interface Cache {
default void evict() {
println "Evict cache"
}
}