java8--stream


java8-stream

Stream API

Stream 是java8中處理集合的關鍵抽象概念,他可以制定你希望對集合進行的操作,但是將執行操作的時間交給具體實現來決定。

要點

  • 可以從集合、數組、生成器或者迭代器中創建Stream。
  • 使用filter來選擇元素,使用map來改變元素。
  • 其他改變Stream的操作包括limit、distinct、sorted。
  • 要從Stream中獲得結果,請使用reduction操作符,例如count、max、min、findFirst或findAny、其中一些方法會返回一個Optional值。
  • Optional類型的目的是為了安全的替代使用null值。要想安全的使用,需要借助ifPresent和orElse方法。
  • 可以收集集合、數組、字符串或map中的Stream結果。
  • Collectors類的groupingBy和partitioningBy允許對Stream中的內容分組,並獲取每個組的結果。
  • java8對原始類型int、long和double提供了專門的Stream。
  • 當使用並行Stream時,請確保不帶有副作用,並考慮放棄排序約束。

Stream Example:

    @Test//*不使用Stream的方法*
    public void testCount(){
        List<String> words = getWords();
        int count = 0;
        for (String w: words) {
            if(w.length()>10)count++;
        }
        System.out.println(count);
    }
    @Test//*使用Stream*
    public void testStream(){
        List<String> words = getWords();
        long count=0;
        count =words.stream().filter(w->w.length()>10).count();
        System.out.println(count);
    }
    public List<String> getWords(){
        List<String> words = new ArrayList<String>();
        for (int i = 0; i < 20; i++) {
            words.add("aaabbbccc"+i);
        }
        return words;
    }


可以看出來Stream給我們帶來了很大的方便性和可讀性
當使用Stream時,通過三個階段來建立一個操作流水線
1. 創建一個Stream
2. 在一個或多個步驟中,制定將初始Stream轉換為另一個Stream的中間操作。
3. 使用一個終止操作來產生一個結果。該操作會強制他之前的延遲操作立即執行。在這之后,該Stream就不會在被使用了。

創建Stream

java8在Collection接口中添加了stream方法,可以將任何一個集合轉化為一個Stream。

    Stream<String> words = Stream.of(contents.split(","));
    //split方法返回一個String[]數組
    //of方法接受可變長度的參數,因此可以構造一個含有任何個參數的Stream
    Stream<String> words = Stream.of("hello","world","java8","lambda");


使用Arrays.stream(array,from,to)方法將數組的一部分轉化為Stream
如果要創建一個不含任何元素的Stream,可以使用靜態的Stream.empty方法:

Stream<String> words = Stream.empty();


Stream接口有兩個用來創建無限Stream的靜態方法。generate方法接受一個無參函數,

    @Test
    public void testInit(){
        Stream<String> echos = Stream.generate(()->"Echo");
        Stream<Double> randoms= Stream.generate(Math::random);
    }


Java8中添加了許多能夠產生Stream的方法。比如 Pattern類添加了一個splitAsStream方法。

Stream<String> words = Pattern.complie(",").splitAsStream(contents);

filter、map和flatMap方法

  • filter方法的參數是一個Predicate對象–即一個從T到boolean的函數。
    Stream<String> words = getWords().stream().filter(w->w.length()>10);
    
  • 當對一個流中的值進行某種轉換時,使用map方法。
    //將單詞轉換為大寫
    Stream<String> words = getWords().stream().map(String::toUpperCase);
    //獲得單詞的第一個字母
    Stream<Character> wordsFirstChar = getWords().stream().map(s->s.charAt(0));
    
  • flatMap
        @Test
        public void testMapAndFlatMap(){
            Stream<Stream<Character>> chars = getWords().stream().map(w->getCharacter(w));
            //這里可以用flatMap
            //flatMap 會對每個對象調用getCharacter()方法,並展開結果
            Stream<Character> charss=getWords().stream().flatMap(w->getCharacter(w));
        }
        public Stream<Character> getCharacter(String s){
            List<Character> chars = new ArrayList<Character>();
            for (char c: s.toCharArray()) {
                chars.add(c);
            }
            return chars.stream();
        }
    

limit()、skip()和concat():提取子流和組合流

  • Stream.limit(n)會返回一個包含n個元素的新流如果原始流長度小於n,則返回原始流
    //返回1個具有100個隨機數的流
    Stream<Double> randoms100 = Stream.generate(Math::random).limit(100);
    
  • Stream.skip(n)會跳過n個元素。
    //跳過第一個單詞組成流
    Stream<String> wordsWithoutFirst = getWords().stream().skip(1);
    System.out.println(wordsWithoutFirst.count());//打印為 19  略過了第一個元素
    
  • Stream.concat() 靜態方法。合並兩個流。
    合並流的第一個應該為無限狀態
        Stream<String> total = Stream.concat(getWords().stream().skip(10), getWords().stream());
        System.out.println(total.count());
    

distinct()、Sorted():有狀態轉換

上面的流轉換都是無狀態的。也就是獲得的結果不依賴前面的元素。java8也提供了有狀態的轉換。例如distinct()
distinct()方法需要記錄已存儲的單詞。然后判斷重復及是否添加

    Stream<String> unique = Arrays.asList("todo","todo","todo","today").stream().distinct();
    System.out.println(unique.count());//2


sorted方法排序必須遍歷整個流。並在產生任何元素之前對他進行排序。

    Stream<String> sortWords = getWords().stream().sorted(Comparator.comparing(String::length).reversed());

