The new features of Java 8 are mainly Lambda expressions and streams. When streams and Lambda expressions are used together, the characteristics of stream declarative processing of data sets can make the code concise and easy to read
How to simplify the code
If there is a demand, you need to process the dishes queried in the database:
- Select dishes with less than 400 calories
- Sort the selected dishes
- Get the name of the dish after sorting
Dish java
public class Dish { private String name; private boolean vegetarian; private int calories; private Type type; // getter and setter }
Previous implementations of Java 8
private List<String> beforeJava7(List<Dish> dishList) { List<Dish> lowCaloricDishes = new ArrayList<>(); //1. Select dishes with less than 400 calories for (Dish dish : dishList) { if (dish.getCalories() < 400) { lowCaloricDishes.add(dish); } } //2. Sort the selected dishes Collections.sort(lowCaloricDishes, new Comparator<Dish>() { @Override public int compare(Dish o1, Dish o2) { return Integer.compare(o1.getCalories(), o2.getCalories()); } }); //Get the name of the dish after sorting 3 List<String> lowCaloricDishesName = new ArrayList<>(); for (Dish d : lowCaloricDishes) { lowCaloricDishesName.add(d.getName()); } return lowCaloricDishesName; } //Implementation after Java 8 private List<String> afterJava8(List<Dish> dishList) { return dishList.stream() .filter(d -> d.getCalories() < 400) //Select dishes with less than 400 calories .sorted(comparing(Dish::getCalories)) //Sort by calories .map(Dish::getName) //Extract dish name .collect(Collectors.toList()); //Convert to List }
It's done at one go without procrastination. The functions that originally needed to be realized by writing 24 code can now be completed in only 5 lines
After writing the demand happily, there is a new demand at this time. The new demand is as follows:
Classify the dishes found in the database according to the type of dishes, and return a result of map < type, list < dish > >
If you put it before jdk8, your scalp would be numb
Previous implementations of Java 8
private static Map<Type, List<Dish>> beforeJdk8(List<Dish> dishList) { Map<Type, List<Dish>> result = new HashMap<>(); for (Dish dish : dishList) { //Initialize if not present if (result.get(dish.getType())==null) { List<Dish> dishes = new ArrayList<>(); dishes.add(dish); result.put(dish.getType(), dishes); } else { //Add if there is one result.get(dish.getType()).add(dish); } } return result; }
Fortunately jdk8, there is a Stream, so you don't have to worry about complex collection processing requirements anymore
Implementation after Java 8
private static Map<Type, List<Dish>> afterJdk8(List<Dish> dishList) { return dishList.stream().collect(groupingBy(Dish::getType)); }
It's another line of code that solves the demand. I can't help shouting Stream API. You see the powerful function of stream. Next, I'll introduce stream in detail
What is flow
The functions that can be generated from an array of files and processed from a sequence of data sources. Stream is not a set element, it is not a data structure and does not save data. Its main purpose is to calculate
How to generate flow
There are five main ways to generate streams
1. It is generated through collection, which is the most commonly used one in applications
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream();
Generate a stream through the stream method of the collection
2. Generate by array
int[] intArr = new int[]{1, 2, 3, 4, 5}; IntStream stream = Arrays.stream(intArr);
Via arrays The stream method generates a stream, and the stream generated by this method is a numerical stream [i.e. IntStream] rather than stream < integer >. In addition, the use of numerical flow can avoid unpacking in the calculation process and improve the performance.
The Stream API provides mapToInt, mapToDouble and mapToLong to convert the object Stream [i.e. Stream] into the corresponding numerical Stream, and provides the boxed method to convert the numerical Stream into the object Stream
3. Generate by value
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Generate a Stream through the of method of Stream, and generate an empty Stream through the empty method of Stream
4. Generated by file
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
Through files The line method gets a stream, and each stream is a line in a given file
5. Two static methods, iterate and generate, are provided to generate streams from functions through function generation
iterator
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);
The iterate method accepts two parameters. The first is the initialization value and the second is the function operation. Because the stream generated by the iterator is infinite, the stream is truncated by the limit method, and only five even numbers are generated
generator
Stream<Double> stream = Stream.generate(Math::random).limit(5);
The generate method accepts a parameter. The method parameter type is Supplier, which provides a value for the flow. The stream generated by generate is also an infinite stream, so it is truncated by limit convection
Operation type of flow
There are two main types of operations for streams
1. Intermediate operation
A flow can be followed by zero or more intermediate operations. Its main purpose is to open the flow, make some degree of data mapping / filtering, and then return a new flow for the next operation. Such operations are inert. Only calling such methods does not really start the flow traversal. The real traversal needs to wait until the terminal operation. Common intermediate operations include filter, map and so on
2. Terminal operation
A stream has and can only have one terminal operation. When this operation is executed, the stream is closed and can no longer be operated. Therefore, a stream can only be traversed once. If you want to traverse, you need to generate a stream through the source data. Only when the terminal operation is executed can the flow traversal really begin. Such as count, collect and so on, which will be introduced later
Stream usage
The use of stream will be divided into terminal operation and intermediate operation
Intermediate operation
Filter filter
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().filter(i -> i > 3);
The filter method is used to filter conditions. The filter method parameter is a condition
distinct remove duplicate elements
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().distinct();
Rapid removal of duplicate elements by distinct method
limit returns the number of specified streams
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().limit(3);
Specify the number of return streams through the limit method. The parameter value of limit must be > = 0, otherwise an exception will be thrown
skip skips the elements in the stream
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().skip(2);
Skip the elements in the stream through the skip method. The above example skips the first two elements, so the print result is 2,3,4,5. The parameter value of skip must be > = 0, otherwise an exception will be thrown
map stream mapping
The so-called flow mapping is to map the accepted element to another element
List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action"); Stream<Integer> stream = stringList.stream().map(String::length);
The mapping can be completed through the map method. In this example, the mapping of string - > integer is completed. In the previous example, the mapping of dish - > string is completed through the map method
flatMap stream conversion
Converts each value in one stream to another
List<String> wordList = Arrays.asList("Hello", "World"); List<String> strList = wordList.stream() .map(w -> w.split(" ")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList());
The return value of map (W - > w.split ("")) is stream < string [] >. If we want to get stream < string >, we can complete the conversion of stream - > stream through flatMap method
Element matching
Three matching methods are provided
1. allMatch matches all
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().allMatch(i -> i > 3)) { System.out.println("All values are greater than 3"); }
Realized by allMatch method
2. anyMatch matches one of them
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().anyMatch(i -> i > 3)) { System.out.println("There is a value greater than 3"); }
Equivalent to
for (Integer i : integerList) { if (i > 3) { System.out.println("There is a value greater than 3"); break; } }
If there is a value greater than 3, it will print. This function is realized through anyMatch method in java8
3. None matches
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().noneMatch(i -> i > 3)) { System.out.println("All values are less than 3"); }
It is realized by noneMatch method
Terminal operation
Count the number of elements in the flow
1. Through count
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Long result = integerList.stream().count();
Count the number of elements in the output stream by using the count method
2. Through counting
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Long result = integerList.stream().collect(counting());
The last method of counting the number of elements is particularly useful when used in conjunction with collect
lookup
Two search methods are provided
1. findFirst find the first
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
Find the first element greater than three through the findFirst method and print it
2. findAny randomly finds one
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
Find and print one of the elements greater than three through findAny method. For internal optimization reasons, it ends when the first element satisfying greater than three is found. The result of this method is the same as that of findFirst method. findAny method is provided to make better use of parallel streams. findFirst method has more restrictions on parallelism [parallel streams will not be introduced in this article]
reduce combines the elements in the flow
Suppose we sum the values in a set
Before jdk8
int sum = 0; for (int i : integerList) { sum += i; }
jdk8 is processed through reduce
int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
It can be completed in one line. It can also be abbreviated as:
int sum = integerList.stream().reduce(0, Integer::sum);
reduce accepts two parameters: an initial value here is 0 and a binaryoperator < T > accumulator
To combine the two elements to produce a new value,
In addition, the reduce method has an overloaded method without initialization value
Gets the minimum and maximum values in the stream
Obtain the minimum and maximum value through min/max
Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo); Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);
It can also be written as:
OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min(); OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();
min gets the minimum value in the stream, max gets the maximum value in the stream, and the method parameter is comparator <? superT>comparator
Get the minimum and maximum value through minBy/maxBy
Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo)); Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));
minBy gets the minimum value in the stream, maxBy gets the maximum value in the stream, and the method parameter is comparator <? superT>comparator
Get the minimum and maximum values through reduce
Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min); Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);
Sum
Via summingInt
int sum = menu.stream().collect(summingInt(Dish::getCalories));
If the data type is double or long, the summation is performed by summingDouble or summingLong methods
Through reduce
int sum = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
Through sum
int sum = menu.stream().mapToInt(Dish::getCalories).sum();
There are different methods for the same operation when calculating the sum, maximum and minimum values above. collect, reduce and min/max/sum methods can be selected. Min, Max and sum methods are recommended. Because it is the most concise and easy to read. At the same time, mapToInt converts the object stream into a numerical stream, avoiding the boxing and unpacking operations
Average through averaging int
double average = menu.stream().collect(averagingInt(Dish::getCalories));
If the data type is double or long, average through the methods of averagedouble and averagelong
summarizingInt is used to calculate the sum, average, maximum and minimum values at the same time
IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories)); double average = intSummaryStatistics.getAverage(); //Get average int min = intSummaryStatistics.getMin(); //Get minimum value int max = intSummaryStatistics.getMax(); //Get maximum long sum = intSummaryStatistics.getSum(); //Get sum
If the data type is double or long, use summarizingDouble or summarizingLong methods
Element traversal through foreach
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); integerList.stream().forEach(System.out::println);
Before jdk8:
for (int i : integerList) { System.out.println(i); }
After jdk8, it is more convenient to traverse elements. The original for each can be realized directly through the foreach method
Return set
List<String> strings = menu.stream().map(Dish::getName).collect(toList()); Set<String> sets = menu.stream().map(Dish::getName).collect(toSet());
Just a few examples, there are many other methods jdk8 before
List<String> stringList = new ArrayList<>(); Set<String> stringSet = new HashSet<>(); for (Dish dish : menu) { stringList.add(dish.getName()); stringSet.add(dish.getName()); }
Through the use of traversal and return set, it is found that the flow only puts the original external iteration into the interior, which is also one of the main characteristics of the flow. Internal iteration can reduce a lot of code
Splicing elements in a stream by joining
String result = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));
By default, if the map method is not used to map the string returned by the spliced toString method, the method parameter of joining is the delimiter of the element. If it is not specified, the generated string will be a string, which is not readable
Advanced grouping by groupingBy
Map<Type, List<Dish>> result = dishList.stream().collect(groupingBy(Dish::getType));
In the collect method, pass in groupingBy for grouping. The method parameter of groupingBy is the classification function. You can also use groupingBy for multi-level classification through nesting
Map<Type, List<Dish>> result = menu.stream().collect(groupingBy(Dish::getType, groupingBy(dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; })));
Advanced partitioning by
Partition is a special group. Its classification basis is true and false, so the returned results can be divided into two groups at most
Map<Boolean, List<Dish>> result = menu.stream().collect(partitioningBy(Dish :: isVegetarian))
Equivalent to
Map<Boolean, List<Dish>> result = menu.stream().collect(groupingBy(Dish :: isVegetarian))
This example may not be able to see the difference between zoning and classification, and even feel that zoning is not necessary at all. Another obvious example:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));
The key of the return value is still Boolean, but its classification is based on the range. The partition is more suitable for classification based on the range
summary
By using Stream API, you can simplify the code, improve the readability of the code, and quickly use it in the project. Make sense. Before learning Stream API, anyone who writes a lot of Lambda and Stream API in my application wants to give him a kick when flying.