BT

InfoQ Homepage Articles Groovy 3.0 Adds New Java-Like Features

Groovy 3.0 Adds New Java-Like Features

Lire ce contenu en français

Bookmarks

Key Takeaways

  • Groovy 3 tries to close some of the feature gaps that have opened up with recent versions of Java
  • New control flow features include do-while and the enhanced for loop
  • Java-style Lambda Expressions have been added, to sit alongside Groovy closures
  • Automatic Resource Management (try-with-resources) also arrives
  • Groovy's set of operators has been enhanced with a few omissions (such as ? in collection indexing) corrected

Apache Groovy is an open source, object-oriented language that runs on the Java Virtual Machine (JVM). Groovy is compatible with Java syntax and in some ways more powerful than Java as it is both dynamically and statically typed (using the def keyword). Groovy is both a programming language and a scripting language. Groovy features not in Java include support for Domain-specific Languages (DSLs), and meta-programming.

Problem

Even though Groovy 2.x is Java syntax compatible and compiles to JVM bytecode it does not support several of the features Java does. Java code is not fully integrable into Groovy 2.x code directly. As an example, method references and lambda expressions are not supported and would need to be converted to Groovy closures.

Solution

Groovy 3 adds several new features taken from Java making Groovy more integrable and interoperable with Java.

The new Groovy 3 features that are influenced by Java’s style include the control-flow do-while loop, enhanced for loop with support for comma separated expressions and multi-assignment statements, array initialization using curly brackets {}, lambda expressions, method references, try-with-resources statement, anonymous code blocks, non-static inner class instantiation, and default implementation for interface methods. In this article we shall discuss these and other new features in the Groovy 3.0.0.

Setting up the Environment

Download and install the following software:

Do-while Control-flow Statement

The do-while statement in Java evaluates the statements within a do block while an expression is true and has the following syntax.

do {
statement/s
} while (an_expression);

The statement/s within the do block are at least run once and after each run the expression an_expression is evaluated.

Groovy 3.0.0 has added support for the do-while statement. An example of using the do-while statement in Groovy is as follows in which value of x is output and incremented using a postfix increment operator in the do block. The expression in while evaluates if x is less than or equal to 10.

Create a Groovy script (hello.groovy) and copy the following listing to the script.

def x = 5;
do{
println("x is " + x);
x++;
} while (x <= 10);

Run the script with groovy hello.groovy command and the same output as with Java is output as follows:

x is 5
x is 6
x is 7
x is 8
x is 9
x is 10

Improved for-loop

The for statement already exists in Groovy 2.x but it does not support exactly the same syntax as in Java. To address this Groovy 3.0.0 has added support for multiple, comma separated expressions, in the initialization and increment expressions in a for statement. For example:

for(int i = 1,j = 5; i < 6; i++,j--){
println("i is: " + i);
println("j is: " + j);
}

Run the preceding Groovy script and the same output as in Java gets generated.

i is: 1
j is: 5
i is: 2
j is: 4
i is: 3
j is: 3
i is: 4
j is: 2
i is: 5
j is: 1

Multi-assignment has been supported in Groovy since version 1.6. Groovy 3.0.0 extends this support and has added multi-assignment statements in a for loop as in the following script:

def xy = []
for (def (String x, int y) = ['groovw', 1]; y < 4; x++, y++) {
xy << "$x $y"
}
println xy

If we run the preceding script the following output is generated.

[groovw 1, groovx 2, groovy 3]

Array Initialization with {}

Groovy has been omitting support for Java-style curly braces {} in array initialization as it could lead to confusion with the closures syntax, which also makes use of curly braces.

But, the closures syntax is really different than the array initialization syntax in which the curly braces follow an array type declaration. Consider the following array initialization expression from Java.

String[] strArray=new String[] {"A","B","C"};

Groovy 3.0.0 has added support for the array initialization syntax in which the curly braces follow an array type declaration as demonstrated with the following array initialization example.

