1. 程式人生 > >java8 Stream流操作介紹

java8 Stream流操作介紹

流操作在本人接觸到的部分,都是處理集合容器Collection,加入流操作主要是為了函數語言程式設計,在很大程度上可以簡化程式碼

簡潔的程式碼處理複雜邏輯這是每個程式猿的追求,廢話不多說,開始介紹

使用Stream基本步驟

1. 建立Stream;
2. 轉換Stream,每次轉換原有Stream物件不改變,返回一個新的Stream物件(**可以有多次轉換**);
3. 對Stream進行聚合(Reduce)操作,獲取想要的結果;

Stream 建立

//Lists是Guava中的一個工具類
List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
nums.stream().filter(num -> num != null).count();
<pre name="code" class="java">/**
of方法:有兩個overload方法,一個接受變長引數,一個介面單一值
*/
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
/**
generator方法:生成一個無限長度的Stream,其元素的生成是通過給定的Supplier
*/
Stream.generate(new Supplier<Double>() {
   	@Override
   	public Double get() {
		return Math.random();
	}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
   
/**
iterate方法:也是生成無限長度的Stream,和generator不同的是,其元素的生成是重複對給定的種子值(seed)
呼叫使用者指定函式來生成的。其中包含的元素可以認為是:seed
*/
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);


所有集合類都可以直接呼叫stream()方法返回一個Stream物件

這個在本文的第一個例子中就展示了從List物件獲取其對應的Stream物件,如果檢視Java doc就可以發現Collection介面有一個stream方法,所以其所有子類都都可以獲取對應的Stream物件。

public interface Collection<E> extends Iterable<E> {
    //其他方法省略
	default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
}
轉換Stream
轉化Stream方法有很處理方法,此處全部介紹,介紹幾種,其餘方法的使用都差不多

1. distinct: 對於Stream中包含的元素進行去重操作(去重邏輯依賴元素的equals方法),新生成的Stream中沒有重複的元素;

List<String> list = Arrays.asList("aa", "bb", "cc", "a", "b", "c", "aa", "ab", "cc", "bb", "bc");
list = list.stream().distinct().collect(Collectors.toList()); // [aa, bb, cc, a, b, c, ab, bc]

2. filter: 對於Stream中包含的元素使用給定的過濾函式進行過濾操作,新生成的Stream只包含符合條件的元素
List<String> list = Arrays.asList("aa", "bb", "cc", "a", "b", "c","a", "aa", "ab", "cc", "bb", "bc");
list.stream().filter(e -> e.length()>=2).forEach(e -> System.out.print(e + ","));
// aa,bb,cc,aa,ab,cc,bb,bc,
3. map: 對於Stream中包含的元素使用給定的轉換函式進行轉換操作,新生成的Stream只包含轉換生成的元素。這個方法有三個對於原始型別的變種方法,分別是:mapToInt,mapToLong和mapToDouble。這三個方法也比較好理解,比如mapToInt就是把原始Stream轉換成一個新的Stream,這個新生成的Stream中的元素都是int型別。之所以會有這樣三個變種方法,可以免除自動裝箱/拆箱的額外消耗;
<pre name="code" class="java">List<Integer> integerList = Arrays.asList(1, 2, 3, 8, 9, 6, 4, 2, 3, 7, 6);
integerList.stream().map(var -> { var ++; var += 2; return var; }).forEach(System.out::print);
DoubleStream doubleStream = integerList.stream().mapToDouble((value) -> value * 1.0);

4. flatMap:和map類似,不同的是其每個元素轉換得到的是Stream物件,會把子Stream中的元素壓縮到父集合中;
List<Integer> together = Stream.of(asList(1, 2),asList(3, 4)).flatMap(numbers -> numbers.stream()).collect(toList());
將兩個佇列一起處理流操作的時候,可以利用此方法將其轉化為一個流

上部分的程式碼可以簡化為

List<Integer> together = Stream.of(Arrays.asList(1, 2),Arrays.asList(3, 4))
                .flatMap(Collection::stream).collect(Collectors.toList());
5. peek: 生成一個包含原Stream的所有元素的新Stream,同時會提供一個消費函式(Consumer例項),新Stream每個元素被消費的時候都會執行給定的消費函式;
此方法不會更改流中元素形式,主要用於除錯或輸出。
list.stream().distinct().peek(e -> System.out.println(e.length())).count();
在peek()方法中使用lambda 表示式的時候,返回值只能為void型別 


方法暫時只介紹這麼幾個其餘還有 limit()  skip()   forEach()等

聚合(Reduce)操作

下面會分兩部分來介紹匯聚操作:

1. 可變匯聚:把輸入的元素們累積到一個可變的容器中,比如Collection或者StringBuilder;

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
       List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
               collect(() -> new ArrayList<Integer>(),
                       (list, item) -> list.add(item),
                       (list1, list2) -> list1.addAll(list2));
上面這段程式碼就是對一個元素是Integer型別的List,先過濾掉全部的null,然後把剩下的元素收集到一個新的List中。進一步看一下collect方法的三個引數,都是lambda形式的函式(*上面的程式碼可以使用方法引用來簡化,留給讀者自己去思考*)。

第一個函式生成一個新的ArrayList例項;
第二個函式接受兩個引數,第一個是前面生成的ArrayList物件,二個是stream中包含的元素,函式體就是把stream中的元素加入ArrayList物件中。第二個函式被反覆呼叫直到原stream的元素被消費完畢;
第三個函式也是接受兩個引數,這兩個都是ArrayList型別的,函式體就是把第二個ArrayList全部加入到第一個中;

但是上面的collect方法呼叫也有點太複雜了,沒關係!我們來看一下collect方法另外一個override的版本,其依賴

<R, A> R collect(Collector<? superT, A, R> collector);

Java8還給我們提供了Collector的工具類–[Collectors]其中已經定義了一些靜態工廠方法,比如:Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中。這樣的靜態方法還有很多,這裡就不一一介紹了,大家可以直接去看JavaDoc。下面看看使用Collectors對於程式碼的簡化:

List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
                collect(Collectors.toList());

2. 其他匯聚:除去可變匯聚剩下的,一般都不是通過反覆修改某個可變物件,而是通過把前一次的匯聚結果當成下一次的入參,反覆如此。比如reduce,count,allMatch;

reduce方法:reduce方法非常的通用,後面介紹的count,sum等都可以使用其實現。reduce方法有三個override的方法,本文介紹兩個最常用的,最後一個留給讀者自己學習。先來看reduce方法的第一種形式,其方法定義如下:
Optional<T> reduce(BinaryOperator<T> accumulator);
接受一個BinaryOperator型別的引數,在使用的時候我們可以用lambda表示式來。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get());
可以看到reduce方法接受一個函式,這個函式有兩個引數,第一個引數是上次函式執行的返回值(也稱為中間結果),第二個引數是stream中的元素,這個函式把這兩個值相加,得到的和會被賦值給下次執行這個函式的第一個引數。要注意的是:**第一次執行的時候第一個引數的值是Stream的第一個元素,第二個引數是Stream的第二個元素**。這個方法返回值型別是Optional,這是Java8防止出現NPE的一種可行方法,後面的文章會詳細介紹,這裡就簡單的認為是一個容器,其中可能會包含0個或者1個物件。
reduce方法還有一個很常用的變種:
T reduce(T identity, BinaryOperator<T> accumulator);
這個定義上上面已經介紹過的基本一致,不同的是:它允許使用者提供一個迴圈計算的初始值,如果Stream為空,就直接返回該值。而且這個方法不會返回Optional,因為其不會出現null值。下面直接給出例子,就不再做說明了。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));

count方法:獲取Stream中元素的個數。比較簡單,這裡就直接給出例子,不做解釋了。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().count());

– 搜尋相關
allMatch:是不是Stream中的所有元素都滿足給定的匹配條件
anyMatch:Stream中是否存在任何一個元素滿足匹配條件
findFirst: 返回Stream中的第一個元素,如果Stream為空,返回空Optional
noneMatch:是不是Stream中的所有元素都不滿足給定的匹配條件
max和min:使用給定的比較器(Operator),返回Stream中的最大|最小值
下面給出allMatch和max的例子,剩下的方法讀者當成練習。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println(ints.stream().allMatch(item -> item < 100));
ints.stream().max((o1, o2) -> o1.compareTo(o2)).ifPresent(System.out::println);