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.
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):
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:
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.
COLLECTIONS: EXTERNAL ITERATION
STREAMS: INTERNAL ITERATION
To summarize, working with Streams in general involves three steps:
1. Filtering with a predicate
2. Filtering unique elements
3. Truncating a Stream - first elements are returned up to a maximum of n.
4. Skipping elements - discards the first n elements
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.
Finding and Matching
anyMatch - “is there an element in the stream matching the given predicate”
The below example finds a dish which is Vegetarian
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:
For example, the code below, given a list of numbers, finds the first square that is divisible by 3:
Here's some examples of various ways we can use this in Java 8
GIVEN : A Menu which contains a list of Dishes
Count the number of Dishes:
Finding maximum and minimum in a Stream of values
We also have equivalent for average Collectors.averagingInt/averagingLong/averagingDouble
This Collector gathers all that information in a class called IntSummaryStatistics
Joining Strings -Collector which reduces a Stream to a single result doesn’t operate on
numeric values but allows a concatenation of Strings
Generalized summarization with reduction - Collectors.reducing factory method is a generalization of all of the above specialized cases.
Various ways to find the total number of calories in the menu:
Using Data Processing way :
Using Data Collecting way
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):
ListAFTER (JAVA 8):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
ListlowCaloricDishesName = 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
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.
Listtitle = 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
Listnames = new ArrayList<>(); Iterator iterator = menu.iterator(); while(iterator.hasNext()) { // iterating explicitly Dish d = iterator.next(); names.add(d.getName()); }
STREAMS: INTERNAL ITERATION
Listnames = 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:
- A data source (such as a Collection) to perform a query on.
- A chain of intermediate operations, which form a stream pipeline.
- One terminal operation, which executes the stream pipeline and produces a result.
1. Filtering with a predicate
ListvegetarianMenu = menu.stream() .filter(Dish::isVegetarian) // a method reference to check if a dish is vegetarian-friendly! .collect(toList());
2. Filtering unique elements
Listnumbers = 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.
Listdishes = menu.stream() .filter(d -> d.getCalories() > 300) .limit(3) .collect(toList());
4. Skipping elements - discards the first n elements
ListMappingdishes = menu.stream() .filter(d -> d.getCalories() > 300) .skip(2) .collect(toList());
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.
ListTo find out the length of the name of each dish, we can do this by chaining another map as follows:dishNames = menu.stream() .map(Dish::getName) .collect(toList());
ListFalttening StreamsdishNames = menu.stream() .map(Dish::getName) .map(String::length) .collect(toList());
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
OptionalThe Optionaldish = menu.stream() .filter(Dish::isVegetarian) .findAny();
menu.stream() .filter(Dish::isVegetarian) .findAny(); // returns an OptionalfindFirst - returns the first element of the current stream.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
For example, the code below, given a list of numbers, finds the first square that is divisible by 3:
ListsomeNumbers = 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
Listmenu = 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
OptionalSummarization - the below methods are avaialble in Collectors for Summarization Collectors.summingInt Collectors.summingLong Collectors.summingDoublehighCalorieDish = menu.stream() .collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));
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)); OptionalmostCalorificDish =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