Stream是Java 8 提供的高效操作集合類(Collection)數(shù)據(jù)的API。
1. 從Iterator到Stream
有一個字符串的list术瓮,要統(tǒng)計其中長度大于7的字符串的數(shù)量,用迭代來實現(xiàn):
List<String> wordList = Arrays.asList("regular", "expression", "specified", "as", "a",
"string", "must");
int countByIterator = 0;
for (String word: wordList) {
if (word.length() > 7) {
countByIterator++;
}
}
用Stream實現(xiàn):
long countByStream = wordList.stream().filter(w -> w.length() > 7).count();
顯然,用stream實現(xiàn)更簡潔,不僅如此逻悠,stream很容易實現(xiàn)并發(fā)操作肖卧,比如:
long countByParallelStream = wordList.parallelStream().
filter(w -> w.length() > 7).count();
stream遵循的原則是:告訴我做什么蚯窥,不用管我怎么做。比如上例:告訴stream通過多線程統(tǒng)計字符串長度塞帐,至于以什么順序拦赠、
在哪個線程中執(zhí)行,由stream來負(fù)責(zé)葵姥;而在迭代實現(xiàn)中荷鼠,由于計算的方式已確定,很難優(yōu)化了榔幸。
Stream和Collection的區(qū)別主要有:
- stream本身并不存儲數(shù)據(jù)允乐,數(shù)據(jù)是存儲在對應(yīng)的collection里矮嫉,或者在需要的時候才生成的;
- stream不會修改數(shù)據(jù)源牍疏,總是返回新的stream蠢笋;
- stream的操作是懶執(zhí)行(lazy)的:僅當(dāng)最終的結(jié)果需要的時候才會執(zhí)行,比如上面的例子中鳞陨,結(jié)果僅需要前3個長度大于7
的字符串昨寞,那么在找到前3個長度符合要求的字符串后,filter()
將停止執(zhí)行厦滤;
使用stream的步驟如下:
- 創(chuàng)建stream编矾;
- 通過一個或多個中間操作(intermediate operations)將初始stream轉(zhuǎn)換為另一個stream;
- 通過中止操作(terminal operation)獲取結(jié)果馁害;該操作觸發(fā)之前的懶操作的執(zhí)行窄俏,中止操作后,該stream關(guān)閉碘菜,不能再
使用了凹蜈;
在上面的例子中,wordList.stream()
和wordList.parallelStream()
是創(chuàng)建stream忍啸,filter()
是中間操作仰坦,過
濾后生成一個新的stream,count()
是中止操作计雌,獲取結(jié)果悄晃。
2. 創(chuàng)建Stream的方式
-
從array或list創(chuàng)建stream:
Stream<Integer> integerStream = Stream.of(10, 20, 30, 40);
String[] cityArr = {"Beijing", "Shanghai", "Chengdu"};
Stream<String> cityStream = Stream.of(cityArr);
Stream<String> nameStream = Arrays.asList("Daniel", "Peter", "Kevin").
stream();
Stream<String> cityStream2 = Arrays.stream(cityArr, 0, 1);
Stream<String> emptyStream = Stream.empty(); -
通過
generate
和iterate
創(chuàng)建無窮stream:Stream<String> echos = Stream.generate(() -> "echo");
Stream<Integer> integers = Stream.iterate(0, num -> num + 1); -
通過其它API創(chuàng)建stream:
Stream<String> lines = Files.lines(Paths.get("test.txt"))
String content = "AXDBDGXC";
Stream<String> contentStream = Pattern.compile("[ABC]{1,3}").
splitAsStream(content);
3. Stream轉(zhuǎn)換
-
filter()
用于過濾,即使原stream中滿足條件的元素構(gòu)成新的stream:List<String> langList = Arrays.asList("Java", "Python", "Swift", "HTML");
Stream<String> filterStream = langList.stream().filter(lang -> lang.equalsIgnoreCase("java")); -
map()
用于映射凿滤,遍歷原stream中的元素妈橄,轉(zhuǎn)換后構(gòu)成新的stream:List<String> langList = Arrays.asList("Java", "Python", "Swift", "HTML");
Stream<String> mapStream = langList.stream().map(String::toUpperCase); -
flatMap()
用于將[["ABC", "DEF"], ["FGH", "IJK"]]
的形式轉(zhuǎn)換為
["ABC", "DEF", "FGH", "IJK"]
:Stream<String> cityStream = Stream.of("Beijing", "Shanghai", "Shenzhen");
// [['B', 'e', 'i', 'j', 'i', 'n', 'g'], ['S', 'h', 'a', 'n', 'g', 'h', 'a', 'i'], ...]
Stream<Stream<Character>> characterStream1 = cityStream.
map(city -> characterStream(city));Stream<String> cityStreamCopy = Stream.of("Beijing", "Shanghai", "Shenzhen");
// ['B', 'e', 'i', 'j', 'i', 'n', 'g', 'S', 'h', 'a', 'n', 'g', 'h', 'a', 'i', ...]
Stream<Character> characterStreamCopy = cityStreamCopy.
flatMap(city -> characterStream(city));
其中,
characterStream()
返回有參數(shù)字符串的字符構(gòu)成的Stream<Character>;
-
limit()
表示限制stream中元素的數(shù)量翁脆,skip()
表示跳過stream中前幾個元素眷蚓,concat
表示將多個stream連接
起來,peek()
主要用于debug時查看stream中元素的值:Stream<Integer> limitStream = Stream.of(18, 20, 12, 35, 89).sorted().limit(3);
Stream<Integer> skipStream = Stream.of(18, 20, 12, 35, 89).sorted(Comparator.reverseOrder())
.skip(1);
Stream<Integer> concatStream = Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5, 6));
concatStream.peek(i -> System.out.println(i)).count();
peek()
是intermediate operation反番,所以后面需要一個terminal operation沙热,如count()
才能在輸出中
看到結(jié)果;
-
有狀態(tài)的(stateful)轉(zhuǎn)換罢缸,即元素之間有依賴關(guān)系篙贸,如
distinct()
返回由唯一元素構(gòu)成的stream,sorted()
返回排
序后的stream:Stream<String> distinctStream = Stream.of("Beijing", "Tianjin", "Beijing").distinct();
Stream<String> sortedStream = Stream.of("Beijing", "Shanghai", "Chengdu")
.sorted(Comparator.comparing(String::length).reversed());
4. Stream reduction
reduction
就是從stream中取出結(jié)果枫疆,是terminal operation
爵川,因此經(jīng)過reduction
后的stream不能再使用了。
4.1 Optional
Optional<T>表示或者有一個T類型的對象养铸,或者沒有值雁芙;
- 創(chuàng)建Optional對象:
直接通過Optional的類方法:of()
/empty()
/ofNullable()
:
Optional<Integer> intOpt = Optional.of(10);
Optional<String> emptyOpt = Optional.empty();
Optional<Double> doubleOpt = Optional.ofNullable(5.5);
- 使用Optional對象:
你當(dāng)然可以這么使用:
if (intOpt.isPresent()) {
intOpt.get();
}
但是,最好這么使用:
doubleOpt.orElse(0.0);
doubleOpt.orElseGet(() -> 1.0);
doubleOpt.orElseThrow(RuntimeException::new);
List<Double> doubleList = new ArrayList<>();
doubleOpt.ifPresent(doubleList::add);
map()
方法與ifPresent()
用法相同钞螟,就是多個返回值兔甘,flatMap()
用于Optional的鏈?zhǔn)奖磉_(dá):
Optional<Boolean> addOk = doubleOpt.map(doubleList::add);
Optional.of(4.0).flatMap(num -> Optional.ofNullable(num * 100))
.flatMap(num -> Optional.ofNullable(Math.sqrt(num)));
4.2 簡單的reduction
主要包含以下操作: findFirst()
/findAny()
/allMatch
/anyMatch()
/noneMatch
,比如:
Optional<String> firstWord = wordStream.filter(s -> s.startsWith("Y")).findFirst();
Optional<String> anyWord = wordStream.filter(s -> s.length() > 3).findAny();
wordStream.allMatch(s -> s.length() > 3);
wordStream.anyMatch(s -> s.length() > 3);
wordStream.noneMatch(s -> s.length() > 3);
4.3 reduce方法
-
reduce(accumulator)
:參數(shù)是一個執(zhí)行雙目運算的Functional Interface
鳞滨,假如這個參數(shù)表示的操作為op洞焙,
stream中的元素為x, y, z, ...,則reduce()
執(zhí)行的就是x op y op z ...
拯啦,所以要求op這個操作具有結(jié)合性
(associative)澡匪,即滿足:(x op y) op z = x op (y op z)
,滿足這個要求的操作主要有:求和褒链、求積唁情、求最大值、
求最小值甫匹、字符串連接甸鸟、集合并集和交集等。另外兵迅,該函數(shù)的返回值是Optional的:Optional<Integer> sum1 = numStream.reduce((x, y) -> x + y);
-
reduce(identity, accumulator)
:可以認(rèn)為第一個參數(shù)為默認(rèn)值抢韭,但需要滿足identity op x = x
,所以對于求
和操作恍箭,identity
的值為0刻恭,對于求積操作,identity
的值為1扯夭。返回值類型是stream元素的類型:Integer sum2 = numStream.reduce(0, Integer::sum);
5. collect結(jié)果
-
collect()
方法:
reduce()
和collect()
的區(qū)別是:
-
reduce()
的結(jié)果是一個值鳍贾; -
collect()
可以對stream中的元素進(jìn)行各種處理后,得到stream中元素的值交洗;
Collectors
接口提供了很方便的創(chuàng)建Collector
對象的工廠方法:
// collect to Collection
Stream.of("You", "may", "assume").collect(Collectors.toList());
Stream.of("You", "may", "assume").collect(Collectors.toSet());
Stream.of("You", "may", "assume").collect(Collectors.toCollection(TreeSet::new));
// join element
Stream.of("You", "may", "assume").collect(Collectors.joining());
Stream.of("You", "may", "assume").collect(Collectors.joining(", "));
// summarize element
IntSummaryStatistics summary = Stream.of("You", "may", "assume")
.collect(Collectors.summarizingInt(String::length));
summary.getMax();
-
foreach()
方法:
foreach()
用于遍歷stream中的元素贾漏,屬于terminal operation
;
forEachOrdered()
是按照stream中元素的順序遍歷藕筋,也就無法利用并發(fā)的優(yōu)勢纵散;
Stream.of("You", "may", "assume", "you", "can", "fly").parallel()
.forEach(w -> System.out.println(w));
Stream.of("You", "may", "assume", "you", "can", "fly")
.forEachOrdered(w -> System.out.println(w));
-
toArray()
方法:
得到由stream中的元素得到的數(shù)組,默認(rèn)是Object[]隐圾,可以通過參數(shù)設(shè)置需要結(jié)果的類型:
Object[] words1 = Stream.of("You", "may", "assume").toArray();
String[] words2 = Stream.of("You", "may", "assume").toArray(String[]::new);
-
toMap()
方法:
toMap
: 將stream中的元素映射為<key, value>的形式伍掀,兩個參數(shù)分別用于生成對應(yīng)的key和value的值。比如有一個字符串
stream暇藏,將首字母作為key蜜笤,字符串值作為value,得到一個map:
Stream<String> introStream = Stream.
of("Get started with UICollectionView and the photo library".split(" "));
Map<String, String> introMap =
introStream.collect(Collectors.toMap(s -> s.substring(0, 1), s -> s));
如果一個key對應(yīng)多個value盐碱,則會拋出異常把兔,需要使用第三個參數(shù)設(shè)置如何處理沖突沪伙,比如僅使用原來的value、使用新的
value县好,或者合并:
Stream<String> introStream = Stream.of("Get started with UICollectionView and the photo library"
.split(" "));
Map<Integer, String> introMap2 = introStream.collect(Collectors.toMap(s -> s.length(),
s -> s, (existingValue, newValue) -> existingValue));
如果value是一個集合围橡,即將key對應(yīng)的所有value放到一個集合中,則需要使用第三個參數(shù)缕贡,將多個value合并:
Stream<String> introStream3 = Stream.of("Get started with UICollectionView and the photo library"
.split(" "));
Map<Integer, Set<String>> introMap3 = introStream3.collect(Collectors.toMap(s -> s.length(),
s -> Collections.singleton(s), (existingValue, newValue) -> {
HashSet<String> set = new HashSet<>(existingValue);
set.addAll(newValue);
return set;
}
));
introMap3.forEach((k, v) -> System.out.println(k + ": " + v));
如果value是對象自身翁授,則使用Function.identity()
,如:
Map<Integer, Person> idToPerson = people.collect(Collectors
.toMap(Person::getId, Function.identity()));
toMap()
默認(rèn)返回的是HashMap晾咪,如果需要其它類型的map收擦,比如TreeMap,則可以在第四個參數(shù)指定構(gòu)造方法:
Map<Integer, String> introMap2 = introStream.collect(
Collectors.toMap(s -> s.length(), s -> s, (existingValue, newValue)
-> existingValue, TreeMap::new));
6. Grouping和Partitioning
-
groupingBy()
表示根據(jù)某一個字段或條件進(jìn)行分組谍倦,返回一個Map塞赂,其中key為分組的字段或條件,value默認(rèn)為
list昼蛀,groupingByConcurrent()
是其并發(fā)版本:Map<String, List<Locale>> countryToLocaleList = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.groupingBy(l -> l.getDisplayCountry())); -
如果
groupingBy()
分組的依據(jù)是一個bool條件减途,則key的值為true/false,此時與partitioningBy()
等價曹洽,且
partitioningBy()
的效率更高:// predicate
Map<Boolean, List<Locale>> englishAndOtherLocales = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.groupingBy(l -> l.getDisplayLanguage().equalsIgnoreCase("English")));// partitioningBy
Map<Boolean, List<Locale>> englishAndOtherLocales2 = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.partitioningBy(l -> l.getDisplayLanguage().equalsIgnoreCase("English"))); groupingBy()
提供第二個參數(shù)鳍置,表示downstream
,即對分組后的value作進(jìn)一步的處理:
返回set送淆,而不是list:
Map<String, Set<Locale>> countryToLocaleSet = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.groupingBy(l -> l.getDisplayCountry(), Collectors.toSet()));
返回value集合中元素的數(shù)量:
Map<String, Long> countryToLocaleCounts = Stream.of(Locale.getAvailableLocales())
.collect(Collectors.groupingBy(l -> l.getDisplayCountry(), Collectors.counting()));
對value集合中的元素求和:
Map<String, Integer> cityToPopulationSum = Stream.of(cities)
.collect(Collectors.groupingBy(City::getName, Collectors.summingInt(City::getPopulation)));
對value的某一個字段求最大值税产,注意value是Optional的:
Map<String, Optional<City>> cityToPopulationMax = Stream.of(cities)
.collect(Collectors.groupingBy(City::getName,
Collectors.maxBy(Comparator.comparing(City::getPopulation))));
使用mapping對value的字段進(jìn)行map處理:
Map<String, Optional<String>> stateToNameMax = Stream.of(cities)
.collect(Collectors.groupingBy(City::getState, Collectors.mapping(City::getName,
Collectors.maxBy(Comparator.comparing(String::length)))));
Map<String, Set<String>> stateToNameSet = Stream.of(cities)
.collect(Collectors.groupingBy(City::getState,
Collectors.mapping(City::getName, Collectors.toSet())));
通過summarizingXXX
獲取統(tǒng)計結(jié)果:
Map<String, IntSummaryStatistics> stateToPopulationSummary = Stream.of(cities)
.collect(Collectors.groupingBy(City::getState, Collectors.summarizingInt(City::getPopulation)));
reducing()
可以對結(jié)果作更復(fù)雜的處理,但是reducing()
卻并不常用:
Map<String, String> stateToNameJoining = Stream.of(cities)
.collect(Collectors.groupingBy(City::getState, Collectors.reducing("", City::getName,
(s, t) -> s.length() == 0 ? t : s + ", " + t)));
比如上例可以通過mapping達(dá)到同樣的效果:
Map<String, String> stateToNameJoining2 = Stream.of(cities)
.collect(Collectors.groupingBy(City::getState,
Collectors.mapping(City::getName, Collectors.joining(", ")
)));
7. Primitive Stream
Stream<Integer>
對應(yīng)的Primitive Stream就是IntStream
偷崩,類似的還有DoubleStream
和LongStream
辟拷。
-
Primitive Stream的構(gòu)造:
of()
,range()
,rangeClosed()
,Arrays.stream()
:IntStream intStream = IntStream.of(10, 20, 30);
IntStream zeroToNintyNine = IntStream.range(0, 100);
IntStream zeroToHundred = IntStream.rangeClosed(0, 100);
double[] nums = {10.0, 20.0, 30.0};
DoubleStream doubleStream = Arrays.stream(nums, 0, 3); -
Object Stream與Primitive Stream之間的相互轉(zhuǎn)換,通過
mapToXXX()
和boxed()
:// map to
Stream<String> cityStream = Stream.of("Beijing", "Tianjin", "Chengdu");
IntStream lengthStream = cityStream.mapToInt(String::length);// box
Stream<Integer> oneToNine = IntStream.range(0, 10).boxed(); 與Object Stream相比阐斜,Primitive Stream的特點:
toArray()
方法返回的是對應(yīng)的Primitive類型:
int[] intArr = intStream.toArray();
自帶統(tǒng)計類型的方法衫冻,如:max()
, average()
, summaryStatistics()
:
OptionalInt maxNum = intStream.max();
IntSummaryStatistics intSummary = intStream.summaryStatistics();
8. Parallel Stream
- Stream支持并發(fā)操作,但需要滿足以下幾點:
構(gòu)造一個paralle stream谒出,默認(rèn)構(gòu)造的stream是順序執(zhí)行的隅俘,調(diào)用paralle()
構(gòu)造并行的stream:
IntStream scoreStream = IntStream.rangeClosed(10, 30).parallel();
要執(zhí)行的操作必須是可并行執(zhí)行的,即并行執(zhí)行的結(jié)果和順序執(zhí)行的結(jié)果是一致的笤喳,而且必須保證stream中執(zhí)行的操作是線程安
全的:
int[] wordLength = new int[12];
Stream.of("It", "is", "your", "responsibility").parallel().forEach(s -> {
if (s.length() < 12) wordLength[s.length()]++;
});
這段程序的問題在于为居,多線程訪問共享數(shù)組wordLength
,是非線程安全的杀狡。解決的思路有:1)構(gòu)造AtomicInteger數(shù)組蒙畴;
2)使用groupingBy()
根據(jù)length統(tǒng)計;
- 可以通過并行提高效率的常見場景:
使stream無序:對于distinct()
和limit()
等方法呜象,如果不關(guān)心順序膳凝,則可以使用并行:
LongStream.rangeClosed(5, 10).unordered().parallel().limit(3);
IntStream.of(14, 15, 15, 14, 12, 81).unordered().parallel().distinct();
在groupingBy()
的操作中碑隆,map的合并操作是比較重的,可以通過groupingByConcurrent()
來并行處理蹬音,不過前提是
parallel stream:
Stream.of(cities).parallel().collect(Collectors.groupingByConcurrent(City::getState));
在執(zhí)行stream操作時不能修改stream對應(yīng)的collection上煤;
stream本身是不存儲數(shù)據(jù)的,數(shù)據(jù)保存在對應(yīng)的collection中祟绊,所以在執(zhí)行stream操作的同時修改對應(yīng)的collection,結(jié)果
是未定義的:
// ok
Stream<String> wordStream = wordList.stream();
wordList.add("number");
wordStream.distinct().count();
// ConcurrentModificationException
Stream<String> wordStream = wordList.stream();
wordStream.forEach(s -> { if (s.length() >= 6) wordList.remove(s);});
9. Functional Interface
僅包含一個抽象方法的interface被成為Functional Interface
哥捕,比如:Predicate
, Function
, Consumer
等牧抽。
此時我們一般傳入一個lambda表達(dá)式或Method Reference
。
常見的Functional Interface
有:
Functional Interface Parameter Return Type Description Types
Supplier<T> None T Supplies a value of type T
Consumer<T> T void Consumes a value of type T
BiConsumer<T, U> T,U void Consumes values of types T and U
Predicate<T> T boolean A Boolean-valued function
ToIntFunction<T> T int An int-, long-, or double-valued function
ToLongFunction<T> T long
ToDoubleFunction<T> T double
IntFunction<R> int R A function with argument of type int, long, or double
LongFunction<R> long
DoubleFunction<R> double
Function<T, R> T R A function with argument of type T
BiFunction<T, U, R> T,U R A function with arguments of types T and U
UnaryOperator<T> T T A unary operator on the type T
BinaryOperator<T> T,T T A binary operator on the type T
本文來自我的博客遥赚,轉(zhuǎn)載請注明出處扬舒,謝謝!