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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

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

  • Building Resilient Networks: Limiting the Risk and Scope of Cyber Attacks
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  • Building Custom Tools With Model Context Protocol
  • The Human Side of Logs: What Unstructured Data Is Trying to Tell You
  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
    Oops! Something Went Wrong

    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:

    Likes
    There are no likes...yet! 👀
    Be the first to like this post!
    It looks like you're not logged in.
    Sign in to see who liked this post!