Collections.sort方法會對原有集合進行排序,Stream.sorted方法會返回一個新的已排序的流;

聚合方法–終止流操作

聚合方法都是終止操作。當一個流應用了終止操作以后,他就不能在進行其他操作了。
聚合方法主要有:count(),max(),min(),findFirst,findAny,allMatch,noneMatch等

    //找到排序最大的單詞 min同理
    Optional<String> max = getWords().stream().max(String::compareToIgnoreCase);
    if(max.isPresent()){
        System.out.println(max.get());
    }
    //找到所有的以a開頭的單詞
    Optional<String> words = getWords().stream().filter(s->s.startsWith("a")).findAny();
    //找到所有的以a開頭的第一個單詞
    Optional<String> wordFirst = getWords().stream().filter(s->s.startsWith("a")).findFirst();


allMatch,noneMatch分別表示所有元素和沒有元素匹配predicate返回true。

Optional類型

  • Optional對象或者是對一個T類型對象的封裝,或表示不是任何對象。他比一般指向T類型的引用更安全,因為他不會返回null–但也是在正確的使用前提下。
    如果存在被封裝的對象,那么 get方法會返回該對象,否則會拋出一個NoSuchElementException.所以,可以用isPresend()來判斷一下。
        Optional<String> max = getWords().stream().max(String::compareToIgnoreCase);
            if(max.isPresent()){
                //do something
            }
    
  • 高效使用Optional的關鍵在於,使用一個或接受正確值、或者返回另一個替代值的方法
    String result = max.orElse("");
    

    如果沒有值則用“”替代。
    此外orElse還有兩個方法
    orElseGet(Lambda):可以調用代碼計算值
    orElseThrow(Exception):拋出異常
  • 創建optional
    利用Optional的靜態方法可以創建對象,比如Optional.of(result)或者Optional.empty();
    ofNullable(obj)方法會跟據obj的值來進行創建optional,如果obj==null,那么這個方法間接等於Optional.empty(),
    如果obj!=null 那么 間接等於Optional.of(obj);
    +收集結果
    可以通過iterator方法生成一個迭代器,用來訪問結果的元素,或者使用stream.toArray(),他會返回一個Object[]數組。如果想獲得具體類型的。
    可以把類型傳遞給數組的構造函數
    String[] result = words.toArray(String[]::new);
    

    如果要把結果收集到一個HashSet中,如果這個過程是並行的,那么不能將元素放到一個單獨的Hashset中,因為HashSet不是線程安全的。所以要使用collect方法,他接受三個參數
    1. 一個能創建目標類型實例的方法,例如hashset的構造函數。
    2. 一個江元素添加到目標中的方法,例如一個add方法。
    3. 一個將兩個對象整合到一起的方法,例如addALl方法。
    HashSet<String> result = stream.collect(HashSet::new,HashSet::add,HashSet::addAll);
    

    在實際中,通常不需要這么做,因為Collector借口已經提供了這三個方法,
    List<String> result = getWords().stream().collect(Collectors.toList());
    List<String> result = getWords().stream().collect(Collectors.toSet());
    TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
    

    Collectors還有許多方法,可以自己去看一下api

原始流類型

主要有IntStream、DoubleStream、LongStream等。
IntStream:short、char、byte、boolean
DoubleStream:float
LongStream:long

IntStream intStream = IntStream.of(1,2,3,4);
intStream = Arrays.stream(value,from,to);  //value 為一個int[]數組
  • 原始流和對象流轉換

當有一個對象流的時候,可以通過mapToInt、mapToLong或者mapToDouble方法將他們轉換為一個原始流。

Stream<String> words = getWords();
IntStream length = words.mapToInt(String::length);


將一個原始流轉換為對象流時,可以使用boxed方法。

Stream<Integer> integers = IntStream.range(0,100).boxed();

並行流

默認情況下,流操作會創建一個串行流,方法Collection.parallelStream()除外。parallel方法可以將任意一個串行流變為一個並行流。

Stream<String> words = getWords().stream().parallel();


只要在終止方法執行時,流處於並行模式,那么所有的延遲執行的流操作就都會被並行執行。
當並行運行流時,他應返回與串行運行時相同的結果。這些操作都是無狀態的,因此可以以任意順序被執行
下面是一個反例,用來提示不應該這么做,假設你需要績一個字符串流中所有短單詞的總數:

        int[] shortWords = new int[11];
        Stream<String> words = Arrays.asList("java","c++","javac","eclipse","tomcat","helloworld").stream();
        words.parallel().forEach(s->{if(s.length()<5)shortWords[s.length()]++;});
        System.out.println(Arrays.toString(shortWords));


傳遞給foreach的函數會在多個線程中並發運行,來更新一個共享數組,這是一個經典的條件競爭。如果多運行幾次,很有可能會得到不同的總數,並且沒有一個是對的。
使用並行流操作的函數都應該是線程安全的。

當執行一個流(串行和並行)操作時,並不會修改流底層的集合,流不會收集他自己的數據–這些數據總是存在另一個集合中。如果想要修改原有的集合,那么就無法定義流操作的輸出。

下面這么做是可以的。

List<String> words = ...;
Stream<String> wordStream=words.stream();
words.add("end");
long n  = wordStream.distinct().count();


下面是不可以的,因為干擾了原集合

Stream<String> wordStream = words.stream();
wordStream.forEach(s->if(s.length<12)words.remove(s));


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM