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