def strArray=new String[] {"A","B","C"}
println strArray.size()
println strArray[0]

Run the preceding Groovy script to generate the following output:

3
A

Lambda Expressions

Java added support for lambda expressions in JDK 8 as an alternative to anonymous classes to implement interfaces that declare a single method such as the java.lang.Runnable interface, which declares a single method run(). The syntax for a lambda expression consists of an argument list followed by an arrow followed by a statement block or expression, as an example:

Runnable r2 = () -> System.out.println("Hello from Lambda Expression");

Or:

Runnable r2 = () -> {}

The run() method may be invoked as follows:

r2.run();

In Groovy 2.x Java-style lambda expressions are converted to closures. Closures lack the same level of type inference and type checking and as a result the same level of performance that lambda expressions offer. Groovy 3.0.0 has added support for lambda expressions. As an example, consider the following Groovy script:

def r2 = () -> System.out.println("Hello from Lambda Expression");
r2.run();

Run the Groovy script to generate the same output as with Java as follows:

Hello from Lambda Expression

Consider the following statement in Groovy that makes use of a closure.

def multiply = { x, y ->
return x*y
}
println multiply(2, 3)

When the Groovy script is run the output is 6.

In Groovy 3.0.0 the closure may be replaced with a lambda expression as in the following example:

def multiply = (int x, int y) -> { return x*y }
println multiply(2, 3)

If curly braces are not specified (as below) the expression after the arrow -> is evaluated and returned from a lambda expression as follows:

def multiply = (int x, int y) -> x*y

Parameter types are optional in the argument list as below:

def multiply = (x,y) -> x*y

If statement/s is/are used after the -> curly braces {} are required as in:

def multiply = (x,y) -> { z=x*y; println z}

Groovy syntax allows default value in the argument list, which has no equivalent in Java, as in:

def multiply = (x,y=1) -> x*y

As in Java no parentheses are needed for a single parameter with no type.

def hello = x -> "hello "+x

Method References

Method references in Java provide a simplified syntax for named methods as an alternative to using lambda expressions to create anonymous methods. Method references make use of the :: operator. As an example, consider the following declaration for parameterized List.

List<String> list = Arrays.asList("Hello", " Deepak", " and"," Hello"," John");

The following statement makes use of a lambda expression to iterate over the list and output the String values.

list.forEach((String s)->System.out.println(s));

The output from the preceding statements is as follows:

Hello
Deepak
and
Hello
John

A method reference could be used to replace the lambda expression for a simplified syntax.

list.forEach(System.out::println);

In Groovy 2.x a method reference is converted to a method closure. Groovy 3.0.0 adds support for method references. The same method reference example may be run as a Groovy script to generate the same output.

In the preceding example an instance method is invoked with a method reference. As another example of method reference to invoke an instance method convert a list of String values to uppercase. The following Groovy snippet makes use of a method reference to invoke the toUpperCase instance method in class String.

import java.util.stream.Collectors;
def list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");

def upperCaseList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
println upperCaseList

Run the preceding Groovy script to generate the following output:

[A, B, C, D, E, F, G, H, I, J]

As an example of using a method reference to invoke a static method consider the following Groovy script in which a list of String values is converted to a list of integers using static method valueOf(String) in Integer class. A filter is used to include only odd integers.

import java.util.stream.Collectors;
def list = Arrays.asList("1","2","3","4","5","6","7","8","9","10");

def subList = list.stream().map(Integer::valueOf).filter(number -> number % 2 == 1).collect(Collectors.toList());

println subList

Run the Groovy script to output odd integer values:

[1, 3, 5, 7, 9]

Anonymous Code Blocks

An anonymous code block in Java is a statement or group of statements enclosed within curly braces {}. As an example the following is an anonymous code block.

{
int x = 5;
x--;
System.out.println(x);
}

If run in JShell the code block outputs 4. Anonymous code blocks in Java are often used to restrict a variable scope. As an example, run the following try-catch statement after the preceding code block.

