Java Streams API Preview

Edwin DalorzoFor today’s post I want to share a series of examples that I have developed while trying the latest features in the Java 8 Stream API. In my last post I did a comparison of the features with those available in LINQ, and for today’s post I have decided to go a bit further and try to use the API to work with a small domain model. The examples developed here are based on the same examples presented in the LambdaJ Project.

The Data Model

For the examples I will use the following domain model:

lambda-model

You can see a full implementation of all the examples developed here by downloading the following Gist.

For the examples presented below assume that  in the context of the code there are three streams always available:

  • Stream<Person> persons.
  • Stream<Car> cars.
  • Stream<Sale> sales.

Challenge 1: Print All Car Brands

From a collection of cars print all car brands.

StringJoiner brands = cars.map(Car::getBrand)
                          .collect(toStringJoiner(","));
String allBrands = brands.toString();

For this example I have also use the new StringJoiner class.

Challenge 2: Select all Sales on Toyota

From a collection of sales, select all those that are related to Toyota cars.

List<Sale> toyotaSales;

toyotaSales = sales.filter(s -> s.getCar().getBrand().equals("Toyota"))
                   .collect(toList());

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

For this example I could have also used the forEach method in the stream to get all the sales printed. I did it this way just to illustrate that it is possible to collect all items in the stream into a list and from there we can process them. But ideally, I should have processed the items directly in the stream.

Challenge 3: Find Buys of the Youngest Person

From a collection of sales, find all those that are from the youngest buyer.

ToIntFunction<Entry<Person, List<Sale>>> byAge;
byAge = e -> e.getKey().getAge();
byYoungest = sales.collect(groupingBy(Sale::getBuyer))
                  .entrySet()
                  .stream()
                  .sorted(comparing(byAge))
                  .map(Entry::getValue)
                  .findFirst();
if(byYoungest.isPresent()) {
 System.out.println(byYoungest.get());
}

Challenge 4: Find Most Costly Sale

From a collection of sales, find the most costly of all of them.

Optional<Sale> mostCostlySale;
Comparator<Sale> byCost = comparing((ToDoubleFunction<Sale>)Sale::getCost)
                          .reverseOrder();

mostCostlySale = sales.sorted( byCost )
                      .findFirst();

if(mostCostlySale.isPresent()) {
	System.out.println(mostCostlySale.get());
}

Challenge 5: Sum of Sales from Male Buyers & Sellers

From a collection of sales find the sum of all buys/sells made by men.

double sum = sales.filter(s -> s.getBuyer().isMale()
                               && s.getSeller().isMale())
                  .mapToDouble(Sale::getCost)
                  .sum();

Challenge 6: Find the Age of the Youngest Buyer

From a collection of sales, find the age of the youngest buyer who bought for more than 40,000.

OptionalInt ageOfYoungest;

ageOfYoungest = sales.filter(sale -> sale.getCost() > 40000)
                     .map(Sale::getBuyer)
                     .mapToInt(Person::getAge)
                     .sorted()
                     .findFirst();

if(ageOfYoungest.isPresent()) {
	System.out.println(ageOfYoungest.getAsInt());
}

Challenge 7: Sort Sales by Cost

Sort a collection of sales by cost.

Comparator<Sale> byCost= comparing((ToDoubleFunction<Sale>) Sale::getCost);
List<Sale> sortedByCost;

sortedByCost = sales.sorted( byCost )
                    .collect(toList());

Challenge 8: Index Cars by Brand

From a collection of cars, index cars by the their brand.

Map<String,List<Car>> byBrand;
byBrand = cars.collect( groupingBy(Car::getBrand ));

Challenge 9: Find Most Bought Car

From a collection of sales find the most bought car.

ToIntFunction<Entry<Car,List<Sale>>> toSize = (e -> e.getValue().size());

Optional<Car> mostBought;

mostBought = sales.collect( groupingBy(Sale::getCar) )
                  .entrySet()
                  .stream()
                  .sorted( comparing(toSize).reverseOrder() )
                  .map(Entry::getKey)
                  .findFirst();

if(mostBought.isPresent()) {
   System.out.println(mostBought.get());
}

Related Posts

Futher Reading

About these ads

