Core Java
Java Lambda Streams and Groovy Clouses Comparisons
This Blog post will look at some proverbial operations on List data structure and make some comparison between Java 8/9 and Groovy syntax.
So firstly, the data structure. It’s just a simple Rugby player who has name and a rating.
Java
class RugbyPlayer { private String name; private Integer rating; RugbyPlayer(String name, Integer rating) { this.name = name; this.rating = rating; } public String toString() { return name + "," + rating; } public String getName() { return name; } public Integer getRating() { return rating; } } //... //... List<RugbyPlayer> players = Arrays.asList( new RugbyPlayer("Tadgh Furlong", 9), new RugbyPlayer("Bundee AKi", 7), new RugbyPlayer("Rory Best", 8), new RugbyPlayer("Jacob StockDale", 8) );
Groovy
@ToString class RugbyPlayer { String name Integer rating } //... //... List<RugbyPlayer> players = [ new RugbyPlayer(name: "Tadgh Furlong", rating: 9), new RugbyPlayer(name: "Bundee AKi", rating: 7), new RugbyPlayer(name: "Rory Best", rating: 8), new RugbyPlayer(name: "Jacob StockDale", rating: 8) ]
Find a specific record
Java
// Find Tadgh Furlong Optional<RugbyPlayer> result = players.stream() .filter(player -> player.getName().indexOf("Tadgh") >= 0) .findFirst(); String outputMessage = result.isPresent() ? result.get().toString() : "not found"; System.out.println(outputMessage);
Groovy
println players.find{it.name.indexOf("Tadgh") >= 0}
Comments
- The Java lambda has just one parameter in – player. This doesn’t need to be typed as its type can be inferred. Note: this lambda only uses one parameter. If there were two parameters in the parameter list, parenthesis would be needed around the parameter list.
- In Java, a stream must be created from the List first. A lambda is then used to before performing a function which returns an Optional
- The lambda definition doesn’t need a return statement. It also doesn’t need {} braces or one of those semi-colons to complete a Java statement. However, you can use {} if you want to and if you want to, you must include the ; and the return statement. Note: if you lambda is more than one line, you don’t get a choice, you must use {}. It is recommended best practise to keep Lambda’s short and to just one line.
- Java 8 supports fluent APIs for pipeline Stream operations. This is also supported in Groovy Collection operations.
- In Java a player variable is specified for the Lambda. The Groovy closure doesn’t need to specify a variable. It can just use “it” which is the implicit reference to the parameter (similar to _ in Scala).
- The Java filter API takes a parameters of type Predicate. A Functional Interface means: can be used as the assignment target for a lambda expression or method reference. Predicate, is type of Functional interface. It’s one abstract method is: boolean test(T t). In this case, in the lamda, the player corresponds to t. The body definition should evaluate to a true or a false, in our case player.getName().indexOf(“Tadgh”) will always evaluate to true or false. True corresponds to a match.
- Java 8 has other types of Functional Interfaces:
- Function – it takes one argument and returns a result
- Consumer – it takes one argument and returns no result (represents a side effect)
- Supplier – it takes not argument and returns a result
- Predicate – it takes one argument and returns a boolean
- BiFunction – it takes two arguments and returns a result
- BinaryOperator – it is similar to a BiFunction, taking two arguments and returning a result. The two arguments and the result are all of the same types
- UnaryOperator – it is similar to a Function, taking a single argument and returning a result of the same type
- Java 8 can infer the type for the lambda input parameters. Note if you have to specify the parameter type, the declaration must be in brackets which adds further verbosity.
- Groovy can println directly. No System.out needed, and no need for subsequent braces.
- Like Java, Groovy doesn’t need the return statement. However, this isn’t just for closures, in Groovy it extends to every method. Whatever is evaluated as the last line is automatically returned.
- Groovy has no concept of a Functional interface. This can mean if you forget to ensure your last expression is an appropriate boolean expression, you get unexpected results and bugs at runtime.
- The arrow operator is used in both Groovy and Java to mean effectively the same thing – separating parameter list from body definition. In Groovy it is only needed it you need to declare the parameters (the default it, doesn’t suffice). Note: In Scala, => is used.
Find specific records
Java
// Find all players with a rating over 8 List<RugbyPlayer> ratedPlayers = players.stream() .filter(player -> player.getRating() >= 8) .collect(Collectors.toList()); ratedPlayers.forEach(System.out::println);
Groovy
println players.findAll{it.rating >= 8}
Comments
- In the Java version, the Iterable Object ratedPlayers has its forEach method invoked. This method takes a FunctionalInterface of type Consumer (see Jdoc here). Consumer, methods a function which takes an input parameter but returns nothing, it is void.
- System.out::println is a method reference – a new feature in Java 8. It is syntactic sugar to reduce the verbosity of some lambdas. This is essentially saying, for every element in ratedPlayers, execute, System.out.println, passing in the the current element as a parameter.
- Again less syntax from Groovy. The function can operate on the collection, there is no need to create a Stream.
- We could have just printed the entire list in the Java sample, but heck I wanted to demo forEach and method reference.
Map from object type to another
Java
// Map the Rugby players to just names. // Note, the way we convert the list to a stream and then back again to a to a list using the collect API. System.out.println("Names only..."); List<String> playerNames = players.stream().map(player -> player.getName()).collect(Collectors.toList()); playerNames.forEach(System.out::println);
Groovy
println players.collect{it.name}
Comments
- A stream is needed to be created first before executing the Lambda. Then the collect() method is invoked on the Stream – this is needed to convert it back to a List. This makes code more verbose.
Perform a Reduction calculation
Java
System.out.println("Max player rating only..."); Optional<Integer> maxRatingOptional = players.stream().map(RugbyPlayer::getRating).reduce(Integer::max); String maxRating = maxRatingOptional.isPresent() ? maxRatingOptional.get().toString() : "No max"; System.out.println("Max rating=" + maxRating);
Groovy
def here = players.inject(null){ max, it -> it.rating > max?.rating ? it : max }
Comments
- The null safe operator is used in the Groovy inject closure – so that the first comparsion will work
Summary
- Groovy is still far more terse
- However, some of the operations in Java are lazily run. For example map(), filter() which are considered intermediate. They won’t execute unless a terminal function e.g. forEach, collect, reduce is invoked on the stream. This may the code more verbose in cases, but it also means it can be more performant.
- Groovy also offers some lazy functions.
Full Java code here. Full Groovy code here.
Published on Java Code Geeks with permission by Alex Staveley, partner at our JCG program. See the original article here: Java Lambda Streams and Groovy Clouses Comparisons Opinions expressed by Java Code Geeks contributors are their own. |