try {

x++;
} catch(Exception ex) {
System.out.println(ex.getMessage());
}

An error message is generated indicating that x is not defined.

cannot find symbol
| symbol: variable x
| x++;
| ^

Groovy 3.0.0 has added support for anonymous code blocks. The following Groovy script defines an anonymous code block that outputs 4.

{
def x = 5
x--
println(x)
}

The scope of x is only within the code block. To demonstrate, run the following try-catch statement after the preceding code block.

try {
x++
} catch(Exception ex) {
println ex.message
}

An error message gets output indicating that x is not defined.

No such property: x for class: hello

A nested anonymous code block defines another scope as in the following Groovy script that declares two variables called "a", each in a different block.

{
{

def a = "Groovy"
def size= a.length()
println size
}
int a = 1
println a
}

When the script is run the value of "a" from both the blocks is output.

6
1

If a code block is defined after a method call in Groovy it is considered as passing a closure as the last parameter of the method call. As an example, define a method that defines a vararg parameter of type Object and outputs the number of args passed to the method. Invoke the method and subsequently define an anonymous code block.

def hello(Object... args) {println args.length }
hello(1,2)
{5}

The output from the preceding Groovy script might be expected to be 2, but is actually 3. To not pass the anonymous code block as the last parameter to the method call add a ";" after the method call as in:

def hello(Object... args) {println args.length }
hello(1,2);
{5}

Non-static Inner Class Instantiation

Java supports non-static inner class instantiation using the new operator on an instance of the outer class. As an example declare a class Hello and declare an inner class Groovy.

public class Hello{

public class Groovy{
int x;
public Groovy(int x){
this.x=x;
}
}
}

Next, create an instance of the outer class Hello and instantiate the inner class as follows.

int y=new Hello().new Groovy(5).x;

Output the value of y.

System.out.println(y);

Run the preceding inner class instantiation example in JShell and a value of 5 is output.

Groovy 3.0.0 has added support for non-static inner class instantiation. As an example, create a Groovy script that declares an inner class Hello2 within an outer class Hello. The inner class declares an instance method that outputs a message "Hello Groovy". The outer class declares a static method that takes an instance of the outer class as a parameter and returns an instance of the inner class. The inner class instantiation is demonstrated using the new operator. The main method invokes the static method in the outer class with an instance of the outer class and the inner class method is invoked on the inner class instance returned. The Groovy script (hello.groovy) is listed:

public class Hello {
public class Hello2 {

public void hello() {
println "Hello Groovy"
}
}

public static Hello2 createHello2(Hello y) {
return y.new Hello2();
}
static void main(String... args) {
Hello.createHello2(new Hello()).hello()


}
}

Run the Groovy script with the following output.

Hello Groovy

Default Method Implementation in Interfaces

Default method implementation for interface methods feature was added in JDK 8 to make it feasible to add new functionality to an interface without breaking binary compatibility with the older version/s of the interface. Without default method implementation in an interface if new functionality is added to an interface it would break binary compatibility with classes that implement an older version of the interface. Groovy 2.x implements interface default methods as traits. Groovy 3.0.0 adds experimental support for default method implementation in interfaces. As an example declare an interface that includes a default implementation for a method and declare a class HelloImpl that implements the interface. Copy the following listing to a Groovy script (hello.groovy).

class HelloImpl implements Hello {
static void main(String... args) {
def hello=new HelloImpl()
hello.hello("Deepak")
}

}

interface Hello {

default void hello(String name){
println "Hello " +name
}
}

Run the Groovy script to generate the output:

Hello Deepak

Try-with-resources Statement

Java supports the try-with-resources statement as a variation of the try statement to declare one or resources with a resource being an object that needs to be closed after the program has run. An example of a resource would be a JDBC Statement object. Groovy 3.0 has added support for try-with-resouces. As an example the following Groovy script makes use of a try-with-resources statement to declare a Statement resource.

import java.sql.*

