Why Steam is needed
Steam in Java 8 is an enhancement of collection object function. It focuses on a variety of very convenient and efficient aggregate operations or bulk data operations for collection objects
With the help of the same new Lambda expression, the Steam API greatly improves programming efficiency and program readability. At the same time, it provides two modes for aggregation operation: traversal mode and parallel mode. The concurrent mode can make use of the advantages of multi-core processor and use the fork/join parallel wizard to separate tasks and accelerate processing
It is usually difficult and error prone to write parallel code, but using the Steam API can easily write high-performance concurrent code without writing a line of multithreaded code
java.util.stream, which first appeared in Java 8, is a product of the comprehensive influence of functional language + multi-core era
What is aggregation operation
TODO
(requirement: if all transactions of type grocery are found, then the transaction ID set sorted in descending order of transaction value is returned.)
public class Transaction { private final int id; private final Integer value; private final Type type; public Transaction(int id, Integer value, Type type) { this.id = id; this.value = value; this.type = type; } public enum Type { A, B, C, D, GEOCERY } public int getId() {return id;} public Integer getValue() {return value;} public Type getType() {return type;} }
Listing 1. Java 7 sorting, value implementation
public static void main(String[] args) { List<Transaction> transactions = new ArrayList<>(); transactions.add(new Transaction(1, 10, Transaction.Type.GEOCERY)); transactions.add(new Transaction(3, 30, Transaction.Type.GEOCERY)); transactions.add(new Transaction(6, 60, Transaction.Type.GEOCERY)); transactions.add(new Transaction(5, 50, Transaction.Type.GEOCERY)); transactions.add(new Transaction(2, 20, Transaction.Type.A)); transactions.add(new Transaction(4, 40, Transaction.Type.C)); // JDK 7 finds all transactions of type grocery List<Transaction> groceryTransactions = new ArrayList<>(); for (Transaction t : transactions) { if (t.getType() == Transaction.Type.GEOCERY) { groceryTransactions.add(t); } } // Set sort transaction value descending sort Collections.sort(groceryTransactions, new Comparator<Transaction>() { @Override public int compare(Transaction o1, Transaction o2) { return o2.getValue().compareTo(o1.getValue()); } }); // Transaction ID acquisition List<Integer> transactionIds = new ArrayList<>(); for (Transaction t : groceryTransactions) { transactionIds.add(t.getId()); } System.out.println(transactionIds);//[6, 5, 3, 1] }
Listing 2. Java 8 sorting, value implementation
// JDK 8 if all transactions of type grocery are found, then the transaction ID set sorted in descending order of transaction value is returned List<Integer> transactionsIds = transactions.parallelStream().filter(t -> t.getType() == Transaction.Type.GEOCERY) .sorted(Comparator.comparing(Transaction::getValue).reversed()) .map(Transaction::getId) .collect(Collectors.toList()); System.out.println(transactionsIds);//[6, 5, 3, 1]
Steam overview
There are two types of operations for streams:
Intermediate: a flow can be followed by zero or more intermediate operations. Its purpose is to open the flow, make some degree of data mapping / filtering, and then return a new flow to the next operation. This kind of operation is Lazy, that is to say, just calling this kind of method does not really start the flow traversal
Terminal: a flow can only have one terminal operation. When this operation is executed, the flow will be used with "light" and cannot be operated. So this must be the last operation of the flow. Only when the terminal operation is executed, can the flow be traversed and a result, or a side effect, be generated
Listing 3. An example of a flow operation
// JDK 8 public class Widget { private final Color color; private final int weight; enum Color {RED, BLACK, BLUE} public Widget(Color color, int weight) { this.color = color; this.weight = weight; } public Color getColor() {return color;} public int getWeight() {return weight;} public static void main(String[] args) { List<Widget> widgets = new ArrayList<>(); widgets.add(new Widget(Color.RED, 1)); widgets.add(new Widget(Color.RED, 2)); widgets.add(new Widget(Color.BLACK, 3)); widgets.add(new Widget(Color.BLUE, 4)); // stream() obtains the current source, filter and mapToInt as intermediate operations, and performs data filtering and conversion, // The last sum is the terminal operation, which is used to sum the weight of all qualified widget s int sum = widgets.stream() .filter(w -> w.getColor() == Color.RED) .mapToInt(w -> w.getWeight()) .sum(); System.out.println(sum);// 3 } }
Listing 4. Several common ways to construct a flow
// JDK 8 public class SteamConstruct { public static void main(String[] args) { // 1. Individual values Stream stream = Stream.of("a1", "b1", "c1"); stream.forEach(System.out::print);//Print a1b1c1 // 2. Arrays String[] strArray = new String[] {"a2", "b2", "c2"}; stream = Stream.of(strArray); stream = Arrays.stream(strArray); System.out.println(stream.collect(Collectors.joining(",")).toString());//Print a2,b2,c2 // 3. Collections collection List<String> list = Arrays.asList(strArray); stream = list.stream(); } }
Listing 5. Construction of a numeric stream (for basic numeric types, there are currently three corresponding packing types Stream: 1. IntStream 2. LongStream 3. DoubleStream)
// JDK 8 public class BasicStream { // Of course, we can also use stream < integer >, stream < long >, stream < double >, // But boxing and unboxing are time-consuming, so the corresponding streams are provided for these three basic numerical types public static void main(String[] args) { IntStream.of(new int[] {1, 2, 3}).forEach(System.out::print);// 123 IntStream.range(1, 3).forEach(System.out::print);// [1,3) 12 IntStream.rangeClosed(1, 3).forEach(System.out::print);// [1,3] 123 } }
Listing 6. Converting a Stream to another data structure (a Stream can only be used once, otherwise an error will be reported)
public class StreamExchange { public static void main(String[] args) { Stream stream = Stream.of("a1", "b1", "c1"); // 1. Array String[] strArray1 = (String[]) stream.toArray(String[]::new); for (String s : strArray1) { System.out.print(s); } //a1b1c1 // 2.Collection list stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed List<String> list1 = (List<String>) stream.collect(Collectors.toList()); for (String s : list1) { System.out.print(s); }//a1b1c1 // 2.Collection list stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed List<String> list2 = (List<String>) stream.collect(Collectors.toCollection(ArrayList::new)); for (String s : list2) { System.out.print(s); } //a1b1c1 // 2.Collection set stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed Set<String> set = (Set<String>) stream.collect(Collectors.toSet()); for (String s : set) { System.out.print(s); } //a1c1b1 // 2.Collection stack stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed Stack<String> stack = (Stack<String>) stream.collect(Collectors.toCollection(Stack::new)); for (String s : stack) { System.out.print(s); } //a1b1c1 // 3. String stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed String str = stream.collect(Collectors.joining()).toString(); System.out.print(str); // a1b1c1 } }
Flow operation
- Intermediate
map(mapToInt,flatMap, etc.), filter, distinct, sorted, peek, limit, skip, parallel, sequential, unordered - Terminal
forEach, forEachOrdered, toArray, reduce, collect, min, max, count, anyMatch, allMatch, noneMatch, findFirst, findAny, iterator - Short-cricuiting
anyMatch, allMatch, noneMatch, findFirst, findAny, limit
Map/flatMap
Listing 7. Convert uppercase [. map(String::toUpperCase)] and [map (s - > {returns. Touppercase();})] and [. Map (s - > s.touppercase())]
public class ToUpperCase { public static void main(String[] args) { Stream<String> stream = Stream.of("hello", "world", "java8", "stream"); List<String> wordList = stream.map(String::toUpperCase).collect(Collectors.toList()); System.out.println(wordList.toString());// [HELLO, WORLD, JAVA8, STREAM] stream = Stream.of("hello", "world", "java8", "stream"); wordList = stream.map(s -> { return s.toUpperCase(); }).collect(Collectors.toList()); System.out.println(wordList.toString());// [HELLO, WORLD, JAVA8, STREAM] stream = Stream.of("hello", "world", "java8", "stream"); wordList = stream.map(s -> s.toUpperCase()).collect(Collectors.toList()); System.out.println(wordList.toString());// [HELLO, WORLD, JAVA8, STREAM] } }
Listing 8. Square number (map produces a 1:1 mapping, where each input element is converted to another according to the rules)
public class ToSquare { public static void main(String[] args) { Stream<Integer> stream = Arrays.asList(1, 2, 3, 4).stream(); List<Integer> squareList = stream.map(n -> n * n).collect(Collectors.toList()); System.out.println(squareList.toString());// [1, 4, 9, 16] } }
Listing 9. One to many (flatMap flattens the hierarchy in the input stream, that is, it pulls out the underlying elements and puts them together. In the end, there is no List in the output Stream, all of which are direct numbers.)
public class ManyToOne { public static void main(String[] args) { Stream<List<Integer>> inputStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); Stream<Integer> outputStream = inputStream.flatMap(childList -> childList.stream()); System.out.print(outputStream.collect(Collectors.toList()).toString());// [1, 2, 3, 4, 5, 6] } }
Filter
Listing 10. Leave even
public class KeepEvenNumber { public static void main(String[] args) { Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Integer[] evens = Stream.of(sixNums).filter(n -> n % 2 == 0).toArray(Integer[]::new); System.out.println(Arrays.toString(evens));// [2, 4, 6] } }
Listing 11. Pick out the words (first, use flatMap to organize the words in each line into a new Stream, and then keep all the words in the authentic article if the length is not 0)
public class PickAllWords { public static void main(String[] args) { Path path = Paths.get(System.getProperty("user.dir") + "/src/main/java/com/wdxxl/jdk8/ibm/stream/PickAllWords.java"); // 1. Java 8 Read File + Stream try (Stream<String> stream = Files.lines(path)) { List<String> output = stream.flatMap(line -> Stream.of(line.split(" "))) .filter(word -> word.length() > 0).collect(Collectors.toList()); System.out.println(output); } catch (IOException e) { e.printStackTrace(); } // 2. BufferedReader + Stream try (BufferedReader br = Files.newBufferedReader(path)) { List<String> output = br.lines().flatMap(line -> Stream.of(line.split(" "))) .filter(word -> word.length() > 0).collect(Collectors.toList()); System.out.println(output); } catch (IOException e) { e.printStackTrace(); } } }
ForEach
Listing 12. Print name (comparison of forEach and pre-java8) [forEach cannot modify the value of local variables it contains, nor use keywords such as break/return to end the cycle ahead of time]
public class TestForEach { public static void main(String[] args) { List<Person> roster = new ArrayList<>(); roster.add(new Person(Person.Sex.FEMALE, "Lisa")); roster.add(new Person(Person.Sex.MALE, "King")); roster.add(new Person(Person.Sex.MALE, "Jake")); // JDK 8 roster.stream().filter(p -> p.gender == Person.Sex.MALE) .forEach(p -> System.out.println(p.name)); // JDK 7 for (Person p : roster) { if(p.gender == Person.Sex.MALE){ System.out.println(p.name); } } } } class Person { Sex gender; String name; public enum Sex { MALE, FEMALE } public Person(Sex gender, String name) { this.gender = gender; this.name = name; } }
Listing 13. peek performs an operation on each element and returns a new Stream [peek: peep] note the execution order
public class Peek { public static void main(String[] args) { Stream.of("one", "two", "three", "four") .filter(p -> p.length() > 3) .peek(v -> System.out.println("Filtered Value:" + v)) .map(String::toUpperCase) .peek(v -> System.out.println("Mapped Value:" + v)) .collect(Collectors.toList()); // 1. Filtered Value:three // 2. Mapped Value:THREE // 3. Filtered Value:four // 4. Mapped Value:FOUR } }
Listing 14. Two use cases of Optional [the readability of using Optional code is good, and it provides compile time check, which can greatly reduce the impact of NPE on the program]
public class OptionalTest { public static void main(String[] args) { String strA = " abcd", strB = null; print(strA); print(" "); print(strB); System.out.println(getLength(strA)); System.out.println(getLength(" ")); System.out.println(getLength(strB)); } public static void print(String text) { // JDK 8 Optional.ofNullable(text).ifPresent(System.out::println); // Pre-JDK 8 if (text != null) { System.out.println(text); } } public static int getLength(String text) { // JDK 8 return Optional.ofNullable(text).map(String::length).orElse(-1); // Pre-JDK 8 // return (text != null) ? text.length() : -1; } }
reduce
Listing 15. Use case for reduce
public class ReduceTest { public static void main(String[] args) { // 1. SUM 10 Integer sum = Stream.of(1, 2, 3, 4).reduce(0, (a, b) -> a + b); sum = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); //There are starting values. sum = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); //No starting value // 2. Min value = - 3.0 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::min).get(); // 2. Max value = 1.0 double maxValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MIN_VALUE, Double::max); maxValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::max).get(); // 3. String connection Concat "ABCD" String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); // 4. Filter and string connectionfilter & concat = "ace" concat = Stream.of("a", "B", "c", "D", "e", "F") .filter(x -> x.compareTo("Z") > 0) .reduce("", String::concat); } }
limit/skip (limit returns the first n elements of the Stream; skip discards the first n elements; it is renamed from a method called subStream.)
Listing 16. The impact of limit and skip on the number of runs
public class LimitSkipTest { public static void main(String[] args) { List<LimitSkipTest.User> users = new ArrayList<>(); LimitSkipTest limitSkipTest = new LimitSkipTest(); for (int i = 0; i < 100; i++) { users.add(limitSkipTest.new User(i, "name_" + i)); // Inner class construction } List<String> userList = users.stream() .map(User::getName) // name_0name_1name_2name_3name_4name_5name_6name_7name_8name_9 .limit(10) .skip(3) .collect(Collectors.toList()); System.out.println(userList);// [name_3, name_4, name_5, name_6, name_7, name_8, name_9] } // Inner class class User { public int no; private final String name; public User(int no, String name) { this.no = no; this.name = name; } public String getName() { System.out.print(name); return name; } } }
Listing 17. limit and skip have no effect on the number of runs after sorted
public class LimitSkipTest2 { public static void main(String[] args) { List<LimitSkipTest2.User> users = new ArrayList<>(); LimitSkipTest2 limitSkipTest2 = new LimitSkipTest2(); for (int i = 0; i < 5; i++) { users.add(limitSkipTest2.new User(i, "name_" + i)); } // 13 fine adjustments have been made to users. First, the streams of 5 elements are sorted, and then the limit operation is performed List<String> userList = users.stream() .sorted((p1, p2) -> p1.getName().compareTo(p2.getName())) .map(User::getName) // name_1,name_0,name_2,name_1,name_3,name_2,name_4,name_3,name_0,name_1, .limit(2) .collect(Collectors.toList()); System.out.println(userList);// [name_0, name_1] } // Inner class class User { public int no; private final String name; public User(int no, String name) { this.no = no; this.name = name; } public String getName() { System.out.print(name); return name; } } }
sorted
Listing 18. limit and skip before sorting (this optimization has the limitation of business logic: you don't need to sort and then take a value)
List<String> userList = users.stream() .limit(2) .sorted((p1, p2) -> p1.getName().compareTo(p2.getName())) .map(User::getName) // name_1,name_0,name_0,name_1, .collect(Collectors.toList()); System.out.println(userList);// [name_0, name_1]
min/max/distinct
Listing 19. Find the length of the longest line
public class FindLongestLine { public static void main(String[] args) { Path path = Paths.get(System.getProperty("user.dir") + "/src/main/java/com/wdxxl/jdk8/ibm/stream/FindLongestLine.java"); // 2. BufferedReader + Stream try (BufferedReader br = Files.newBufferedReader(path)) { int output = br.lines() .mapToInt(String::length) .max() .getAsInt(); System.out.println(output);// 83 } catch (IOException e) { e.printStackTrace(); } } }
Listing 20. Find the full text word, turn it to lowercase, and sort it
public class OperateWords { public static void main(String[] args) { Path path = Paths.get(System.getProperty("user.dir") + "/src/main/java/com/wdxxl/jdk8/ibm/stream/OperateWords.java"); // 2. BufferedReader + Stream try (BufferedReader br = Files.newBufferedReader(path)) { List<String> output = br.lines() .flatMap(line -> Stream.of(line.split(" "))) .map(String::toLowerCase) .distinct() .sorted() .collect(Collectors.toList()); System.out.println(output); } catch (IOException e) { e.printStackTrace(); } } }
Match
- Allmatch: all elements in the stream match the passed predicate, returning true
- Anymatch: returns true if only one element in the stream matches the passed predicate
- Nonematch: none of the elements in the stream match the incoming predicate, returning true
Listing 21. Using Match
public class MatchTest { public static void main(String[] args) { List<MatchTest.User> users = new ArrayList<>(); MatchTest matchTest = new MatchTest(); for (int i = 0; i < 5; i++) { users.add(matchTest.new User(i, "name_" + i, i * 5)); } boolean isAllAdult = users.stream().allMatch(p -> { System.out.println(p.age); // 0 and private final int age?? return p.age > 18; }); System.out.println("All are adult? " + isAllAdult); // All are adult? false boolean isAnyChild = users.stream().anyMatch(p -> p.age < 12); System.out.println("Any Child? " + isAnyChild); // Any Child? true boolean noneOldThan19 = users.stream().noneMatch(p -> p.age > 19); System.out.println("none Old Than 19? " + noneOldThan19);// none Old Than 19? false boolean noneOldThan50 = users.stream().noneMatch(p -> p.age > 50); System.out.println("none Old Than 50? " + noneOldThan50);// none Old Than 50? true } class User { public int no; public String name; private final int age; public User(int no, String name, int age) { this.no = no; this.name = name; this.age = age; } } }
Advanced: generate flow by yourself
Stream.generate
By implementing the Supplier excuse, you can control the flow generation yourself. This situation is usually used for random numbers, constant streams, or streams that need to maintain some state information before and after the elements. Pass the Supplier sample to the Stream generated by Stream.generate(), which is serial (relative to parallel) but unordered (relative to ordered) by default. Because it is infinite, in the pipeline, you have to limit the Stream size with operations like limit.
Listing 22. Production of 10 random integers
public class RandomTest { public static void main(String[] args) { Random seed = new Random(); Supplier<Integer> random = seed::nextInt; Stream.generate(random) .limit(10) .forEach(System.out::println); // Another way IntStream.generate(() -> (int) (System.nanoTime() % 100)) .limit(10) .forEach(System.out::println); } }
Listing 23. Self implemented Supplier [Stream.generate also accepts its own implemented Supplier. For example, when constructing massive test data, use some automatic rules to assign values to each variable, or calculate each element of the Stream according to the formula. These are the cases of maintaining state information]
public class SupplierTest { public static void main(String[] args) { SupplierTest supplierTest = new SupplierTest(); Stream.generate(supplierTest.new UserSupplier()).limit(10) .forEach(p -> System.out.println(p.name + ":" + p.age)); } class UserSupplier implements Supplier<User> { private int index = 0; private final Random random = new Random(); @Override public User get() { return new User(index++, "name_" + index, random.nextInt(100)); } } class User { public int no; private final String name; private final int age; public User(int no, String name, int age) { this.no = no; this.name = name; this.age = age; } } }
Listing 24. Producing an isochromatic sequence
public class Sequence { public static void main(String[] args) { Stream.iterate(0, n -> n + 3) .limit(10).forEach(x -> System.out.print(x + " "));// 0 3 6 9 12 15 18 21 24 27 Stream.iterate(4, n -> n + 3) .limit(10).forEach(x -> System.out.print(x + " "));// 4 7 10 13 16 19 22 25 28 31 } }
Advanced: use Collectors to reduce
grouping/partitioningBy
Listing 25. Grouping by age
public class AdultGroup { public static void main(String[] args) { AdultGroup adultGroup = new AdultGroup(); Map<Integer, List<User>> children = Stream.generate(adultGroup.new UserSupplier()) .limit(100) .collect(Collectors.groupingByConcurrent(User::getAge)); Iterator it = children.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, List<User>> users = (Map.Entry) it.next(); System.out.println("Age: " + users.getKey() + "=" + users.getValue().size()); } } class UserSupplier implements Supplier<User> { private int index = 0; private final Random random = new Random(); @Override public User get() { return new User(index++, "name_" + index, random.nextInt(100)); } } class User { public int no; public String name; public int age; public User(int no, String name, int age) { this.no = no; this.name = name; this.age = age; } public int getAge() { return age; } } }
Listing 26. partitioningBy grouped by minors and adults
After using the condition "age less than 18" for grouping, we can see that minors under 18 years old are one group and adults are another group.
public class AdultPartition { public static void main(String[] args) { AdultPartition adultPartition = new AdultPartition(); Map<Boolean, List<User>> children = Stream.generate(adultPartition.new UserSupplier()) .limit(100) .collect(Collectors.partitioningBy(p -> p.age > 18)); System.out.println("Children number:" + children.get(false).size()); System.out.println("Adult number:" + children.get(true).size()); } class UserSupplier implements Supplier<User> { private int index = 0; private final Random random = new Random(); @Override public User get() { return new User(index++, "name_" + index, random.nextInt(100)); } } class User { public int no; public String name; public int age; public User(int no, String name, int age) { this.no = no; this.name = name; this.age = age; } } }
Concluding remarks
In a word, the characteristics of Stream can be summarized as follows:
- Not a data structure
- It has no internal storage. It just grabs data from source (data structure, array, generator function, IO channel) with operation pipeline.
- It will never modify the data of the underlying data structure it encapsulates. For example, the filter operation of a Stream will generate a new Stream that does not contain the filtered elements, rather than removing those elements from the source.
- All Stream operations must take a lambda expression as an argument
- Index access is not supported
- You can request the first element, but not the second, third, or last. But see next.
- It's easy to generate arrays or lists
- Inertness
- Many Stream operations are backward delayed until it knows how much data is needed.
- Intermediate operations are always lazy.
- Parallel ability
- When a Stream is parallelized, there is no need to write multi-threaded code, and all operations on it will be automatically parallelized.
- Can be infinite
- The collection has a fixed size, and the Stream does not have to. Short circuiting operations such as limit(n) and findFirst() can operate on infinite streams and complete them quickly.
By kexue
Link: https://www.jianshu.com/p/9101b2ef96d8
Source: Jianshu
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.