Collections optimize the storage of objects, while streams are concerned with the processing of objects.
A stream is a set of elements independent of a particular storage mechanism — in fact, a stream is not “stored.”
With flows, you can extract and manipulate elements in a collection without iterating through them. These pipes are usually combined to form an operating pipe over the flow.
Most of the time, objects are stored in collections to process them, so you’ll find that the programming focus shifts from collections to flows. One of the core benefits of streaming is that it makes programs shorter and easier to understand. Lambda expressions can feel self-contained when used with method references and streams. Streams make Java 8 even more attractive.
Let’s say you want to randomly show integers between 5 and 20 that are not repeated and sort them. In fact, your focus is to create an ordered collection in the first place. Follow up around this collection. But with streaming programming, you can simply state what you want to do:
// streams/Randoms.java
import java.util.*;
public class Randoms {
public static void main(String[] args) {
new Random(47)
.ints(5.20)
.distinct()
.limit(7) .sorted() .forEach(System.out::println); }}Copy the code
Output result:
6, 10, 13, 16, 17, 18, 19Copy the code
First, we give the Random object a seed (so that the program will produce the same output when it is run again). The ints() method produces a stream and the ints() method is overloaded in a variety of ways – two parameters define the boundaries of numeric generation. This will generate a stream of integers. We can use intermediate Stream operation distinct() to get their non-repeating values, and then use the limit() method to get the first seven elements. Next, we use the sorted() method to sort. The output is ultimately traversed using the forEach() method, which performs operations on each stream object based on the function passed to it. Here, we pass a method reference that displays each element in the console. System. The out: : println.
Note that no variables are declared in Randoms.java. Streams can be very useful for modeling stateful systems without using assignment or mutable data.
Declarative programming is a style of programming that states what to do, not how to do it. As we saw in functional programming. Note that the form of imperative programming is more difficult to understand. Code examples:
// streams/ImperativeRandoms.java
import java.util.*;
public class ImperativeRandoms {
public static void main(String[] args) {
Random rand = new Random(47);
SortedSet<Integer> rints = new TreeSet<>();
while(rints.size() < 7) {
int r = rand.nextInt(20);
if(r < 5) continue; rints.add(r); } System.out.println(rints); }}Copy the code
Output result:
[7, 8, 9, 11, 13, 15, 18]
Copy the code
In Randoms.java, we don’t have to define any variables, but here we define three variables: RAND, RINts, and R. This code is more complicated to implement because the nextInt() method has no lower bound (its built-in lower bound is always 0). So we generate extra values to filter out results less than 5.
Note that you have to investigate the real intent of the program, whereas in Randoms.java, the code just tells you what it is doing. This semantic clarity is also an important reason why streaming programming in Java 8 is preferred.
Explicitly writing an iteration mechanism in Imperativerandoms.java is called external iteration. In Randoms.java, streaming uses internal iteration, which is one of the core features of streaming programming. This mechanism makes writing code more readable and takes advantage of multi-core processors. By giving up control over the iterative process, we give control to the parallelization mechanism. We’ll learn about this in the chapter on Concurrent Programming.
Another important aspect is that streams are lazily loaded. This means that it is calculated only when absolutely necessary. You can think of a stream as a “deferred list.” Due to computational latency, streams enable us to represent very large (or even infinite) sequences without having to worry about memory.
Streaming support
Java designers face the challenge of having a large class library that is not only available for Java, but also used in millions of lines of code throughout the Java ecosystem. How do you integrate the concept of a new stream into an existing class library?
For example, add more methods to Random. As long as the original method is not changed, the existing code is not disturbed.
The question is, what about the interface part? In particular, those involving the collection class interface. If you want to convert a collection to a stream, adding new methods directly to the interface breaks all the old interface implementation classes.
The solution adopted in Java 8 is to add methods decorated by default to the interface. With this approach, designers can embed the stream method smoothly into existing classes. The flow method presets operations that meet almost all of our usual needs. There are three types of flow Operations: create flow, modify flow elements (Intermediate Operations), and consume flow elements (Terminal Operations). The last type usually means collecting stream elements (usually into a collection).
Let’s look at each type of stream operation.
Flow to create
You can easily convert a set of elements to a Stream with stream.of () (the Bubble class is defined later in this chapter) :
// streams/StreamOf.java
import java.util.stream.*;
public class StreamOf {
public static void main(String[] args) {
Stream.of(new Bubble(1), new Bubble(2), new Bubble(3))
.forEach(System.out::println);
Stream.of("It's "."a "."wonderful "."day "."for "."pie!")
.forEach(System.out::print);
System.out.println();
Stream.of(3.14159.2.718.1.618) .forEach(System.out::println); }}Copy the code
Output result:
Bubble(1)
Bubble(2)
Bubble(3)
It's a wonderful day for pie!
3.14159
2.718
1.618
Copy the code
Each collection can produce a stream through stream(). Example:
import java.util.*;
import java.util.stream.*;
public class CollectionToStream {
public static void main(String[] args) {
List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
System.out.println(bubbles.stream()
.mapToInt(b -> b.i)
.sum());
Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split("")));
w.stream()
.map(x -> x + "")
.forEach(System.out::print);
System.out.println();
Map<String, Double> m = new HashMap<>();
m.put("pi".3.14159);
m.put("e".2.718);
m.put("phi".1.618);
m.entrySet().stream()
.map(e -> e.getKey() + ":"+ e.getValue()) .forEach(System.out::println); }}Copy the code
Output result:
6 a pie! It's for Wonderful Day Phi: 1.618E: 2.718 PI: 3.14159Copy the code
- create
List<Bubble>
Object is simply calledIt’s in all setsthestream()
. - In the middle of operation
map()
All elements in the stream are fetched, and operations are applied to elements in the stream to produce new elements that are passed to subsequent flows. usuallymap()
Objects are fetched and new objects are generated, but here a special stream is generated for numeric types. For example,mapToInt()
The Object Stream () method converts an object stream to an integer numberIntStream
. - By calling the string
split()
To get the element used to define the variablew
. - In order to fromMapThe collection generates stream data, which we call first
entrySet()
Produces a stream of objects, one for each objectkey
The key and its associated with itvalue
Value. And then call them separatelygetKey()
和getValue()
Get the value.
Random number stream
The Random class is enhanced by a set of methods that generate streams. Code examples:
// streams/RandomGenerators.java
import java.util.*;
import java.util.stream.*;
public class RandomGenerators {
public static <T> void show(Stream<T> stream) {
stream
.limit(4)
.forEach(System.out::println);
System.out.println("+ + + + + + + +");
}
public static void main(String[] args) {
Random rand = new Random(47);
show(rand.ints().boxed());
show(rand.longs().boxed());
show(rand.doubles().boxed());
// Control upper limit and lower limit:
show(rand.ints(10.20).boxed());
show(rand.longs(50.100).boxed());
show(rand.doubles(20.30).boxed());
// Control flow size:
show(rand.ints(2).boxed());
show(rand.longs(2).boxed());
show(rand.doubles(2).boxed());
// Control the size and bounds of the flow
show(rand.ints(3.3.9).boxed());
show(rand.longs(3.12.22).boxed());
show(rand.doubles(3.11.5.12.3).boxed()); }}Copy the code
Output result:
-1172028779 1717241110-2014573909 229403722 ++++++++ 2955289354441303771 3476817843704654257 -8917117694134521474 4941259272818818752 ++++++++ 0.2613610344283964 0.0508673570556899 0.8037155449603999 0.7620665811558285 ++++++++ 16 10 11 12 ++++++++ 65 99 54 58 ++++++++ 29.86777681078574 24.83968447804611 20.09247112332014 24.046793846338723 ++++++++ 1169976606 1947946283 ++++++++ 2970202997824602425-2325326920272830366 ++++++++ 0.7024254510631527 0.6648552384607359 + + + + + + + + 6 to 7 7 + + + + + + + + 17 12 to 20 + + + + + + + + 11.732085449736195 + 12.196509449817267 + 12.27872414236691 + + + + + +Copy the code
To eliminate redundant code, I created a generic method show(Stream
Stream). (Using this feature before going into generics is a bit of a cheat, but the payoff is well worth it.) The type parameter T can be of any type, so this method works for Integer, Long, and Double. But the Random class can only generate streams of the basic int, long, and double types. Fortunately, the Boxed () stream operation will automatically wrap the base type into the corresponding boxing type, making the stream acceptable to show().
We can use Random to create the Supplier for any collection of objects. Here is an example of a text file providing a string object.
Chez. dat file contents:
// streams/Cheese.dat
Not much of a cheese shop really, is it?
Finest in the district, sir.
And what leads you to that conclusion?
Well, it's so clean.
It's certainly uncontaminated by cheese.
Copy the code
We use the File class to read all lines of the chez. dat File into List
. Code examples:
// streams/RandomWords.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
import java.io.*;
import java.nio.file.*;
public class RandomWords implements Supplier<String> {
List<String> words = new ArrayList<>();
Random rand = new Random(47);
RandomWords(String fname) throws IOException {
List<String> lines = Files.readAllLines(Paths.get(fname));
// Skip the first line
for (String line : lines.subList(1, lines.size())) {
for (String word : line.split("[?] +")) words.add(word.toLowerCase()); }}public String get(a) {
return words.get(rand.nextInt(words.size()));
}
@Override
public String toString(a) {
return words.stream()
.collect(Collectors.joining(""));
}
public static void main(String[] args) throws Exception {
System.out.println(
Stream.generate(new RandomWords("Cheese.dat"))
.limit(10)
.collect(Collectors.joining(""))); }}Copy the code
Output result:
it shop sir the much cheese by conclusion district is
Copy the code
Here you can see the more complicated use of split(). In the constructor, each line is split() by Spaces or arbitrary punctuation wrapped in square brackets. The + after the closing square bracket means that the thing before the + can appear once or more.
We notice that the body of the loop uses imperative programming (external iteration) in the constructor. You’ll even see how we can eliminate this in future examples. The old form wasn’t particularly bad, but it felt better to use a stream.
In toString() and the main method you see the collect() operation, which combines all elements in a stream based on parameters.
When you use joining(), you get a String, with each element split according to the parameters of joining(). There are also many different Collectors used to produce different results.
In the main method, we saw the use of stream.generate () ahead of time, which can use any Supplier
to generate a Stream of type T.
The range of int types
The IntStream class provides the range() method for generating streams of integer sequences. This method is even more convenient when writing loops:
// streams/Ranges.java
import static java.util.stream.IntStream.*;
public class Ranges {
public static void main(String[] args) {
// Traditional method:
int result = 0;
for (int i = 10; i < 20; i++)
result += i;
System.out.println(result);
// for-in loop:
result = 0;
for (int i : range(10.20).toArray())
result += i;
System.out.println(result);
// Use stream:
System.out.println(range(10.20).sum()); }}Copy the code
Output result:
145
145
145
Copy the code
The first way in the main method is the way we traditionally write for loops; In the second way, we use range() to create a stream and convert it to an array, which we then use in a for-in block. However, it would be better if you could use the flow all the way, as in the third method. We sum the numbers in the range. It is convenient to use sum() in a stream.
Note that intstream.range () has more restrictions than onjava.range.range (). This is due to its optional third parameter, which allows steps to be larger than 1 and can be generated from large to small.
The utility widget repeat() can be used to replace the simple for loop. Code examples:
// onjava/Repeat.java
package onjava;
import static java.util.stream.IntStream.*;
public class Repeat {
public static void repeat(int n, Runnable action) {
range(0, n).forEach(i -> action.run()); }}Copy the code
The cycle is clearer:
// streams/Looping.java
import static onjava.Repeat.*;
public class Looping {
static void hi(a) {
System.out.println("Hi!");
}
public static void main(String[] args) {
repeat(3, () -> System.out.println("Looping!"));
repeat(2, Looping::hi); }}Copy the code
Output result:
Looping!
Looping!
Looping!
Hi!
Hi!
Copy the code
In principle, it’s not worth including and interpreting repeat() in your code. It’s a fairly transparent tool to be sure, but the results depend on how your team and company operate.
generate()
See the example of stream.generate () with Supplier
in randomwords.java. Code examples:
// streams/Generator.java
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Generator implements Supplier<String> {
Random rand = new Random(47);
char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
public String get(a) {
return "" + letters[rand.nextInt(letters.length)];
}
public static void main(String[] args) {
String word = Stream.generate(new Generator())
.limit(30) .collect(Collectors.joining()); System.out.println(word); }}Copy the code
Output result:
YNZBRNYGCFOWZNTCQRGSEGZMMJMROE
Copy the code
Use the random.nextint () method to pick the uppercase letters of the alphabet. The argument to random.nextint () represents the largest range of Random numbers that can be accepted, so the use of array boundaries is deliberate.
To create a stream containing the same objects, just pass a lambda to generate those objects: generate()
// streams/Duplicator.java
import java.util.stream.*;
public class Duplicator {
public static void main(String[] args) {
Stream.generate(() -> "duplicate")
.limit(3) .forEach(System.out::println); }}Copy the code
Output result:
duplicate
duplicate
duplicate
Copy the code
Here is the Bubble class that was used in the previous examples in this chapter. Note that it includes its own Static generator method.
// streams/Bubble.java
import java.util.function.*;
public class Bubble {
public final int i;
public Bubble(int n) {
i = n;
}
@Override
public String toString(a) {
return "Bubble(" + i + ")";
}
private static int count = 0;
public static Bubble bubbler(a) {
return newBubble(count++); }}Copy the code
Since bubbler() is interface compatible with Supplier
, we can pass its method reference directly to stream.generate () :
// streams/Bubbles.java
import java.util.stream.*;
public class Bubbles {
public static void main(String[] args) {
Stream.generate(Bubble::bubbler)
.limit(5) .forEach(System.out::println); }}Copy the code
Output result:
Bubble(0)
Bubble(1)
Bubble(2)
Bubble(3)
Bubble(4)
Copy the code
This is another way to create a Separate Factory class. In many ways it’s cleaner, but it’s a matter of code organization and taste — you can always create a completely different factory class.
iterate()
Stream.iterate() begins with the seed (first argument) and passes it to the method (second argument). The result of the method is added to the stream and stored as the first argument for the next call, iterate(), and so on. We can make a Fibonacci sequence with iterate(). Code examples:
// streams/Fibonacci.java
import java.util.stream.*;
public class Fibonacci {
int x = 1;
Stream<Integer> numbers(a) {
return Stream.iterate(0, i -> {
int result = x + i;
x = i;
return result;
});
}
public static void main(String[] args) {
new Fibonacci().numbers()
.skip(20) // Filter the first 20
.limit(10) // Then take 10.forEach(System.out::println); }}Copy the code
Output result:
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
Copy the code
The Fibonacci sequence sums the last two elements of the sequence to produce the next element. Iterate () only remembers results, so we need to track another element with a variable x.
In the main method, we use a skip() operation that we haven’t seen before. It discards a specified number of stream elements based on arguments. Here, we discard the first 20 elements.
The builder pattern of flows
In builder design mode (also known as constructor mode), a Builder object is first created, multiple constructor information is passed to it, and “construction” is performed. The Stream library provides such a Builder. Here, we revisit the process of reading a file and converting it into a word stream. Code examples:
// streams/FileToWordsBuilder.java
import java.io.*;
import java.nio.file.*;
import java.util.stream.*;
public class FileToWordsBuilder {
Stream.Builder<String> builder = Stream.builder();
public FileToWordsBuilder(String filePath) throws Exception {
Files.lines(Paths.get(filePath))
.skip(1) // Skip the opening comment line
.forEach(line -> {
for (String w : line.split("[?] +"))
builder.add(w);
});
}
Stream<String> stream(a) {
return builder.build();
}
public static void main(String[] args) throws Exception {
new FileToWordsBuilder("Cheese.dat")
.stream()
.limit(7)
.map(w -> w + "") .forEach(System.out::print); }}Copy the code
Output result:
Not much of a cheese shop really
Copy the code
Notice that the constructor adds all the words in the file (except the first line, which is a comment containing the file path information), but it doesn’t call build(). As long as you don’t call the stream() method, you can continue adding words to the Builder object.
In the more complete form of the class, you can add a flag bit to see if build() is called, and possibly a method to add more words. Continuing to try to add a word after calling the build() method on stream.Builder raises an exception.
Arrays
The Arrays class contains a static method called stream() that converts Arrays into streams. We can override the main method in interfaces/ machine.java to create a flow and apply execute() to each element. Code examples:
// streams/Machine2.java
import java.util.*;
import onjava.Operations;
public class Machine2 {
public static void main(String[] args) {
Arrays.stream(new Operations[] {
() -> Operations.show("Bing"),
() -> Operations.show("Crack"),
() -> Operations.show("Twist"),
() -> Operations.show("Pop") }).forEach(Operations::execute); }}Copy the code
Output result:
Bing
Crack
Twist
Pop
Copy the code
The new Operations[] expression dynamically creates an array of Operations objects.
Stream () also produces IntStream, LongStream, and DoubleStream.
// streams/ArrayStreams.java
import java.util.*;
import java.util.stream.*;
public class ArrayStreams {
public static void main(String[] args) {
Arrays.stream(new double[] { 3.14159.2.718.1.618 })
.forEach(n -> System.out.format("%f ", n));
System.out.println();
Arrays.stream(new int[] { 1.3.5 })
.forEach(n -> System.out.format("%d ", n));
System.out.println();
Arrays.stream(new long[] { 11.22.44.66 })
.forEach(n -> System.out.format("%d ", n));
System.out.println();
// Select a subfield:
Arrays.stream(new int[] { 1.3.5.7.15.28.37 }, 3.6)
.forEach(n -> System.out.format("%d ", n)); }}Copy the code
Output result:
3.141590 2.718000 1.618000
1 3 5
11 22 44 66
7 15 28
Copy the code
The last call to stream() takes two additional arguments. The first argument tells stream() where in the array to start selecting elements, and the second argument tells stream() where to stop. Each different type of stream() has similar operations.
Regular expression
Java regular expressions are covered in detail in the section on Strings. Java 8 added a new method called splitAsStream() to java.util.regex.pattern. This method converts a sequence of characters into a stream based on the formula passed in. One restriction, however, is that the input can only be CharSequence, so streams cannot be taken as arguments to splitAsStream().
Let’s look again at the process of processing a file into a word stream. This time, we used streams to split the file into individual strings, followed by regular expressions to convert the strings into word streams.
// streams/FileToWordsRegexp.java
import java.io.*;
import java.nio.file.*;
import java.util.stream.*;
import java.util.regex.Pattern;
public class FileToWordsRegexp {
private String all;
public FileToWordsRegexp(String filePath) throws Exception {
all = Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.collect(Collectors.joining(""));
}
public Stream<String> stream(a) {
return Pattern
.compile("[?] +").splitAsStream(all);
}
public static void
main(String[] args) throws Exception {
FileToWordsRegexp fw = new FileToWordsRegexp("Cheese.dat");
fw.stream()
.limit(7)
.map(w -> w + "")
.forEach(System.out::print);
fw.stream()
.skip(7)
.limit(2)
.map(w -> w + "") .forEach(System.out::print); }}Copy the code
Output result:
Not much of a cheese shop really is it
Copy the code
In the constructor we read everything in the file (skipping the first line comment and converting it to a single-line string). Now, when you call stream(), you can get a stream as usual, but this time you can call stream() multiple times to create a new stream in the stored string. One limitation is that the entire file must be stored in memory; In most cases this is not a problem, but it takes away a very important advantage of stream operations:
- Streams “don’t need to be stored.” Of course they require some internal storage, but this is only a small part of the sequence, not the same as holding the whole sequence.
- They are lazily computed.
Fortunately, we’ll find out how to solve this problem shortly.
In the middle of operation
An intermediate operation is used to get an object from one stream and output it from the back end as another stream to connect to other operations.
Trace and debug
The peek() operation is intended to aid debugging. It allows you to view elements in a stream without modification. Code examples:
// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + "") .peek(System.out::print) .map(String::toUpperCase) .peek(System.out::print) .map(String::toLowerCase) .forEach(System.out::print); }}Copy the code
Output result:
Well WELL well it IT it s S s so SO so
Copy the code
FileToWords will be defined later, but its implementation looks similar to what we saw before: a stream that generates string objects. It is then processed with a call to peek() as it passes through the pipe.
Because peek() conforms to the Consumer functional interface with no return value, we can only observe and cannot replace objects in the stream with different elements.
Stream element sorting
In randoms.java, we are familiar with the default comparator implementation of sorted(). There is another implementation: passing in a Comparator parameter. Code examples:
// streams/SortedComparator.java
import java.util.*;
public class SortedComparator {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(10)
.limit(10)
.sorted(Comparator.reverseOrder())
.map(w -> w + "") .forEach(System.out::print); }}Copy the code
Output result:
you what to the that sir leads in district And
Copy the code
Sorted () presets some default comparators. Here we use reverse “natural ordering”. Of course you can also pass a Lambda function to sorted() as an argument.
Remove elements
-
Distinct () : Distinct () in the Randoms.java class can be used to eliminate duplicate elements in a flow. This method is much less work than creating a Set collection.
-
Filter (Predicate) : The filter operation preserves elements that are evaluated to true by the filter function passed in.
In the following example, isPrime() is used as a filter function to detect primes.
import java.util.stream.*;
import static java.util.stream.LongStream.*;
public class Prime {
public static Boolean isPrime(long n) {
return rangeClosed(2, (long)Math.sqrt(n))
.noneMatch(i -> n % i == 0);
}
public LongStream numbers(a) {
return iterate(2, i -> i + 1)
.filter(Prime::isPrime);
}
public static void main(String[] args) {
new Prime().numbers()
.limit(10)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
new Prime().numbers()
.skip(90)
.limit(10)
.forEach(n -> System.out.format("%d ", n)); }}Copy the code
Output result:
23 5 7 11 13 17 19 23 29 467 479 487 491 499 503 509 521 523 541Copy the code
RangeClosed () contains the upper value. The noneMatch() operation returns true if it is not divisible, that is, the remainder is not equal to 0, or false if any result is equal to 0. The noneMatch() operation exits on any failure.
Apply functions to elements
-
Map (Function) : Applies Function operations to elements of the input stream and passes the return value to the output stream.
-
MapToInt (ToIntFunction) : same operation as above, but result IntStream.
-
MapToLong (ToLongFunction) : same operation as above, but result is LongStream.
-
MapToDouble (ToDoubleFunction) : Same operation as above, but result in DoubleStream.
Here, we use map() to map multiple functions into a string stream. Code examples:
// streams/FunctionMap.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class FunctionMap {
static String[] elements = { "12".""."23"."45" };
static Stream<String>
testStream(a) {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println("(" -- - + descr + ") -");
testStream()
.map(func)
.forEach(System.out::println);
}
public static void main(String[] args) {
test("add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
}
catch(NumberFormatException e) {
returns; }}); test("Replace", s -> s.replace("2"."9"));
test("Take last digit", s -> s.length() > 0 ?
s.charAt(s.length() - 1) + "": s); }}Copy the code
Output result:
---( add brackets )--- [12] [] [23] [45] ---( Increment )--- 13 24 46 ---( Replace )--- 19 93 45 ---( Take last digit 2 3-5Copy the code
In the increment example above, we use integer.parseint () to try to convert a string to an Integer. A NumberFormatException is thrown if the string can’t be converted to an integer, so we just need to go back and put the original string back into the output stream.
In the above example, map() maps one string to another, but it is entirely possible to generate and receive completely different types, thus changing the data type of the stream. Example code:
// streams/FunctionMap2.java
// Different input and output types
import java.util.*;
import java.util.stream.*;
class Numbered {
final int n;
Numbered(int n) {
this.n = n;
}
@Override
public String toString(a) {
return "Numbered(" + n + ")"; }}class FunctionMap2 {
public static void main(String[] args) {
Stream.of(1.5.7.9.11.13)
.map(Numbered::new) .forEach(System.out::println); }}Copy the code
Output result:
Numbered(1)
Numbered(5)
Numbered(7)
Numbered(9)
Numbered(11)
Numbered(13)
Copy the code
We convert the fetched integer into the Numbered type using the constructor Numbered::new.
If the result returned by Function is one of the numeric types, we must use the appropriate mapTo numeric type instead. Code examples:
// streams/FunctionMap3.java
// Producing numeric output streams
import java.util.*;
import java.util.stream.*;
class FunctionMap3 {
public static void main(String[] args) {
Stream.of("5"."Seven"."9")
.mapToInt(Integer::parseInt)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
Stream.of("17"."19"."23")
.mapToLong(Long::parseLong)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
Stream.of("17"."1.9".23 ".")
.mapToDouble(Double::parseDouble)
.forEach(n -> System.out.format("%f ", n)); }}Copy the code
Output result:
5 7 9
17 19 23
17.000000 1.900000 0.230000
Copy the code
Unfortunately, Java designers have not done their best to eliminate primitive types.
inmap()
Combination of flow
Suppose we now have an incoming element stream and want to use the map() function to stream elements. Now you’ve found some cute and unique functionality, but here’s the problem: this functionality produces a flow. We want to produce a stream of elements, but we actually produce a stream of elements.
FlatMap () does two things: it applies the stream-generating function to each element (the same as map() does), and then flattens each stream into elements, so that it ends up producing only elements.
FlatMap (Function) : Used when Function generates a stream.
FlatMapToInt (Function) : Used when Function generates IntStream.
FlatMapToLong (Function) : Used when Function generates a LongStream.
FlatMapToDouble (Function) : Used when Function produces DoubleStream.
To understand how it works, we start by passing a deliberately designed function to map(). This function takes an integer and produces a string stream:
// streams/StreamOfStreams.java
import java.util.stream.*;
public class StreamOfStreams {
public static void main(String[] args) {
Stream.of(1.2.3)
.map(i -> Stream.of("Gonzo"."Kermit"."Beaker")) .map(e-> e.getClass().getName()) .forEach(System.out::println); }}Copy the code
Output result:
java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head
Copy the code
We naively expect a string stream, but what we get is a stream of “Head” streams. We can solve this problem using flatMap() :
// streams/FlatMap.java
import java.util.stream.*;
public class FlatMap {
public static void main(String[] args) {
Stream.of(1.2.3)
.flatMap(i -> Stream.of("Gonzo"."Fozzie"."Beaker")) .forEach(System.out::println); }}Copy the code
Output result:
Gonzo
Fozzie
Beaker
Gonzo
Fozzie
Beaker
Gonzo
Fozzie
Beaker
Copy the code
Each stream returned from the map is automatically flattened into the string that makes it up.
Here is another demonstration where we start with a stream of integers and then use each integer to create more random numbers.
// streams/StreamOfRandoms.java
import java.util.*;
import java.util.stream.*;
public class StreamOfRandoms {
static Random rand = new Random(47);
public static void main(String[] args) {
Stream.of(1.2.3.4.5)
.flatMapToInt(i -> IntStream.concat(
rand.ints(0.100).limit(i), IntStream.of(-1)))
.forEach(n -> System.out.format("%d ", n)); }}Copy the code
Output result:
58-1 55 93-1 61 61 29-1 68 0 22 7-1 88 28 51 89 9-1Copy the code
Here we introduce concat(), which combines two streams in argument order. Thus, we add a -1 to the end of each random Integer stream as a marker. You can see that the final stream is indeed created from a set of flat streams.
Because rand.ints() produces an IntStream, I must use the specific integer forms of flatMap(), concat(), and of().
Let’s look again at the task of dividing a file into word streams. The last one we used was FileToWordsRegexp.java, which had the problem of reading the entire file into the row table — obviously storing the list. What we really want is to create a word stream that doesn’t require an intermediate storage layer.
Let’s use flatMap() again to solve this problem:
// streams/FileToWords.java
import java.nio.file.*;
import java.util.stream.*;
import java.util.regex.Pattern;
public class FileToWords {
public static Stream<String> stream(String filePath) throws Exception {
return Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.flatMap(line ->
Pattern.compile("\\W+").splitAsStream(line)); }}Copy the code
Stream () is now a static method because it can do the whole stream creation process by itself.
Note: \\W+ is a regular expression. It means “non-word character” and + means “can occur once or more.” The lowercase form \\w means “word character”.
The problem we ran into earlier was that pattern.pile ().splitasstream () produced a stream, which means that calling map() on the stream of lines passed in produces a stream of word streams when we just want a simple word stream. Fortunately, flatMap() flattens the flow of an element stream into a simple element stream. Alternatively, we can use string.split () to generate an array that can be converted to a stream by arrays.stream () :
.flatMap(line -> Arrays.stream(line.split("\\W+"))))
Copy the code
With a real, rather than collector-based stream in FileToWordsRegexp. Java, we have to create it from scratch every time we use it because streams can’t be reused:
// streams/FileToWordsTest.java
import java.util.stream.*;
public class FileToWordsTest {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.limit(7)
.forEach(s -> System.out.format("%s ", s));
System.out.println();
FileToWords.stream("Cheese.dat")
.skip(7)
.limit(2)
.forEach(s -> System.out.format("%s ", s)); }}Copy the code
Output result:
Not much of a cheese shop really
is it
Copy the code
The %s in system.out.format () indicates that the argument is of type String.
Optional class
Before we look at terminal operations, we must consider what happens if you fetch elements in an empty stream. We like to concatenate streams for a “happy path” and assume that the stream will not be interrupted. Placing NULL in a stream is a good way to interrupt. So is there some object that can act as a holder of a stream element that will kindly inform us even if the element we’re looking at doesn’t exist (that is, no exception)?
Optional can do just that. Some standard flow operations return Optional objects because they do not guarantee that the expected result will exist. Include:
findFirst()
Returns an object containing the first elementOptionalObject that returns if the stream is emptyOptional.emptyfindAny()
Object containing any elementOptionalObject that returns if the stream is emptyOptional.emptymax()
和min()
Returns a value that contains either a maximum or minimum valueOptionalObject that returns if the stream is emptyOptional.empty
Reduce () no longer begins with identity, but wraps its return value in Optional. (The identity object becomes the default result of other forms of reduce(), so there is no risk of null results)
For digital streams IntStream, LongStream, and DoubleStream, Average () wraps the result in Optional to prevent the stream from being empty.
Here’s a simple test to do all of this with an empty stream:
// streams/OptionalsFromEmptyStreams.java
import java.util.*;
import java.util.stream.*;
class OptionalsFromEmptyStreams {
public static void main(String[] args) { System.out.println(Stream.<String>empty() .findFirst()); System.out.println(Stream.<String>empty() .findAny()); System.out.println(Stream.<String>empty() .max(String.CASE_INSENSITIVE_ORDER)); System.out.println(Stream.<String>empty() .min(String.CASE_INSENSITIVE_ORDER)); System.out.println(Stream.<String>empty() .reduce((s1, s2) -> s1 + s2)); System.out.println(IntStream.empty() .average()); }}Copy the code
Output result:
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
OptionalDouble.empty
Copy the code
Instead of throwing an exception, you get an optional. empty object when the stream is empty. Optional has the toString() method that can be used to display useful information.
Note that the empty Stream is created with stream.
empty(). If you call stream.empty () without any context, Java doesn’t know its data type; This syntax solves the problem. If the compiler has enough context information, for example:
Stream<String> s = Stream.empty();
Copy the code
You can infer the type when you call empty().
This example demonstrates two basic uses of Optional:
// streams/OptionalBasics.java
import java.util.*;
import java.util.stream.*;
class OptionalBasics {
static void test(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst()); test(Stream.<String>empty().findFirst()); }}Copy the code
Output result:
Epithets
Nothing inside!
Copy the code
When you receive an Optional object, you should first call isPresent() to check if it contains an element. If it exists, you can get it using get().
Convenience function
There are a number of convenience functions available to unpack Optional, which simplifies the process of checking and performing operations on contained objects described above:
ifPresent(Consumer)
: called when the value existsConsumerOtherwise, do nothing.orElse(otherObject)
: Returns the value if it exists, otherwiseotherObject.orElseGet(Supplier)
: returns the value if it exists, otherwiseSupplierThe function generates a substitute object.orElseThrow(Supplier)
: returns if the value exists, otherwiseSupplierThe function generates an exception.
Here is a simple demonstration of the different convenience functions:
// streams/Optionals.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class Optionals {
static void basics(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
static void ifPresent(Optional<String> optString) {
optString.ifPresent(System.out::println);
}
static void orElse(Optional<String> optString) {
System.out.println(optString.orElse("Nada"));
}
static void orElseGet(Optional<String> optString) {
System.out.println(
optString.orElseGet(() -> "Generated"));
}
static void orElseThrow(Optional<String> optString) {
try {
System.out.println(optString.orElseThrow(
() -> new Exception("Supplied")));
} catch(Exception e) {
System.out.println("Caught "+ e); }}static void test(String testName, Consumer<Optional<String>> cos) {
System.out.println(" === " + testName + " === ");
cos.accept(Stream.of("Epithets").findFirst());
cos.accept(Stream.<String>empty().findFirst());
}
public static void main(String[] args) {
test("basics", Optionals::basics);
test("ifPresent", Optionals::ifPresent);
test("orElse", Optionals::orElse);
test("orElseGet", Optionals::orElseGet);
test("orElseThrow", Optionals::orElseThrow); }}Copy the code
Output result:
=== basics ===
Epithets
Nothing inside!
=== ifPresent ===
Epithets
=== orElse ===
Epithets
Nada
=== orElseGet ===
Epithets
Generated
=== orElseThrow ===
Epithets
Caught java.lang.Exception: Supplied
Copy the code
Test () avoids duplicate code by passing in Consumer, where all methods apply.
OrElseThrow () uses the catch keyword to catch thrown exceptions. More details will be learned in the exceptions section.
Create the Optional
When we add Optional to our code, we can use the following three static methods:
empty()
: Generates a nullOptional.of(value)
: wraps a non-null value toOptionalIn the water.ofNullable(value)
: For a value that may be null, automatically generated if nullOptional.emptyOtherwise, wrap the value inOptionalIn the.
Here’s how it works. Code examples:
// streams/CreatingOptionals.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class CreatingOptionals {
static void test(String testName, Optional<String> opt) {
System.out.println(" === " + testName + " === ");
System.out.println(opt.orElse("Null"));
}
public static void main(String[] args) {
test("empty", Optional.empty());
test("of", Optional.of("Howdy"));
try {
test("of", Optional.of(null));
} catch(Exception e) {
System.out.println(e);
}
test("ofNullable", Optional.ofNullable("Hi"));
test("ofNullable", Optional.ofNullable(null)); }}Copy the code
Output result:
=== empty ===
Null
=== of ===
Howdy
java.lang.NullPointerException
=== ofNullable ===
Hi
=== ofNullable ===
Null
Copy the code
We cannot create Optional objects by passing NULL to of(). The safest way to handle null gracefully is to use ofNullable().
Optional object operation
When our stream pipeline generates Optional objects, there are three methods that enable Optional to do more:
-
Filter (Predicate) : Applies Predicate to the content in Optional and returns the result. Returns null when Optional does not satisfy Predicate. If Optional is empty, it returns directly.
-
Map (Function) : If Optional is not empty, apply Function to the contents of Optional and return the result. Otherwise, return Optional. Empty.
-
FlatMap (Function) : Same as map(), but the provided mapping Function wraps the result in an Optional object, so flatMap() does not wrap anything at the end.
None of the above methods apply to numeric Optional. In general, the filter() of a stream removes the stream elements when Predicate returns false. Optional.filter() does not remove Optional on failure, but instead leaves it in place and goes null.
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class OptionalFilter {
static String[] elements = {
"Foo".""."Bar"."Baz"."Bingo"
};
static Stream<String> testStream(a) {
return Arrays.stream(elements);
}
static void test(String descr, Predicate<String> pred) {
System.out.println("(" -- - + descr + ") -");
for(int i = 0; i <= elements.length; i++) { System.out.println( testStream() .skip(i) .findFirst() .filter(pred)); }}public static void main(String[] args) {
test("true", str -> true);
test("false", str -> false);
test("str ! = \ "\" ", str -> str ! ="");
test("str.length() == 3", str -> str.length() == 3);
test("startsWith(\"B\")",
str -> str.startsWith("B")); }}Copy the code
Output result:
---( true )--- Optional[Foo] Optional[] Optional[Bar] Optional[Baz] Optional[Bingo] Optional.empty ---( false )--- Optional.empty Optional.empty Optional.empty Optional.empty Optional.empty Optional.empty ---( str ! = "" )--- Optional[Foo] Optional.empty Optional[Bar] Optional[Baz] Optional[Bingo] Optional.empty ---( str.length() == 3 )--- Optional[Foo] Optional.empty Optional[Bar] Optional[Baz] Optional.empty Optional.empty ---( startsWith("B") )--- Optional.empty Optional.empty Optional[Bar] Optional[Baz] Optional[Bingo] Optional.emptyCopy the code
Even if the output looks like a stream, especially the for loop in test(). Each for loop restarts the stream and skips a specified number of elements based on the index of the for loop, which is what it ends up doing on each successive element in the stream. FindFirst () is then called to retrieve the first of the remaining elements, the result wrapped in Optional.
Note that unlike normal for loops, the index range here is not I < elements. Length, but I <= elements. So the last element is actually outside the stream. Conveniently, this will automatically become optional. empty, which you can see at the end of every test.
As with map(), optional.map () applies to functions. It only applies the mapping function if Optional is not empty and extracts the Optional content into the mapping function. Code examples:
// streams/OptionalMap.java
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Stream;
class OptionalMap {
static String[] elements = {"12".""."23"."45"};
static Stream<String> testStream(a) {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println("(" -- - + descr + ") -");
for (int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst() // Produces an Optional.map(func)); }}public static void main(String[] args) {
// If Optional is not empty, map() first extracts
// the contents which it then passes
// to the function:
test("Add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
} catch (NumberFormatException e) {
returns; }}); test("Replace", s -> s.replace("2"."9"));
test("Take last digit", s -> s.length() > 0 ?
s.charAt(s.length() - 1) + "" : s);
}
// After the function is finished, map() wraps the
// result in an Optional before returning it:
}
Copy the code
Output result:
---( Add brackets )---
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---( Increment )---
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---( Replace )---
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---( Take last digit )---
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
Copy the code
The return result of the mapping function is automatically wrapped as Optional. Optional.empty will be skipped.
The Optional flatMap() applies to the mapping functions that have been generated for Optional, so flatMap() does not encapsulate the results in Optional as map() does. Code examples:
// streams/OptionalFlatMap.java
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
class OptionalFlatMap {
static String[] elements = {"12".""."23"."45"};
static Stream<String> testStream(a) {
return Arrays.stream(elements);
}
static void test(String descr, Function
> func)
,> {
System.out.println("(" -- - + descr + ") -");
for (int i = 0; i <= elements.length; i++) { System.out.println( testStream() .skip(i) .findFirst() .flatMap(func)); }}public static void main(String[] args) {
test("Add brackets",
s -> Optional.of("[" + s + "]"));
test("Increment", s -> {
try {
return Optional.of(
Integer.parseInt(s) + 1 + "");
} catch (NumberFormatException e) {
returnOptional.of(s); }}); test("Replace",
s -> Optional.of(s.replace("2"."9")));
test("Take last digit",
s -> Optional.of(s.length() > 0 ?
s.charAt(s.length() - 1) + "": s)); }}Copy the code
Output result:
---( Add brackets )---
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---( Increment )---
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---( Replace )---
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---( Take last digit )---
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
Copy the code
As with map(), flatMap() extracts the non-empty Optional content and applies it to the mapping function. The only difference is that flatMap() does not wrap the result in Optional because the mapping function is already wrapped. In the example above, we’ve explicitly wrapped each mapping function, but it’s clear that optional.flatmap () is designed for functions that already generate Optional themselves.
Optional flow
Given that your generator may produce null values, it is natural to wrap elements in Optional when creating streams with it. Here’s what it looks like, with code examples:
// streams/Signal.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class Signal {
private final String msg;
public Signal(String msg) { this.msg = msg; }
public String getMsg(a) { return msg; }
@Override
public String toString(a) {
return "Signal(" + msg + ")";
}
static Random rand = new Random(47);
public static Signal morse(a) {
switch(rand.nextInt(4)) {
case 1: return new Signal("dot");
case 2: return new Signal("dash");
default: return null; }}public static Stream<Optional<Signal>> stream() {
returnStream.generate(Signal::morse) .map(signal -> Optional.ofNullable(signal)); }}Copy the code
When we use this stream, we have to figure out how to unpack Optional. Code examples:
// streams/StreamOfOptionals.java
import java.util.*;
import java.util.stream.*;
public class StreamOfOptionals {
public static void main(String[] args) {
Signal.stream()
.limit(10)
.forEach(System.out::println);
System.out.println("-");
Signal.stream()
.limit(10) .filter(Optional::isPresent) .map(Optional::get) .forEach(System.out::println); }}Copy the code
Output result:
Optional[Signal(dash)] Optional[Signal(dot)] Optional[Signal(dash)] Optional.empty Optional.empty Optional[Signal(dash)] Optional.empty Optional[Signal(dot)] Optional[Signal(dash)] Optional[Signal(dash)] --- Signal(dot) Signal(dot) Signal(dash) Signal(dash)Copy the code
Here, we use filter() to keep the non-empty Optional, and then use get() in map() to get the elements. Since the meaning of “null value” needs to be defined in each case, we usually take a different approach for each application.
Terminal operation
The following operations will get the final result of the stream. At this point we can no longer pass the flow forward. A terminal operation is always the last thing we do in a stream pipeline, so to speak.
An array of
toArray()
: Converts the flow to an array of the appropriate type.toArray(generator)
: Generates arrays of custom types in special cases.
The above method is useful when we need to get data of array type for subsequent operations. Suppose we need to reuse random numbers generated by the stream. Code examples:
// streams/RandInts.java
package streams;
import java.util.*;
import java.util.stream.*;
public class RandInts {
private static int[] rints = new Random(47).ints(0.1000).limit(100).toArray();
public static IntStream rands(a) {
returnArrays.stream(rints); }}Copy the code
The above example converts a stream of 100 random numbers ranging from 0 to 1000 into an array and stores them in RINTS. This way, the same stream of integers can be retrieved repeatedly each time rands() is called.
cycle
forEach(Consumer)
Such as commonSystem.out::println
As aConsumerFunction.forEachOrdered(Consumer)
: ensure thatforEach
Operate in the original stream order.
The first form, unordered operations, makes sense only when parallel flows are introduced. We won’t delve into this until the section on concurrent programming. Parallel () enables parallel operation of multiple processors. This works by dividing a stream into multiple (usually CPU cores) and performing operations on different processors. This is possible because we are iterating internally, not externally.
Parallel () is deceptively simple, but actually tricky. Learn more about this later in the concurrent programming section.
The following example introduces parallel() to help understand the role and usage scenarios of forEachOrdered(Consumer). Code examples:
// streams/ForEach.java
import java.util.*;
import java.util.stream.*;
import static streams.RandInts.*;
public class ForEach {
static final int SZ = 14;
public static void main(String[] args) {
rands().limit(SZ)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
rands().limit(SZ)
.parallel()
.forEach(n -> System.out.format("%d ", n));
System.out.println();
rands().limit(SZ)
.parallel()
.forEachOrdered(n -> System.out.format("%d ", n)); }}Copy the code
Output result:
258 555 693 861 961 429 868 200 522 207 288 128 551 589 561 429 589 200 522 555 693 258 128 868 288 961 207 258 555 693 861 961 429 868 200 522 207 288 128 551 589Copy the code
To make it easier to test arrays of different sizes, we have isolated the SZ variable. The results are interesting: In the first stream, parallel() is not used, so rands() displays the results in the order in which the elements are iterated; In the second stream, parallel() is introduced, and even if the stream is small, the output will be in a different order. This is due to multiple processors operating in parallel. Run the test multiple times with different results. This result is caused by the uncertainty caused by the parallel operation of multiple processors.
In the last flow, parallel() and forEachOrdered() are used to enforce the original flow order. Therefore, using forEachOrdered() on non-parallel streams has no effect at all.
A collection of
collect(Collector)
Use:CollectorCollect the flow elements into the result set.collect(Supplier, BiConsumer, BiConsumer)
: Same as above, the first parameterSupplierA new result set is created with the second parameterBiConsumerInclude the next element in the result, the third parameterBiConsumerUsed to combine two values.
This section introduces a few examples of Collectors. In fact, it also has some very complex operation, but by looking at the Java. Util. Stream. The API documentation for Collectors. For example, we can collect elements into any kind of specific collection.
Suppose we now store the elements in a TreeSet to keep them ordered. There is no particular toTreeSet() in Collectors, but you can build any kind of collection by passing a constructor reference to the collection to receiver.toCollection (). Let’s gather words from a file into the TreeSet collection. Code examples:
// streams/TreeSetOfWords.java
import java.util.*;
import java.nio.file.*;
import java.util.stream.*;
public class TreeSetOfWords {
public static void
main(String[] args) throws Exception {
Set<String> words2 =
Files.lines(Paths.get("TreeSetOfWords.java"))
.flatMap(s -> Arrays.stream(s.split("\\W+"))) .filter(s -> ! s.matches("\\d+")) // No numbers
.map(String::trim)
.filter(s -> s.length() > 2)
.limit(100)
.collect(Collectors.toCollection(TreeSet::new)); System.out.println(words2); }}Copy the code
Output result:
[Arrays, Collectors, Exception, Files, Output, Paths,
Set, String, System, TreeSet, TreeSetOfWords, args,
class, collect, file, filter, flatMap, get, import,
java, length, limit, lines, main, map, matches, new,
nio, numbers, out, println, public, split, static,
stream, streams, throws, toCollection, trim, util,
void, words2]
Copy the code
Files.lines() opens Path and converts it to a row stream. The next line of code will match one or more non-word character (\\w+) lines for partitioning, then convert them to streams using arrays.stream () and flatmap the results to word streams. Use matches(\\d+) to find and remove the all-numeric string (note that Words2 passed). Next we use string.trim () to trim the whitespace around the words, filter() to filter all words less than 3 in length, then get only 100 words, and save them to the TreeSet.
We can also generate maps in the stream. Code examples:
// streams/MapCollector.java
import java.util.*;
import java.util.stream.*;
class Pair {
public final Character c;
public final Integer i;
Pair(Character c, Integer i) {
this.c = c;
this.i = i;
}
public Character getC(a) { return c; }
public Integer getI(a) { return i; }
@Override
public String toString(a) {
return "Pair(" + c + "," + i + ")"; }}class RandomPair {
Random rand = new Random(47);
// An infinite iterator of random capital letters:
Iterator<Character> capChars = rand.ints(65.91)
.mapToObj(i -> (char)i)
.iterator();
public Stream<Pair> stream(a) {
return rand.ints(100.1000).distinct()
.mapToObj(i -> newPair(capChars.next(), i)); }}public class MapCollector {
public static void main(String[] args) {
Map<Integer, Character> map =
new RandomPair().stream()
.limit(8) .collect( Collectors.toMap(Pair::getI, Pair::getC)); System.out.println(map); }}Copy the code
Output result:
{688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N}
Copy the code
A Pair is just a basic data object. RandomPair creates a stream of randomly generated Pair objects. In Java, we cannot directly combine two streams in some way. So we create an integer stream and use mapToObj() to turn it into a Pair stream. The randomly generated capital iterator of capChars starts with a stream, and then iterator() allows us to use it in stream(). As far as I know, this is the only way to combine multiple streams to generate a new object stream.
Here, we will use only the simplest form, receipts.tomap (), whose method value requires a function that can get key-value pairs from the stream. There are other forms of overloading, including one in which a function is required to handle key-value collisions.
In most cases, Java. Util. Stream. Collectors in the presupposition of the Collector can meet our requirements. In addition, you can use the second form of collect(). I’ll save it for a more advanced exercise, but the following example shows the basic usage:
// streams/SpecialCollector.java
import java.util.*;
import java.util.stream.*;
public class SpecialCollector {
public static void main(String[] args) throws Exception {
ArrayList<String> words =
FileToWords.stream("Cheese.dat")
.collect(ArrayList::new,
ArrayList::add,
ArrayList::addAll);
words.stream()
.filter(s -> s.equals("cheese")) .forEach(System.out::println); }}Copy the code
Output result:
cheese
cheese
Copy the code
Here, the ArrayList methods already do what you need, but it seems more likely that you will have to create your own special definitions if you have to use this form of collect().
combination
reduce(BinaryOperator)
Use:BinaryOperatorTo combine all the elements in the stream. Because the stream may be empty, its return value isOptional.reduce(identity, BinaryOperator)
: Same function as above, but useidentityAs the initial value of its composition. So if the stream is empty,identityThat’s the result.reduce(identity, BiFunction, BinaryOperator)
: A more complex form of use (not covered yet) is included here because it can increase efficiency. In general, we can combine them explicitlymap()
和reduce()
To make it a little bit simpler.
Here is an example of reduce code:
// streams/Reduce.java
import java.util.*;
import java.util.stream.*;
class Frobnitz {
int size;
Frobnitz(int sz) { size = sz; }
@Override
public String toString(a) {
return "Frobnitz(" + size + ")";
}
// Generator:
static Random rand = new Random(47);
static final int BOUND = 100;
static Frobnitz supply(a) {
return newFrobnitz(rand.nextInt(BOUND)); }}public class Reduce {
public static void main(String[] args) {
Stream.generate(Frobnitz::supply)
.limit(10)
.peek(System.out::println)
.reduce((fr0, fr1) -> fr0.size < 50? fr0 : fr1) .ifPresent(System.out::println); }}Copy the code
Output result:
Frobnitz(58)
Frobnitz(55)
Frobnitz(93)
Frobnitz(61)
Frobnitz(61)
Frobnitz(29)
Frobnitz(68)
Frobnitz(0)
Frobnitz(22)
Frobnitz(7)
Frobnitz(29)
Copy the code
Frobnitz contains a generator named supply(); Because this method is signature-compatible for Supplier
, we can pass its method reference to stream.generate () (this signature compatibility is called structural consistency). The return value of the reduce() method without an initial value is Optional. Option.ifpresent () only calls Consumer
if the result is not empty (println is called because Frobnitz can be converted toString via toString()).
The first argument fr0 in the Lambda expression is the result of the last call to reduce(). The second parameter, FR1, is the value passed in from the stream.
Lambda expressions in reduce() use a ternary expression to get the result, fr0 if its length is less than 50 or fr1 if it is the next value in the sequence. When the first Frobnitz of length less than 50 is obtained, the rest is ignored as soon as the result is obtained. This is a very strange constraint, and it does teach us a lot about Reduce ().
matching
allMatch(Predicate)
: if each element of the stream is based on the suppliedPredicateReturn true if both return true. On the first false, the calculation is stopped.anyMatch(Predicate)
: if any element in the stream is based on the suppliedPredicateThe result returns true when true is returned. The first false stops the calculation.noneMatch(Predicate)
: if each element of the stream is based on the suppliedPredicateIf both return false, the result returns true. Stop the calculation on the first true.
We’ve already seen an example of noneMatch() in Prim.java; AllMatch () and anyMatch() are basically the same. Let’s explore the short circuit behavior. To eliminate redundant code, we create show(). We must first know how to describe the operations of these three matchers uniformly, and then convert them into Matcher interfaces. Code examples:
// streams/Matching.java
// Demonstrates short-circuiting of *Match() operations
import java.util.stream.*;
import java.util.function.*;
import static streams.RandInts.*;
interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {}
public class Matching {
static void show(Matcher match, int val) {
System.out.println(
match.test(
IntStream.rangeClosed(1.9)
.boxed()
.peek(n -> System.out.format("%d ", n)),
n -> n < val));
}
public static void main(String[] args) {
show(Stream::allMatch, 10);
show(Stream::allMatch, 4);
show(Stream::anyMatch, 2);
show(Stream::anyMatch, 0);
show(Stream::noneMatch, 5);
show(Stream::noneMatch, 0); }}Copy the code
Output result:
1 2 3 4 5 6 7 8 9 true
1 2 3 4 false
1 true
1 2 3 4 5 6 7 8 9 false
1 false
1 2 3 4 5 6 7 8 9 true
Copy the code
BiPredicate is a binary predicate that takes two arguments and only returns true or false. Its first argument is the flow we want to test, and its second argument is a Predicate. Matcher applies to all Stream::*Match methods, so we can pass each one to show(). The call to mate.test () is converted to a call to the Stream::* match function.
Show () takes two arguments, the Matcher Matcher Matcher and val, which represents the maximum value in the predicate test n < val. This method generates a stream of integers between 1 and 9. Peek () is used to show us the test before short-circuiting. You can see from the output that there was a short circuit each time.
To find the
findFirst()
: returns the first stream elementOptionalReturns if the stream is emptyOptional.empty.findAny(
: Returns any stream elementOptionalReturns if the stream is emptyOptional.empty.
Code examples:
// streams/SelectElement.java
import java.util.*;
import java.util.stream.*;
import static streams.RandInts.*;
public class SelectElement {
public static void main(String[] args) { System.out.println(rands().findFirst().getAsInt()); System.out.println( rands().parallel().findFirst().getAsInt()); System.out.println(rands().findAny().getAsInt()); System.out.println( rands().parallel().findAny().getAsInt()); }}Copy the code
Output result:
258
258
258
242
Copy the code
FindFirst () always selects the first element in a stream whether or not it is parallelized. For non-parallel streams, findAny() selects the first element in the stream (even if, by definition, it selects any element). In this example, we use parallel() to parallel the stream, introducing the possibility of findAny() selecting a non-first stream element.
If you must select the last element in the stream, use reduce(). Code examples:
// streams/LastElement.java
import java.util.*;
import java.util.stream.*;
public class LastElement {
public static void main(String[] args) {
OptionalInt last = IntStream.range(10.20)
.reduce((n1, n2) -> n2);
System.out.println(last.orElse(-1));
// Non-numeric object:
Optional<String> lastobj =
Stream.of("one"."two"."three")
.reduce((n1, n2) -> n2);
System.out.println(
lastobj.orElse("Nothing there!")); }}Copy the code
Output result:
19
three
Copy the code
The reduce() argument simply replaces the last two elements with the last one, resulting in only the last element being generated. For numeric streams, you must use a similar numeric Optional type (numeric Optional Type), otherwise use the Optional type, as in the above example Optional
.
information
count()
: Number of elements in the stream.max(Comparator)
: According to incomingComparatorThe “maximum” element determined.min(Comparator)
: According to incomingComparatorThe “smallest” element determined.
The String type has a default Comparator implementation. Code examples:
// streams/Informational.java
import java.util.stream.*;
import java.util.function.*;
public class Informational {
public static void
main(String[] args) throws Exception {
System.out.println(
FileToWords.stream("Cheese.dat").count());
System.out.println(
FileToWords.stream("Cheese.dat")
.min(String.CASE_INSENSITIVE_ORDER)
.orElse("NONE"));
System.out.println(
FileToWords.stream("Cheese.dat")
.max(String.CASE_INSENSITIVE_ORDER)
.orElse("NONE")); }}Copy the code
Output result:
32
a
you
Copy the code
Min () and Max () return Optional, which requires us to unpack using orElse().
Digital stream information
average()
: Takes the average value of the stream elements.max()
和min()
: No need for numerical flow operationsComparator.sum()
: sums all the stream elements.summaryStatistics()
: Generates potentially useful data. It’s not clear that this method is necessary, because we can get the data we need more directly.
// streams/NumericStreamInfo.java
import java.util.stream.*;
import static streams.RandInts.*;
public class NumericStreamInfo {
public static void main(String[] args) { System.out.println(rands().average().getAsDouble()); System.out.println(rands().max().getAsInt()); System.out.println(rands().min().getAsInt()); System.out.println(rands().sum()); System.out.println(rands().summaryStatistics()); }}Copy the code
Output result:
507.94 998 8 50794 IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, Max =998}Copy the code
The above example applies to both LongStream and DoubleStream.