public class JDBC {

static void main(String[] args) {

try {
def connection
= DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql?" + "user=root&password=mysql")
query(connection)
} catch (SQLException e) {
}
}

public static void query(Connection connection) throws SQLException {

def query = "select * from Catalog"

try (def stmt = connection.createStatement()) {
def rs = stmt.executeQuery(query)
while (rs.next()) {

}
} catch (SQLException e) {
println e.getMessage()
}
}

}

The !in and !instanceof operators

To use the negated form in Groovy 2.x the expressions that contains the instanceof and in operators must be bracketed. As an example:

def x=5;
assert !(x instanceof List)
assert !(x in [1,2,3,4,5,6])

Groovy 3.0.0 has added support for the !instanceof and the !in operators with which brackets are not needed. The preceding script could be used as follows.

def x=5;
assert x !instanceof List
assert x !in [1,2,3,4,5,6]

Both scripts generate the same output:

Caught: Assertion failed:

assert x !in [1,2,3,4,5,6]
| |
5 false

Assertion failed:

assert x !in [1,2,3,4,5,6]
| |
5 false

at Hello.run(Hello.groovy:3)

Identity Comparison operators

Groovy 3.0.0 adds a new identity operator === which is same as the is() method. The following script illustrates the use of === with the equivalent is() method call also used.

def a = ['A','B','C']
def b=a

assert a === b
assert a.is(b)

For the equivalent negated form of is() a new operator !== is added. The following Groovy script illustrates the !== operator with the negated is() also used.

def c= ['A','B','C']

assert a !== c
assert !a.is(c)

New parser and short form for assignment operator

Groovy 3.0.0 has added a new parser, called the Parrot parser, that is more flexible and maintainable than the older parser and with support for additional syntax options and features. The new parser is enabled by default.

Groovy 2.x already supports a short-form of the ternary operator. A ternary operator returns a default value if an expression returns false. Groovy 3.0.0 adds a short form for the assignment operator. As an example, in the following Groovy script name variable is assigned a default value.

import groovy.transform.ToString

@ToString
class Hello {
String name

}

def hello = new Hello(name: 'Deepak')
hello.with {
name ?= 'John'
}

assert hello.toString() == 'Hello(Deepak)'

Safe indexing

Groovy collections such as arrays, lists and maps may be accessed using an index. Safe indexing refers to accessing a collection index to get/set a value with the safe deferencing operator ?. As an example, declare an array to be null.

def anArray = null

The following statement that makes use of the ? operator runs fine as null is returned for all index values.

assert null==anArray?[0]

If the ? operator were not used the following error message would get generated.

Caught: java.lang.NullPointerException: Cannot invoke method getAt() on null object

The following statement that makes use of the ? operator accesses a null object’s index to assign a value and is ignored instead of generating an error message.

anArray?[0]='a'//ignores

Similarly, the following statement runs fine as null is returned for all index values.

assert null==anArray?['a']

And the following statement that makes use of the ? operator to access a null object’s index to set a value is ignored without generating an error message.

anArray?['a']='a'//ignored

Another new feature in Groovy 3.0.0 is embeddable Groovydoc comments.

Summary

In this article we discussed new features in the Groovy 3.0.0. Most of the new features are taken from Java including support for the do-while statement, Java-style for statement with multiple expressions in the initialization and increment expressions, lambda expressions, method references, anonymous code blocks, non-static inner class instantiation, default methods in interfaces, and try-with-resources statement. Groovy specific new features include some new operators and safe indexing.

About the Author

Deepak Vohra is a Sun Certified Java Programmer and Sun Certified Web Component Developer. Vohra has published Java and Java EE related technical articles in  WebLogic Developer's Journal, XML Journal, ONJava, java.net, IBM developerWorks, Java Developer’s Journal, Oracle Magazine, and devx. Vohra has published five books on Docker and is a Docker Mentor.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Is your profile up-to-date? Please take a moment to review and update.

Note: If updating/changing your email, a validation request will be sent

Company name:
Company role:
Company size:
Country/Zone:
State/Province/Region:
You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.