15 thoughts on “Java Streams API Preview

  1. Pingback: Java Streams Preview vs .Net LINQ | Informatech CR Blog

  2. Pingback: Java Infinite Streams | Informatech CR Blog

  3. There’s a problem with the solution to No. 3: if you have more than one equally-youngest buyers, the addition to TreeMap won’t work the way you expect — key equality for a TreeMap is defined by the compare (or compareTo) method — so you get a list of sales for /all/ the equally-youngest buyers. You can fix this by including extra sort keys in the Comparator “byAge” to resolve ties between equally-youngest buyers; assuming you’ve done that there is a slightly neater solution than yours available:
    byYoungest = sales.collect(groupingBy(Sale::getBuyer, () -> new TreeMap(byAge)))
    .values()
    .stream()
    .findFirst();

    An alternative fix is not to use TreeMap:
    byYoungest = sales.collect(groupingBy(Sale::getBuyer))
    .entrySet()
    .stream()
    .sorted(comparing((Entry<Person, List> e) -> e.getKey().getAge()))
    .map(Entry::getValue)
    .findFirst();

    • Thanks for your comments Maurice. This is great! I will try to update the article with all your recommendations. Man, I’ve got to tell you I just can’t believe you dropped by my blog. You just made my day.

  4. There’s a problem with No. 3 if you have more than one equally-youngest buyer. Addition to a TreeMap is done using byAge.compare() as the key equality function, so the sales of all the buyers of any age are added to the sales of the /first/ buyer of that age. You can fix this by adding extra sort keys to the Comparator “byAge”, in which case there is a slightly neater solution than yours available:
    byYoungest = sales.collect(groupingBy(Sale::getBuyer, () -> new TreeMap(byAge)))
    .values()
    .stream()
    .findFirst();

    or (longer but clearer) you can avoid using TreeMap altogether:
    byYoungest = sales.collect(groupingBy(Sale::getBuyer))
    .entrySet()
    .stream()
    .sorted(comparing((Entry<Person, List> e) -> e.getKey().getAge()))
    .map(Entry::getValue)
    .findFirst();

    The problem should probably be improved to specify exactly what behaviour is required when there is more than one equally-youngest buyer.

  5. In No. 6 you can avoid the call to boxed() by instead returning an OptionalInt.

    (Nice post, by the way – thanks!)

  6. Pingback: 10 Subtle Best Practices when Coding Java | Java, SQL, and jOOQ / jOOX

  7. With the final API for Collector, all of the examples in Mario’s presentation can be easily expressed using the Stream API and Collector. I think this post was done before the API was finalized, so there are some that were impractical at the time, but practical now.

    There’s a more efficient solution to Challenge #9, that doesn’t have to keep the whole data set in the map just to determine purchase frequency:

    Map histogram = sales.collect(groupingBy(Sale::getCar, counting()));
    Optional mostBought = histogram.entrySet()
    .sorted(Map.Entry.comparingByValue().reversed())
    .map(Entry::getKey)
    .findFirst();

  8. Also, in #3, 4, 6, and 9, you can eliminate the ‘garbage variable’ and express as a single stream expression by restructuring :

    ToIntFunction<Entry<Person, List>> byAge;
    byAge = e -> e.getKey().getAge();
    byYoungest = sales.collect(groupingBy(Sale::getBuyer))
    .entrySet()
    .stream()
    .sorted(comparing(byAge))
    .map(Entry::getValue)
    .findFirst();
    if(byYoungest.isPresent()) {
    System.out.println(byYoungest.get());
    }

    to

    sales.collect(groupingBy(Sale::getBuyer))
    .entrySet()
    .stream()
    .sorted(comparingInt(e -> e.getKey().getAge()))
    .map(Entry::getValue)
    .findFirst()
    .ifPresent(b – > System.out.println(b));

    • Wow, this is great, Brian! Thanks for taking the time to post your feedback. I love what you guys did with the JDK 8. I have been playing with it for like a year. It is true, I did the examples above months ago and since then you guys have done many changes and improvements in the API and in the compiler and so many of this snippets could be rewritten. I will try your suggestions, as matter of fact, I wrote all these challenges over again a few weeks ago when you guys added improved inference to the compiler. but I don’t recall at this point how I implemented them. I guess I must write a new post and include the new solutions, including those strategies that you suggest.

  9. Pingback: Java 8 Friday: The Best Java 8 Resources – Your Weekend is Booked | Java, SQL and jOOQ.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s