给大忙人看的Java核心技术笔记(8、Stream)
流提供了數(shù)據(jù)視圖,讓你可以在比集合更高的概念層上指定操作。使用流只要指定做什么,而不是怎么做。將操作的調(diào)度執(zhí)行留給實(shí)現(xiàn)。
要點(diǎn):
1、迭代器使用了一種明確的遍歷策略,同時(shí)也阻止了高效的并發(fā)執(zhí)行
2、你可以從集合、數(shù)組、生成器或迭代器創(chuàng)建流
3、使用過(guò)濾器filter來(lái)選擇元素,使用map進(jìn)行元素轉(zhuǎn)換
4、對(duì)于轉(zhuǎn)換流的其他操作還包括limit、distinct、sorted
5、要從Stream中獲得結(jié)果,請(qǐng)使用規(guī)約操作(reduction operation),如count、max、min、findFirst、findAny。這些方法中的一些會(huì)返回Optional類型值。
6、Optional類型是作為處理null值而提供的一個(gè)安全替代者。想要安全地使用它,需要借助于ifPersent和orElse方法。
7、你可以收集集合、數(shù)組、字符串或者map中的Stream結(jié)果。
8、Collections類的groupingBy方法和partitioningBy方法允許你將流的內(nèi)容劃分成組,然后獲得每個(gè)組的結(jié)果。
9、針對(duì)基本數(shù)據(jù)類型如int、long、double,java提供了專門的流
10、并行流自動(dòng)將流操作并行化
1、從迭代到stream
1 //統(tǒng)計(jì)一本書(shū)中所有長(zhǎng)單詞 2 String contents = new String(Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8);//將文件讀入字符串 3 List<String> words = Arrays.asList(contents.split("\PL+"));//拆分成單詞 4 int count = 0; 5 for(String w : words){ 6 if(w.length()>12) count++; 7 } 8 9 long count = words.stream().filter(w->w.length()>12).count();用words.parallelStream()就會(huì)使得流類庫(kù)并行地進(jìn)行過(guò)濾和計(jì)數(shù)操作。
Stream遵循“做什么,而不是怎么去做”的原則。不要指定那個(gè)線程,怎么完成,執(zhí)行順序和執(zhí)行線程都會(huì)自動(dòng)由Stream實(shí)現(xiàn),相比自己定義實(shí)現(xiàn)則會(huì)放棄了優(yōu)化的機(jī)會(huì)
流表面上與集合相似,允許轉(zhuǎn)換和檢索數(shù)據(jù)。然而兩者卻有明顯不同:
1、流不存儲(chǔ)元素,他們存儲(chǔ)在底層的集合或者按需生成。
2、流操作不改變他們的數(shù)據(jù)源。例如,filter方法不會(huì)從一個(gè)新流中刪除元素,而是生成一個(gè)不包含特定元素的新流。
3、如果可能的話,Stream操作可能是延遲執(zhí)行的。這以為著直到需要結(jié)果的時(shí)候,方法才會(huì)執(zhí)行。
流的典型工作流程
1、創(chuàng)建一個(gè)stream
2、強(qiáng)初始流轉(zhuǎn)換成其他流的中間操作,可能需要多步操作(filter方法返回另一個(gè)流)
3、應(yīng)用終止操作產(chǎn)生結(jié)果。該操作強(qiáng)迫懶惰操作進(jìn)行執(zhí)行。在這之后流就不會(huì)再應(yīng)用到了(count方法將流歸納為一個(gè)結(jié)果)
2、創(chuàng)建Stream
用Collection接口的Stream方法將任何集合轉(zhuǎn)化成Stream。用Stream.of方法將一個(gè)數(shù)組轉(zhuǎn)化為Stream。
1 Stream<String> words = Stream.of(contents.split("\\PL+"));//splits方法返回String[]用Arrays.stream(array, from, to)方法將數(shù)組的一部分轉(zhuǎn)換成Stream。用Stream.empty方法創(chuàng)建一個(gè)空的流。
創(chuàng)建無(wú)限Stream的靜態(tài)方法。
generate方法接受一個(gè)無(wú)參的函數(shù)(Supplier<T>接口的對(duì)象)
1 //創(chuàng)建一個(gè)常量值的Stream 2 Stream<String> echos = Stream.generate(()->"echo"); 3 //創(chuàng)建一個(gè)含有隨機(jī)數(shù)的Stream: 4 Stream<String> randoms = Stream.generate(Math::random);interate方法,接受一個(gè)種子值和一個(gè)函數(shù)(UnaryOperator<T>接口的對(duì)象)并會(huì)對(duì)之前的值重復(fù)應(yīng)用該函數(shù)
1 Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n-> n.add(BigInteger.ONE)); 2 //Pattern類有一個(gè)方法可以按照正則表達(dá)式對(duì)字符串進(jìn)行分隔 3 Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents); 4 //靜態(tài)方法Files.lines返回了一個(gè)包含文件中所有行的結(jié)果 5 try(Stream<String> lines = Files.lines(path)){ 6 //對(duì)lines進(jìn)行處理 7 }3、filter、map、flatMap方法
filter轉(zhuǎn)換生成一個(gè)匹配一定條件的新流
//將字符串流轉(zhuǎn)換成另一個(gè)只包含長(zhǎng)單詞的流 List<String> words = ...; Stream<String> longWords = word.stream().filter(w->w.length()>12);filter的參數(shù)是一個(gè)Predicate<T>對(duì)象(從T到boolean的函數(shù))
我們經(jīng)常需要將一個(gè)流中的值進(jìn)行某種形式的轉(zhuǎn)換,這是用map方法,并且傳遞給它一個(gè)執(zhí)行轉(zhuǎn)換的函數(shù)
//將單詞轉(zhuǎn)換成小寫(xiě) Stream<Stream> lowercaseWords = words.stream().map(String::toLowerCase); //產(chǎn)生每個(gè)單詞第一個(gè)字母的流 Stream<String> firstLetters = words.stream().map(s -> s.substring(0,1)); public static Stream<String> letters(String s){List<String> result = new ArrayList<>();for(int i=0;i<s.length();i++){result.add(s.substring(i,i+1)); } return result.stream(); } //等同于 Stream<Stream<String>> result = words.stream().map(w->letters(w)) //將返回一個(gè)包含多個(gè)流的流,如果要展開(kāi)為一個(gè)只包含字符串的流則可以使用flatMap方法而不是map方法 Stream<String> flatResult = words.stream().flatMap(w->letters(w)); //在每個(gè)單詞上調(diào)用letters方法,并展開(kāi)結(jié)果4、提取子流和組合流
stream.limit(n)會(huì)返回一個(gè)包含n個(gè)元素的新流(如果原始流的長(zhǎng)度小于n,則會(huì)返回原始流)
1 Stream<Double> randoms = Stream.generate(Math::random).limit(100);stream.skip(n)正好相反,它會(huì)丟棄前n個(gè)元素。
Stream<String> words = Stream.of(contents.split("\\PL+")).skip(1); //可以使用Stream類中的靜態(tài)方法concat將兩個(gè)流連接起來(lái) Stream<String> combined = Stream.concat(letters("Hello"), letters("World")); //生成流["H","e","l"......] //當(dāng)然第一個(gè)流不是無(wú)限的,否則第二個(gè)流永遠(yuǎn)沒(méi)有機(jī)會(huì)添加到第一個(gè)流后面5、其他流轉(zhuǎn)換
distinct: 對(duì)于Stream中包含的元素進(jìn)行去重操作(去重邏輯依賴元素的equals方法),新生成的Stream中沒(méi)有重復(fù)的元素;
對(duì)于Stream排序來(lái)說(shuō),java 9 提供了多個(gè)sorted方法。其中一個(gè)實(shí)現(xiàn)了Comparable接口的流,一個(gè)接受一個(gè)Comparator對(duì)象
1 //最長(zhǎng)的單詞會(huì)出現(xiàn)在第一個(gè)位置 2 Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());peek: 生成一個(gè)包含原Stream的所有元素的新Stream,同時(shí)會(huì)提供一個(gè)消費(fèi)函數(shù)(Consumer實(shí)例),新Stream每個(gè)元素被消費(fèi)的時(shí)候都會(huì)執(zhí)行給定的消費(fèi)函數(shù);
Object[] powers = Stream.iterate(1.0, p->p*2).peek(e->System.out.println("Fetching" +e)).limit(20).toArray(); //每當(dāng)檢索到一個(gè)元素就會(huì)調(diào)用peek里面的方法,一般用于調(diào)試,可以在peek調(diào)用的方法中設(shè)置斷點(diǎn)。
6、簡(jiǎn)單歸約
從流中或的返回值,稱為歸納方法(reduction)。規(guī)約是終止操作,可以將流歸納為一個(gè)可以在程序中使用的非流值。
count:返回流中元素的數(shù)目
max,min:返回流中的最大值或最小值。
這些方法會(huì)返回一個(gè)Optional<T>類型的值,他可能是一個(gè)封裝類型的值,或這是沒(méi)有返回值。Optional類型是一種更好的表明缺少返回值的方式。
1 Optional<String> largest = words.max(String::compareToIgnoreCase); 2 System.out.println("largest" +largest.getOrElse(""));findFirst方法返回非空集合中的第一個(gè)值,通常與filter方法結(jié)合起來(lái)使用。
1 Optional<String> startsWithQ =words.filter(s -> s.startsWith("Q")).findFirst();如果想找到任何一個(gè)匹配的元素,而不必是第一個(gè),則使用findAny方法,該方法在對(duì)流進(jìn)行并行執(zhí)行時(shí)非常有效
如果只想知道流中是否含有匹配元素,則使用anyMatch方法。這個(gè)方法接受一個(gè)predicate參數(shù),所以不需要使用filter方法
boolean aWordStartsWithQ = words.parallel().anyMatch(s->s.startsWith("Q"));如果所有元素都跟predicate匹配的話,allMatch方法將返回true,如果沒(méi)有元素匹配的話,nonematch方法返回true。
兩個(gè)方法都可以通過(guò)并行執(zhí)行提高速度。
7、Optional類型
Optional<T>對(duì)象是一個(gè)T類型對(duì)象或者空對(duì)象的封裝。Optional<T>類型是要么指向?qū)ο笠礊閚ull的T類型引用的安全替代者。
1 //封裝的字符串,如果沒(méi)有的話則為空字符串“” 2 String result = optionalString.orElse(""); 3 //也可以調(diào)用代碼來(lái)計(jì)算默認(rèn)值,函數(shù)只有在需要時(shí)才會(huì)被調(diào)用 4 String result = optionalString.orElseGet(()->System.getProperty("user.dir")); 5 //提供了一個(gè)可以產(chǎn)生異常對(duì)象的方法 6 String result = optionalString.orElseThrow(IllegalStateException::new);ifPresent方法接受一個(gè)函數(shù),如果Optional值存在的話,他會(huì)被傳遞給函數(shù);否則的話,不進(jìn)行任何處理。
optionalValue.ifPresent(v->Process v);如果你希望當(dāng)值存在時(shí)將其添加到一個(gè)集合中,可以調(diào)用:
optionalValue.ifPresent(v->results.add(v)); //或者簡(jiǎn)單點(diǎn) optionalValue.ifPresent(results::add);當(dāng)調(diào)用ifPresent方法時(shí),函數(shù)不會(huì)返回任何值。如果想對(duì)函數(shù)結(jié)果進(jìn)行處理,則使用map方法:
Optional<Boolean> added = optionalValue.map(results::add)
add方法的值有3種可能性:封裝到Optional中的true或者false;如果optionalValue存在,或者是一個(gè)空的Optional值。
創(chuàng)建Optional類型值有兩個(gè)靜態(tài)方法:Optional.of(result)和Optional.empty()
1 public static Optional<Double> inverse(Double x){ 2 return x ==0 ? Optional.empty() : Optional.of(1/x); 3 }ofNullable方法被設(shè)計(jì)為null值和可選值之間的一座橋梁,obj不為null,Optional.ofNullable(obj),則會(huì)返回Optional.of(obj);否則返回Optional.empty()
Stream的flatMap方法,通過(guò)展開(kāi)方法所返回的流,將兩個(gè)方法組合起來(lái)。
假設(shè)f返回T,g返回U。則可調(diào)用s.f().g()將兩個(gè)方法組合起來(lái),返回T
如果f返回Optional<T>,g返回Optional<U>,就不能調(diào)用s.f().g()
可以調(diào)用 Optional<U> result = s.f().flatMap(T::g);
如計(jì)算平方根:
public static Optional<Double> squareRoot(Double x){return x<0? Optional.empty() : Optional.of(Math.sqrt(x)); } //這樣可以計(jì)算反轉(zhuǎn)值得平方根 Optional<Double> result = inverse(x).flatMap(MyMath::squareRoot); //或者 Optional<Double> result = Optional.of(-4.0).flatMap(Demo::inverse).flatMap(Demo::squareRoot);8、收集結(jié)果
調(diào)用iterate方法生成一個(gè)能夠訪問(wèn)元素的傳統(tǒng)迭代器。
調(diào)用forEach方法作用于沒(méi)一個(gè)元素,在并行流上forEach方法可以以任意順序便利元素,但是想按順序處理需要用forEachOrdered方法。
stream.forEach(System.out::println);調(diào)用roArray獲得一個(gè)含有流中所有元素的數(shù)組。
1 String[] result = stream.toArray(String[]::new);調(diào)用collect方法,傳入Collectors類將流放到目標(biāo)容器中
List<String> result = steam.collect(Collectors.toList()); Set<String> result = stream.collect(Collectors.toSet()); TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new)); //字符串拼接起來(lái) String result = stream.collect(Collectors.joining()); //元素間插入分隔符 String result = stream.collect(Collectors.joining(",")); //如果流包含字符串以外對(duì)象,首先將他們轉(zhuǎn)換成字符串 String result = stream.map(Object::toString).collect(Collectors.joining(","));如果想將流規(guī)約為總和、平均值、最大值、最小值,則用summarizing方法
1 //用summarzing(Int|Long|Double)方法返回(Int|Long|Double)SummaryStatistics類型結(jié)果 2 IntSummaryStatistics summary = stream.collect(Collectors.summaringInt(String::length)); 3 double averageWordLength = summary.getAverage(); 4 double maxWordLength = summary.getMax();9、將結(jié)果收集到Map中
Map<Integer, String> idToName = people.collect(Collectors.toMap(Person::getId, Person::getName)); //如果設(shè)置Map的值為實(shí)際people中的對(duì)象 Map<Integer, String> idToName = people.collect(Collectors.toMap(Person::getId, Function.identity())); //如果多元素?fù)碛邢嗤逆I,會(huì)出異常,則提供第三個(gè)參數(shù),更具已有的值和新值,來(lái)決定鍵的值。解決鍵沖突問(wèn)題,可以看P270,上網(wǎng)搜索下Collectors.toMap方法。
使用toConcurrentMap方法可以生成一個(gè)并發(fā)的map。
10、分組和分片
使用groupingBy方法,對(duì)Map進(jìn)行分組
1 Map<String, List<Local>> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry)); 2 //Locale::getCountry是分組的分類函數(shù) 3 List<Locale> swissLocales = countryToLocales.get("CH");※每個(gè)語(yǔ)言環(huán)境都有一個(gè)語(yǔ)言代碼(en)和地區(qū)碼(US)。en_us代表美國(guó)英語(yǔ),en_zh代表中國(guó)英語(yǔ)
當(dāng)分類函數(shù)是一個(gè)predicate(斷言)函數(shù)時(shí)(返回一個(gè)布爾值的函數(shù)),流元素被分成兩組列表,一組返回true的元素,另一組是返回false的元素。這時(shí)用partitioningBy比使用groupingBy更有效率。
//將所有語(yǔ)言環(huán)境分為英語(yǔ)和使用其他語(yǔ)言 Map<Boolean, List<Locale>> englishAndOtherLocals = locals.collect(Collections.partitioningBy(l->l.getLanguage().equals("en"))); List<Locale> englishLocales = englishAndOtherLocales.get(true);調(diào)用groupingByConcurrent方法,將會(huì)得到一個(gè)并發(fā)映射。當(dāng)與并行流一起使用時(shí),可以并發(fā)地插入值。
11、下游收集器
groupingBy方法產(chǎn)生一個(gè)值為列表的map對(duì)象。如果想以某種方式處理這些列表,則提供一個(gè)下游(downstream)收集器。
如:想讓map中的值是set類型而不是list類型,則可以使用Collectors.toSet方法:
Map<String, Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry, toSet()));//Java 8還提供了以下幾個(gè)收集器用來(lái)將分組元素“歸約”成數(shù)字//counting會(huì)返回所收集元素的總個(gè)數(shù)。 Map<String, Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry, counting())); //summing(Int|long|Double)接受一個(gè)函數(shù)作為參數(shù),然后將函數(shù)應(yīng)用到downstream元素上,并生成他們的求和。 Map<String, Integer> stateToCityPopulation = cities.collect(groupingBy(City::getState, summingInt(City::getPopulation))); //計(jì)算每個(gè)州下屬所有城市的人口數(shù)//maxBy、minBy接受一個(gè)比較器,產(chǎn)生downstream元素中的最大,最小值 Map<String, City> stateToLargestCity = cities.collect(groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation)))); //產(chǎn)生每個(gè)州人口最多的城市//mapping將函數(shù)應(yīng)用到downstream結(jié)果上,但是它需要另一個(gè)收集器處理其結(jié)果。 Map<String, Optional<String>> stateToLongestCityName = cities.collect(groupingBy(City::getState, mapping(City::getName,maxBy(Comparator.comparing(String::length))))); //這里講城市按所屬州進(jìn)行分組,每個(gè)州內(nèi),我們生成每個(gè)城市的名稱并按照其最大長(zhǎng)度進(jìn)行歸約Mapping方法還未上一節(jié)中,獲取一個(gè)國(guó)家所有語(yǔ)言集合的問(wèn)題提供了一個(gè)更好的解決方案
1 Map<String, Set<String>> countryToLanguages = locales.collect(groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, toSet())));上一節(jié)使用的是toMap而不是groupingBy。在這種形式中,不必?fù)?dān)心單獨(dú)集合的合并問(wèn)題。如果grouping活著mapping函數(shù)的返回類型為int、long、double則你可以將元素收集到一個(gè)summary statistics對(duì)象中
Map<String, IntSummaryStatistics>stateToCityPopulationSummary = cities.collect(groupingBy(City::getState, summarizingImt(Vity::getPopulation))); //就可以從summary statistic對(duì)象中獲取函數(shù)值得總和,總數(shù)、平均、最大小值※組合收集器功能強(qiáng)大,但是會(huì)導(dǎo)致非常復(fù)雜的表達(dá)式。應(yīng)該只在通過(guò)groupingBy或者partitioningBy來(lái)處理“downstream”map值時(shí),才使用它們。其他情況下,只需要對(duì)流直接應(yīng)用map、reduce、count、max、min方法即可
12、歸約操作
reduce方法是用來(lái)計(jì)算流中某個(gè)值的一種通用機(jī)制,最簡(jiǎn)單的形式是使用一個(gè)二元函數(shù),從前兩個(gè)元素開(kāi)始,不斷作用到流中的其他元素上。
1 //求和 2 List<Integer> values = ...; 3 Optional<Integer> sum = values.stream().reduce((x,y)->x+y); 4 //可以用reduce(Integer::sum)代替reduce方法含有一個(gè)歸約操作P,那么歸約操作將生成v0 P v1 P V2.....,其中,vi P vi+1 表示函數(shù)調(diào)用 p(vi,vi+1)。操作滿足結(jié)合率,即與你組合元素的順序無(wú)關(guān)。
有許多結(jié)合操作:sum求和、product乘積、string concatenation字符串拼接、maximum最大值、minimum最小值、set union集合并集、intersection交集。
減法就不是結(jié)合操作
通常存在標(biāo)識(shí)e是的 x P (y P z),可以使用該元素作為計(jì)算的起點(diǎn)。例如對(duì)加法來(lái)說(shuō)起點(diǎn)就是0(標(biāo)識(shí))。
1 //帶標(biāo)識(shí)的reduce 2 List<Integer> values = ...; 3 Integer sum = values.stream().reduce(0 , (x,y)->x+y); 4 //如果流為空則返回標(biāo)識(shí)值0如果有一個(gè)對(duì)象流,想得到這些對(duì)象上某個(gè)屬性的和,如求一個(gè)流中所有字符串的總長(zhǎng)度,就不能用reduce方法的簡(jiǎn)單形式((T,T)->T)應(yīng)為參數(shù)和返回值類型是一樣的,需要提供一個(gè)累加器函數(shù)(total, word) -> total +word.length()該函數(shù)會(huì)被重復(fù)調(diào)用形成累加值。但是當(dāng)開(kāi)始并行計(jì)算時(shí),會(huì)出現(xiàn)多個(gè)累加值,需要將他們累加起來(lái)。
1 int result = words.reduce(0, (total, word) -> total + word.length(),(total1, total2) ->total1+total2);在實(shí)際中,不會(huì)大量地使用聚合方法。簡(jiǎn)單的方法是映射到一個(gè)數(shù)字流,使用它的方法進(jìn)行求和、求最大值、最小值。在上述例子中可以調(diào)用words.mapToInt(String::length).sum()。這種方式簡(jiǎn)單高效,不涉及自動(dòng)裝箱。
有時(shí)候reduce方法還不夠通用。假如想要將結(jié)果收集到一個(gè)BitSet(位組)中。如果收集操作是并行的,那么不能直接將元素放進(jìn)單個(gè)BitSet中,因?yàn)锽itSet對(duì)象不是線程安全的。所以不能使用reduce方法。每部分需要從自己的空集合開(kāi)始,而reduce僅允許提供一個(gè)標(biāo)識(shí)值,作為代替,可以使用collect方法,他接受三個(gè)參數(shù)
1、一個(gè)提供者,創(chuàng)建目標(biāo)類型的實(shí)例方法,如HashSet的構(gòu)造函數(shù)
2、一個(gè)累加器,將元素添加到目標(biāo)的方法,如add方法
3、一個(gè)合并器,將兩個(gè)對(duì)象合并成一個(gè)的方法,如addAll方法。
BitSet result = stream.collect(BitSet::new, BitSet::set, BitSet::or);
13、
?
轉(zhuǎn)載于:https://www.cnblogs.com/Greekjone/p/5649063.html
總結(jié)
以上是生活随笔為你收集整理的给大忙人看的Java核心技术笔记(8、Stream)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 让powershell同时只能运行一个脚
- 下一篇: Binary Tree Level Or