DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Thread-Safety Pitfalls in XML Processing
  • Java Stream API: 3 Things Every Developer Should Know About
  • Understanding Lazy Evaluation in Java Streams
  • Exploring TakeWhile and DropWhile Functions in Java

Trending

  • Build a Simple REST API Using Python Flask and SQLite (With Tests)
  • MCP Servers: The Technical Debt That Is Coming
  • System Coexistence: Bridging Legacy and Modern Architecture
  • Optimizing Integration Workflows With Spark Structured Streaming and Cloud Services
  1. DZone
  2. Coding
  3. Languages
  4. Java Lambda Streams and Groovy Closures Comparisons

Java Lambda Streams and Groovy Closures Comparisons

Want to learn more about the difference between in lambda streams in both Java and Groovy? Check out this post to learn more about the differences between them.

By 
Alex Staveley user avatar
Alex Staveley
·
Aug. 01, 18 · Presentation
Likes (7)
Comment
Save
Tweet
Share
52.3K Views

Join the DZone community and get the full member experience.

Join For Free

In this blog post, we will look at some of the proverbial operations on a list data structure and make some comparisons between Java 8/9 and the Groovy syntax.  First let's talk about the data structure.  Think of it as 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";


Groovy

println players.find{it.name.indexOf("Tadgh") >= 0}


Comments

  • The Java lambda has just one parameter — player.  This doesn't need to be typed, since its type can be inferred.  Note: this lambda only uses one parameter.  If there were two parameters in the parameter list, the parenthesis would be needed around the parameter list.
  • In Java, a stream must be created from the list first.  A lambda is then used before performing a function that will then return 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. However, if you do include brackets, you must include the ; and the return statement.  Note: if your lambda is more than one line, you don't have a choice — you must use  {}.   It is a recommended, best practice to keep lambdas short and 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 that 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 the type predicate.   A functional interface means that it can be used as the assignment target for a lambda expression or method reference.  Along with that, predicate is a type of functional interface.  It's one abstract method is the boolean test(T t). In this case, while using the lambda, the player corresponds to t.  The body definition should evaluate if it is true or a false. In our case, the player.getName().indexOf("Tadgh") will always evaluate it as either true or false. True will correspond 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 no arguments 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 that if you have to specify the parameter type, the declaration must be in brackets. This adds further verbosity.
  • Groovy can println directly.  No System.out  is needed, and there is 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 means that if you forget to ensure your last expression as an appropriate boolean expression, you get unexpected results and bugs at runtime.
  • The arrow operator is used in both Groovy and Java to mean essentially the same thing, separating the parameter list from the body definition. In Groovy, it is only needed if you need to declare the parameters (the default it, doesn't suffice). Note: In Scala, => is used.

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 functional interface of the consumer (see Jdoc here).  Consumer methods are a function that takes an input parameter and returns nothing — it is void.  
  • In Java, the stream.filter()will return another stream. Stream.collect() is one of Java 8's stream terminal methods. It performs mutable fold operations on the data elements held inside the stream instances returned by the filter method.  
  •  Collectors.toList () returns a Collector, which collects all stream elements into a list.
  • When using the toList() collector, you can't assume the type of list that will be used.  If you want more control, you need to use the  toCollection().  For example:  .collect(toCollection(LinkedList::new) 
  • Note: We could have omitted the .collect() operation and invoked forEach straight on the stream.   This would make the Java code shorter.  
players.stream()
   .filter(player -> player.getRating() >= 8)
   .forEach(System.out::println);


  •  System.out::println  is a method reference and is a new feature in Java 8. It is syntactic sugar to reduce the verbosity of some lambdas.  This is essentially saying that for every element in  ratedPlayers, execute the  System.out.println, passing in the the current element as a parameter.
    • Again, we get less syntax from Groovy.  The function can operate on the collection, and 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 the 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 also makes code more verbose. 
    • That said if all you are doing is printing the list, you can just do: 
    players.stream()
       .map(player -> player.getName())
       .forEach(System.out::println);

    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

    • In the Java version, the reduced operation is invoked on the stream.  There are three different versions of this method.   In this version, no initial value is specified, meaning that an optional type is returned.  The input parameter of type  BinaryOperator is a functional interface that means a lamda expression or method reference can be used to specify its value.  In this case, the method reference Integer.max() is used.
    • The null safe operator is used in the Groovy inject closure so that the first comparsion will work.
    • In Java, it is possible to avoid the isPresent check on the optional by just doing...
    players.stream()
      .map(RugbyPlayer::getRating())
      .reduce(Integer::max)
      .map(Object::toString())
      .orElse("No Max");

    Summary

    • Groovy is still far more terse.
    • However, some of the operations in Java are lazily run.  For example,  map() and  filter()  are considered intermediate. They won't execute unless a terminal function, e.g. forEach, collects and reduces on the stream.  This made the code more verbose in some cases, but it also means that it can be more performant.
    • Groovy also offers some lazy functions. 

    The full Java code can be found here. And, the full Groovy code can be found here.



    If you enjoyed this article and want to learn more about Java Streams, check out this collection of tutorials and articles on all things Java Streams.

    Java (programming language) Groovy (programming language) Stream (computing)

    Published at DZone with permission of Alex Staveley, DZone MVB. See the original article here.

    Opinions expressed by DZone contributors are their own.

    Related

    • Thread-Safety Pitfalls in XML Processing
    • Java Stream API: 3 Things Every Developer Should Know About
    • Understanding Lazy Evaluation in Java Streams
    • Exploring TakeWhile and DropWhile Functions in Java

    Partner Resources

    ×

    Comments

    The likes didn't load as expected. Please refresh the page and try again.

    ABOUT US

    • About DZone
    • Support and feedback
    • Community research
    • Sitemap

    ADVERTISE

    • Advertise with DZone

    CONTRIBUTE ON DZONE

    • Article Submission Guidelines
    • Become a Contributor
    • Core Program
    • Visit the Writers' Zone

    LEGAL

    • Terms of Service
    • Privacy Policy

    CONTACT US

    • 3343 Perimeter Hill Drive
    • Suite 100
    • Nashville, TN 37211
    • support@dzone.com

    Let's be friends: