Tuesday 5 August 2014

Java8 - Part2

I have covered most of the important topics on Lambda Expressions in my earlier post.

Here, I will be covering on Processing Data with Streams.


Processing Data with Streams

Streams lets you manipulate collections of data in a declarative way and can be processed in parallel without having to write multi-threaded code.

BEFORE (JAVA 7):
List lowCaloricDishes = new ArrayList<>();
for(Dish d: menu){
    if(d.getCalories() < 400){
       lowCaloricDishes.add(d);
     }
} //first we filter the elements, using an accumulator
List lowCaloricDishesName = new ArrayList<>();
Collections.sort(lowCaloricDishes, new Comparator() {
                                   public int compare(Dish d1, Dish d2){
                                      return Integer.compare(d1.getCalories(), d2.getCalories());
                                   }}); //then we sort the dishes, with an anonymous class
for(Dish d: lowCaloricDishes){
   lowCaloricDishesName.add(d.getName());
} //finally we process the sorted list to select the names of Dishes
AFTER (JAVA 8):
List lowCaloricDishesName =
               dishes.parallelStream()
                     .filter(d -> d.getCalories() < 400) //first we select dishes that are below 400 calories
                     .sorted(comparing(Dish::getCalories)) //then we sort them by calories
                     .map(Dish::getName) //we extract the names of these dishes
                     .collect(toList()); //we store all the names into a List

chaining Stream operations forming a Stream pipeline
Stream operations that can be connected are called intermediate operations, and operations that close a stream are called terminal operations.







To summarize, the Streams API in Java 8 lets you write code that is:
  • declarative: more concise and readable 
  • composable: more flexibility 
  • parallelizable: more performance
Stream - “a sequence of elements from a source that supports aggregate operations“

Difference between Collection and Streams
has to do with when things are computed.
A Collection is an in-memory data structure, which holds all the values that the data structure currently has—every element in the Collection has to be computed before it can be added to the Collection.
By contrast a Stream is a conceptually fixed data structure, in which elements are computed on demand or pulled from an existing source.

A Stream is like a lazily constructed Collection: values are computed when they are solicited by a consumer.
A Collection is eagerly constructed

TRAVERSABLE ONLY ONCE
Like Iterator, the Streams can only be traversed once. After that a Stream is said to be “consumed.” You can get a new Stream from the initial data source to traverse it again just like for an Iterator.
List title = Arrays.asList("Java8", "Lambdas", "In", "Action");
Stream s = title.stream();
s.forEach(System.out::println); //  prints each word in the title
s.forEach(System.out::println); // java.lang.IllegalStateException: stream has already been operated upon or closed

COLLECTIONS: EXTERNAL ITERATION
List names = new ArrayList<>();
Iterator iterator = menu.iterator();
while(iterator.hasNext()) { // iterating explicitly
     Dish d = iterator.next();
     names.add(d.getName());
}

STREAMS: INTERNAL ITERATION
List names = menu.stream()
                       .map(Dish::getName) // we parameterize map with the getName method to extract the name of a dish
                       .collect(toList()); // start executing the pipeline of operations, no iteration!

To summarize, working with Streams in general involves three steps:

  1. A data source (such as a Collection) to perform a query on.
  2. A chain of intermediate operations, which form a stream pipeline.
  3. One terminal operation, which executes the stream pipeline and produces a result.
Filtering and Slicing
1. Filtering with a predicate
List vegetarianMenu = menu.stream()
                           .filter(Dish::isVegetarian) // a method reference to check if a dish is vegetarian-friendly!
                           .collect(toList());

2. Filtering unique elements
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
    numbers.stream()
           .filter(i -> i % 2 == 0)
           .distinct()
           .forEach(System.out::println);

3. Truncating a Stream - first elements are returned up to a maximum of n.
 List dishes = menu.stream()
                         .filter(d -> d.getCalories() > 300)
                         .limit(3)
                         .collect(toList());

4. Skipping elements - discards the first n elements
 List dishes = menu.stream()
                         .filter(d -> d.getCalories() > 300)
                         .skip(2)
                         .collect(toList());
Mapping
The Stream API provides map and flatmap which is similar to a "select" statement for selecting a particular column from a table.
Streams support the method map, which takes a function as argument to transform the elements of a Stream into another form.
For example, in the code below we pass a method reference Dish::getName to the map method to extract the names of the dishes in the stream.
List dishNames = menu.stream()
                             .map(Dish::getName)
                             .collect(toList());
To find out the length of the name of each dish, we can do this by chaining another map as follows:
List dishNames = menu.stream()
                             .map(Dish::getName)
                             .map(String::length)
                             .collect(toList());
Falttening Streams

Finding and Matching

anyMatch - “is there an element in the stream matching the given predicate”
if(menu.stream().anyMatch(Dish::isVegatarian)){
      System.out.println(“The menu is (somewhat) vegetarian friendly!!”);
}
allMatch - all the elements of the stream match the given predicate.
boolean isHealthy = menu.stream()
                        .allMatch(d -> d.getCalories() < 1000);
noneMatch - ensures that no elements in the stream match the given predicate.
boolean isHealthy = menu.stream()
                        .noneMatch(d -> d.getCalories() >= 1000);
findAny - returns an arbitrary element of the current stream
The below example finds a dish which is Vegetarian
Optional dish = menu.stream()
                          .filter(Dish::isVegetarian)
                          .findAny();
The Optional class (java.util.Optional) is a container class to represent the existence or absence of a value. The below example would explicitly check the presence of a dish in the Optional object to access its name:
                menu.stream()
                    .filter(Dish::isVegetarian)
                    .findAny(); // returns an Optional
                    .map(Dish::getName) // returns an Optional, NB. This is map from Optional not map from Stream
                    .ifPresent(System.out::println); // if a value is contained it is printed otherwise nothing happens

findFirst - returns the first element of the current stream
For example, the code below, given a list of numbers, finds the first square that is divisible by 3:
 
List someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional firstSquareDivisibleByThree = someNumbers.stream()
                                                           .map(x -> x * x)
                                                           .filter(x % 3 == 0)
                                                           .findFirst(); // 9

Here's some examples of various ways we can use this in Java 8
GIVEN : A Menu which contains a list of Dishes
        List menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));

Dish(String name, boolean vegetarian, int calories, Type type)

Count the number of Dishes:
 
menu.stream().count()
menu.stream().collect(Collectors.counting())

Finding maximum and minimum in a Stream of values
 
 Optional highCalorieDish = menu.stream()
                .collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));
Summarization - the below methods are avaialble in Collectors for Summarization Collectors.summingInt Collectors.summingLong Collectors.summingDouble
 We also have equivalent for average Collectors.averagingInt/averagingLong/averagingDouble
 
int totalCalories =  menu.stream().collect(Collectors.summingInt(Dish::getCalories));
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));

This Collector gathers all that information in a class called IntSummaryStatistics
 
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

IntSummaryStatistics{count=9, sum=4300, min=120,average=477.777778, max=800}

Joining Strings -Collector which reduces a Stream to a single result doesn’t operate on
numeric values but allows a concatenation of Strings
 
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));

Generalized summarization with reduction - Collectors.reducing factory method is a generalization of all of the above specialized cases.
 
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (Integer i, Integer j) -> i + j));

Optional mostCalorificDish =menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

Various ways to find the total number of calories in the menu:
Using Data Processing way :
System.out.println("Using Data Processing way ...with reduce " + menu.stream().map(Dish::getCalories).reduce(0, Integer::sum));
System.out.println("Using Data Processing way ..."               + menu.stream().mapToInt(Dish::getCalories).sum());

Using Data Collecting way
 System.out.println("Using Data Collecting way ..."               + menu.stream().collect(summingInt(Dish::getCalories)));
 System.out.println("Using Data Collecting way with reduction..." + menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum)));

No comments:

Post a Comment