// iterate on a List<String> words
int count = 0;
for (String w: words) {
if (w.length() > 12) count++;
}
// same operations with stream
long count = words.stream().filter(w -> w.length() > 12).count();-
Streams follow the "What, not how" principle. In the example above, we describe what needs to be done: get the long words and count them.
-
significant differences between
StreamandCollections- A Stream does not store its elements
- stored in underlying colletion OR generated on demand.
- Stream operations don't mutate their source.
filtermethod yields a new stream
- Stream operations are lazy when possible, they are not executed until their result is need.
- A Stream does not store its elements
-
the typical Workflow with streams 结合上面的例子来看
- Create a stream (1.2)
- Specify intermediate operations for transforming the initial stream into others, possibly in mutiple steps. (1.3, 1.4, 1.5)
- Apply a terminal operation to produce a result. (1.6, 1.8, 1.9)
- this operation forces the execution of the lazy operations that precede it.
-
(1.1) streams/CountLongWords.java
- turn any collection into stream with
streamandparallelStreammethods - Use static
Stream.offor an array - Use
Arrays.stream(array, from, to)to make a stream from a part of an array. - Use
Stream.empty()to make a stream with no elements.
// split returns a String[] array
Stream<String> words = Stream.of(contents.split("\\PL+"));
// of method has a varargs parameter
Stream<String> song = Stream.of("gently", "down", "the", "stream");- two static method for making infinite streams.
generatemethod takes a function with no arguments (an object ofSupplier<T>interface)iteratemethod takes a "seed" value and a function (aUnaryOperator<T>) and repeatly applies the function to the previous result.- to produce a finite stream, add a
predicatethat specifies when the iteration should finish.
- to produce a finite stream, add a
Stream<String> echos = Stream.generate(() -> "Echo");
Stream<String> randoms = Stream.generate(Math::random);
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
// finite stream
var limit = new BigIteger("10000000");
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO,
n -> n.compareTo(limit) < 0, // predicate
n -> a.add(BigInteger.ONE));-
Stream.ofNullablemakes a really short stream from an object- empty stream of the
object == null, otherwise, contains only one element just theobject - useful in conjunction with
flatMap
- empty stream of the
-
Other API methods yielding streams
Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents);
// yiels a stream of tokens of a scanner
Stream<String> words = new Scanner(contents).tokens();
try (Stream<String> lines = Files.lines(path)) {
// Process lines
}-
turn an
Iterablethat's not a collection into a streamStreamSupport.stream(iterable.spliterator(), false);
-
hava an
Iteratorand want a stream of its resutlsStreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
-
We should not modify the underlying collection of a stream. noninterferences
List<String> wordList = ...;
Stream<String> words = wordList.stream();
wordList.add("END"); // will work but NOT recommended
long n = words.distinct().count();
Stream<String> words = wordList.stream();
words.forEach(s -> if(s.length() < 12) wordList.remove(s)); // ERROR - interference- (1.2) streams/CreatingStreams.java
- A stream transformation produces a stream whose elements are derived from those of another stream.
// filter
Stream<String> longWords = words.stream().filter(w -> w.length() > 12);
// map
Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
Stream<String> firstLetters = words.stream().map(s -> s.substring(0, 1));
// map and flatMap, suppose codePoints return Stream<String>
Stream<Stream<String>> result = words.stream().map(w -> codePoints(w));
Stream<String> result = words.stream().flatMap(w -> codePoints(w));java.util.stream.Stream |
description |
|---|---|
Stream<T> filter(Predicate<? super T> predicate) |
yields a new stream with those elements that fulfill the predicate. |
<R> Stream<R> map(Function<? super T, ? extends R> mapper) |
the mapper will be applied to each element and yields a new stream containing the results |
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) |
flatten a stream of stream to a single stream |
java.util.stream.Stream |
description |
|---|---|
Stream<T> limit(long maxSize) |
yields a stream with up to maxSize of the initial elements from this stream |
Stream<T> skip(long n) |
skip the initial n elements of this stream |
Stream<T> takeWhile(Predicate<? super T> predicate) |
yields a stream whose elements are the initial elements of this stream that fulfill the predicate. |
Stream<T> dropWhile(Predicate<? super T> predicate) |
dropping elements while a condition is true. |
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) |
yields a stream whose elements are the elements of a followed by the elements of b. a should not be infinite otherwise b wouldn't get a chance. |
java.util.stream.Stream |
description |
|---|---|
Stream<T> distinct() |
yields a stream of the distinct elements of this stream. |
Stream<T> sorted() |
elements are instances of a class implementing Comparable |
Stream<T> sorted(Comparator<? super T> comparator) |
yields as tream whose elements are the elements of this stream in sorted order. |
Stream<T> peek(Consumer<? super T> action) |
yields a stream with the same elements as this stream, passing each element to action it is consumed. |
- reductions, terminal operations,
- they reduce the stream to a nonstream value that can be used in your program.
- these methods return an
Optional<T>value that either wraps the answer or indicates that there is none.
Optional<String> largest = words.max(String::compareToIgnoreCase);
System.out.println("largest: " + largest.orElse(""));
// find first match
Optional<String> startsWithQ = words.filter(s -> s.startWith("Q")).findFirst();
// find any match, effective when you parallelize the stream
Optional<String> startsWithQ = words.parallel().filter(s -> s.startWith("Q")).findAny();
//check if any match exists
boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));java.util.stream.Stream |
|---|
Optional<T> max(Comparator<? super T> comparator) |
Optional<T> min(Comparator<? super T> comparator) |
Optional<T> findFirst() |
Option<T> findAny() |
boolean anyMatch(Predicate<? super T> predicate) |
boolean allMatch(Predicate<? super T> predicate) |
boolean noneMatch(Predicate<? super T> predicate) |
An Optional<T> object is a wrapper for either an object of type T or no object.
- three ways to produce an alternative if no value is present
String result = optionalString.orElse("");
String result = optionalString.orElseGet(() -> System.getProperty("myapp.default"));
String result = optionalString.orElseThrow(IllegalStateException::new);java.util.Optional |
|---|
T orElse(T other) |
T orElseGet(Supplier<? extends T> other) |
<X extends Throwable> T orElseThrow(Suppier<? extends X> exceptionSupplier) |
- one ways to consume the value only if it is present.
optionalValue.ifPresent(v -> results.add(v));
- one action for presence, one action for absence
optionalValue.ifPresentOrElse( v -> System.out.println("Found " + v), () -> logger.warning("No match") );
- transforming the value inside an
Optionalby usingmapmethodOptional<String> transformed = optionalString.map(String::toUpperCase);- if the
optionalStringis empty, thentransformedis also empty;
- we can add to a list if it's present
optionalValue.map(results::add);- if
optionalValueis empty, nothing happens.
- use
filtermethod before or after transfoming itOptional<String> transformed = optionalString.filter(s -> s.length() >= 8).map(String::toUpperCase);
- substitute an alternative
Optionalfor an emptyOptionalwithormethodOptional<String> result = optionalString.or(() -> alternatives.stream().findFirst());- computed lazily
- lambda will be executed only if
optionalStringis empty.
- lambda will be executed only if
You should use Optional correctly
Optional<T> optionalValue = ...;
optionalValue.get().someMethod(); // not good
// no safer than
T value = ...;
value.someMethod();
if (optionalValue.isPresent()) optionalValue.get().someMethod();
// no safer than
if (value != null) value.someMethod();- Tips:
- A variable of type
Optionalshould never be null. - Don't use fields of type
Optional. The cost is an additional object.- Inside a class, using
nullfor an absent field is manageable.
- Inside a class, using
- Don't put
Optionalobjects in a set, and don't use them as keys for a map. Collect the values instead.
- A variable of type
- static method
Optional.of(result)andOptional.empty() Optional.ofNullable(object)- returns
Optional.of(object)if object is not null - otherwise return
Optional.empty()
- returns
public static Optional<Double> inverse(Double x) {
return x == 0 ? Optional.empty() : Optional.of(1/x);
}- Suppose
s.f()returnsOptional<T>, then T.g() returnsOptional<U>Optional<U> result = s.f().flatMap(T::g);- if
s.f()is present,T.g()will called. otherwise,Optional<U>is empty
- You can build a pipeline of steps, simply by chaining calls to
flatMap, that will succeed only when all parts do.Optional<Double> result = Optional.of(-4.0).flatMap(Demo::inverse).flatMap(Demo::squareRoot);
stream()method turns anOptional<T>into aStream<T>with zero or one element.isPresent()+get()forOptional<T>object is NOT recommended!
// Suppose Optional<User> lookup(String id) method
Stream<String> ids = ...;
Stream<User> users = ids.map(Users::lookup).filter(Optional::isPresent).map(Optional::get); ❌
Stream<User> users = ids.map(Users::lookup).flatMap(Optional::stream); ✅
// Suppose User lookup(String id) method, return null if no valid result.
Stream<User> users = ids.map(Users::lookup).filter(Objects::nonNull); // option 1
Stream<User> users = ids.flatMap(id -> Stream.ofNullable(Users.lookup(id))); // option 2
Stream<User> users = ids.map(Users::lookup).flatMap(Stream::ofNullable); // option 3- (1.3) optional/OptionalTest.java
- When done with a stream, try to look at the results
- old-fashioned
iteratorto visit all elements (NOT recommended ❌) forEach()apply a function to each element:stream.forEach(System.out::println)- On a parallel stream, the
forEachmethod traverses elements in arbitrary order, can useforEachOrdered()method instead
- On a parallel stream, the
- use
toArray()to get an array of the stream elements:String[] result = stream.toArray(String[]::new)Object[]by default unless pass array constructor.
⚠️ collect()method taking an instance of theCollectorinterface. 最常用- A
collectoris an object that accumulates elements and produces a result. Collectorsclass provides lots of factory methods for common collectors.Collectors.toList()Collectors.toSet()Collectors.toCollection(TreeSet::new)- control specificCollectors.joining()- collect all strings in a stream by concatenating them, can add delimiterCollectors.summarizing(Int|Long|Double)methods return type(Int|Long|Double)SummaryStatistics
- A
List<String> result = stream.collect(Collectors.toList());
Set<String> result = stream.collect(Collectors.toSet());
// specify the collection type
TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
result = noVowels().limit(10).collect(Collectors.joining(", "));
IntSummaryStatistics summary = noVowels().collect(Collectors.summarizingInt(String::length));
double averageWordLength = summary.getAverage();
double maxWordLength = summary.getMax();- (1.4) collecting/CollectingResults.java
- turn a stream into a map, use
Collectors.toMap()- required first two functional arguments produce keys and values
- when value is actual elements:
Function.identity()
- when value is actual elements:
- optional resolve the conflict of duplicate keys
(existingValue, newValue) => { return modifiedValue; }, otherwise throwIllegalStateException
- optional specify the implementation,
TreeSet::new
- required first two functional arguments produce keys and values
Map<String, Set<String>> countryLanguageSets = Locales.collect(Collectors.toMap(
Locale::getDisplayCountry,
l -> Collections.singleton(l.getDisplayLangauge()),
(a, b) -> {
var union = new HashSet<String>(a);
union.addAll(b);
return union; // union of a and b
},
TreeMap::new
));- (1.5) collecting/CollectingIntoMaps.java
groupingBy: forming groups of values with same characteristics- The function
Locale::getCountryis the classifer function of the grouping.
- The function
Map<String, List<Locale>> countryToLocales =
locales.collect(Collectors.groupingBy(Locale::getCountry));-
When the classifier function is a predicate function (functions returning
booleanvalues)- the stream elements are partitioned into two lists,
trueorfalsekey - In such a case, use
paritioningByis more efficient
Map<Boolean, List<Locale>> englishAndOtherLocales = locales.collect( Collectors.partioningBy(l -> l.getLanguage().equals("en")); ); List<Locale> englishLocales = englishAndOtherLocales.get(true);
- the stream elements are partitioned into two lists,
-
groupingBymethod yields a map whose value are lists.- You can supply a
downstream collectorto process those lists
// when you want set instead of list Map<String, Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry, toSet()));
- You can supply a
-
other collectors used here for reducing elements to numbers
counting()summing(Int|Long|Double)produce a sum
Map<String, Integer> countryToLocaleCounts = locales.collect( groupingBy(Locale::getCountry, summingInt(City::getPopulation)) );
maxByandminBytake a comparator and produce max and min of the downstream elements
Map<String, Optional<City>> stateToLargestCity = locales.collect( groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation))) );
-
collectingAndThencollector adds a final processing step behind a collector.Map<Character, Integer> stringCountsByStartingLetter = strings.collect( groupingBy(s -> s.charAt(0), collectingAndThen(toSet(), Set::size)) );
-
mappingapplies a function to each collected element and passes the results to a downstream collector.flatMapping, for use with functions that returns stream
Map<Character, Set<Integer>> stringLengthsByStartingLetter = strings.collect( groupingBy(s -> s.charAt(0), mapping(String::length, toSet())) ); // gathering a set of all languages in a country Map<String, Set<String>> countryToLangauges = locales.collect( groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, toSet())) );
-
summarizing(Int|Long|Double), grouping or mapping function returnint,long,double- return a summary statistics object providing, sum, count, average, min, max.
Map<String, IntSummaryStatistics> stateToCityPopulationSummary = cities.collect( groupingBy(City::getState, summarizingInt(City::getPopulation)) );
-
filteringMap<String, Set<City>> largeCitiesByState = cities.collect( groupingBy(City::getState, filtering(c -> c.getPopulation() > 500_000, toSet())) );
...
-
(1.6) collecting/DownstreamCollectors.java
-
the
reducemethod- a general mechanism for computing a value from a stream.
- the simplest form takes a binary function and keeps applying it, starting with the first two elements.
- many operations in practice: sum, product, string concetenation, max and min, set union or intersection
List<Integer> values = ...; Optional<Integer> sum = values.streams().reduce((x, y) -> x + y); // Integer::sum
-
operations must be associative when using reduction with parallel stream
- It shouldn't matter in which order you combine the elements.
-
set the start value (aka identity) of the computation
- no longer Optional, when the stream is empty, return start value
Integer sum = value.streams().reduce(0, (x, y) -> x + y);
-
to get the sum of length of an array of strings
- you need to provide an accumulator first (parallel by default, so will generate multiple results)
BiFunction<T, U, T>interface - T, U params, returns T
- then you need to combine their results. (an combiner)
- for sequential stream, combiner will not be executed.
BinaryOperator<T> extends BiFunction<T,T,T>interface - T, T params, returns T
// int total, String word, so it's a accumulator int results = words.reduce(0, (total, word) -> total + word.length(), Integer::sum); // HOWEVER, usually, use map is more efficient and clearner int results = words.mapToInt(String::length).sum();
- you need to provide an accumulator first (parallel by default, so will generate multiple results)
-
sometimes,
reduceis not general enough. ❗️- suppose you want to collect results in a BitSet parallelly. because reduce is not thread-safe, you should use collect instead with three arguments ❗️
- A supplier to make new instances of the target object
Supplier<R>interface, no params, return R
- An accumlator that adds an element to the target
BiConsumer<R, T>interface, no returns, R absorb T
- A combiner that merges two objects into one
- for sequential stream, combiner will not be executed. 这里跟 reducer 类似
BiConsumer<R, T>interface, no returns, R absorb T
- A supplier to make new instances of the target object
BitSet result = stream.collect(BitSet::new, BitSet::set, BitSet::or);
- suppose you want to collect results in a BitSet parallelly. because reduce is not thread-safe, you should use collect instead with three arguments ❗️
- Stream library has specialized types
IntStream,LongStream,DoubleStreamthat store primitive types directly, withou using wrappers.- store
short,char,byte,boolean, useIntStream - store
float, useDoubleStream
- store
- besides
generateanditerate,IntStreamandLongStreamhave static methodsrangeandrangeClosedto generate integer ranges with step size one.
IntStream zeroToNinetyNine = IntStream.range(0, 100); // Upper bound is excluded
IntStream zeroToHundred = IntStream.rangeClosed(0, 100); // Upper bound is included-
The
CharSequenceinterface has methodscodePointsandcharsthat yields anIntStreamof the Unicode codes of the characters or of the code units in the UTF-16 encoding. -
can use
mapToInt,mapToLong,mapToDoublemethods to transform a object stream to primitive stream
Stream<String> words = ...;
IntStream lengths = words.mapToInt(String::length);-
use
boxed()method to turn a primitive stream to a wrapper object streamStream<Integer> integers = IntStream.rang(0, 100).boxed();
-
notable difference between primitve stream and object stream
toArray()return primitive type arrays- yields
OptionalInt,OptionalLong,OptionalDoubleinstread ofOptionalclass- they have
getAsInt,getAsLong,getAsDoubleinstead ofgetmethod
- they have
- they have
sum,average,max,min, not defined for object streams summaryStatisticsmethod yields an object ofIntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics- can report sum, count, average, min, max.
-
(1.7) streams/PrimitiveTypeStreams.java
-
parallel stream is easy for bulk operation.
-
paralleleStream()turn any collection into a parallel stream -
parallel()turn any sequential stream into a parallel one. -
any functions passed to to parallel stream should be safe to execute in parallel.
- best way is to stay away from mutatable state, otherwise race condition may occur
-
unordered()indicating that you are not interested in ordering.- dropping orders may speed up some operations,
limit(),distinct() Stream<String> sample = words.parallelStream().unordered().limit(n);
- dropping orders may speed up some operations,
-
merging maps concurrently
Collectors.groupingByConcurrentmethod uses a shared concurrent map.
Map<Integer, List<String>> result = words.parallelStream().collect( // values aren't ordered in stream order Collectors.groupingByConcurrent(String::length) ); Map<Integer, Long> wordCounts = words.parallelStream().collect( // use a downstream collector independent of the ordering groupingByConcurrent(String::length, counting()) );
-
keep in mind (don't turn all streams into parallel)
- a substantial overhead to parallelization that will only pay off for very large data sets.
- Parallelizing a stream is only a win if the underlying data source can be effectively split into multiple parts
- The thread pool that is used by parallel streams can be starved by blocking operations such as file I/O or network access.
-
Parallel streams work best with huge in-memory collections of data and computationally intensive processing.
-
if wanting to parallelize stream based on random numbers
- don't use streams from
Random.ints,Random.longs, orRandom.doubles, these don't split - use
ints,longs,doublesmethods ofSplittableRandomclass
- don't use streams from
-
(1.8) parallel/ParallelStreams.java