上一篇系統學了方法引用的幾種型別及應用場景,本篇開始我們正式學習Stream。
Java8中的Stream與lambda表示式可以說是相伴相生的,通過Stream我們可以更好的更為流暢更為語義化的操作集合。Stream api都位於java.util.stream包中。其中就包含了最核心的Stream介面,一個Stream例項可以序列或者並行操作一組元素序列,官方文件中給出了一個示例
* <pre>{@code
* int sum = widgets.stream()//建立一個流
* .filter(w -> w.getColor() == RED)//取出顏色是紅色的元素
* .mapToInt(w -> w.getWeight())//返回每個紅色元素的重量
* .sum();//重量求和
* }</pre>
Java8中,所有的流操作會被組合到一個 stream pipeline中,這點類似linux中的pipeline概念,將多個簡單操作連線在一起組成一個功能強大的操作。一個 stream pileline首先會有一個資料來源,這個資料來源可能是陣列、集合、生成器函式或是IO通道,流操作過程中並不會修改源中的資料;然後還有零個或多箇中間操作,每個中間操作會將接收到的流轉換成另一個流(比如filter);最後還有一個終止操作,會生成一個最終結果(比如sum)。流是一種惰性操作,所有對源資料的計算只在終止操作被初始化的時候才會執行。
總結一下流操作由3部分組成
1.源
2.零個或多箇中間操作
3.終止操作 (到這一步才會執行整個stream pipeline計算)
建立流的幾種方式
//第一種 通過Stream介面的of靜態方法建立一個流
Stream<String> stream = Stream.of("hello", "world", "helloworld");
//第二種 通過Arrays類的stream方法,實際上第一種of方法底層也是呼叫的Arrays.stream(values);
String[] array = new String[]{"hello","world","helloworld"};
Stream<String> stream3 = Arrays.stream(array);
//第三種 通過集合的stream方法,該方法是Collection介面的預設方法,所有集合都繼承了該方法
Stream<String> stream2 = Arrays.asList("hello","world","helloworld").stream();
接下來我們看一個簡單的需求:將流中字元全部轉成大寫返回一個新的集合
List<String> list = Arrays.asList("hello", "world", "helloworld");
List<String> collect = list.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
這裡我們使用了Stream的map方法,map方法接收一個Function函式式介面例項,這裡的map和Hadoop中的map概念完全一致,對每個元素進行對映處理。然後傳入lambda表示式將每個元素轉換大寫,通過collect方法將結果收集到ArrayList中。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);//map函式定義
那如果我們想把結果放到Set中或者替他的集合容器,也可以這樣
list.stream().map(s -> s.toUpperCase()).collect(Collectors.toSet());//放到Set中
或者更為通用的
list.stream().map(s -> s.toUpperCase()).collect(Collectors.toCollection(TreeSet::new));//自定義容器型別
我們可以自己制定結果容器的型別Collectors的toCollection接受一個Supplier函式式介面型別引數,可以直接使用構造方法引用的方式。
Stream中除了map方法對元素進行對映外,還有一個flatMap方法
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap從方法命名上可以解釋為扁平的map
map方法是將一個容器裡的元素對映到另一個容器中。
flatMap方法,可以將多個容器的元素全部對映到一個容器中,即為扁平的map。
看一個求每個元素平方的例子
Stream<List<Integer>> listStream =
Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
List<Integer> collect1 = listStream.flatMap(theList -> theList.stream()).
map(integer -> integer * integer).collect(Collectors.toList());
首先我們建立了一個Stream物件,Stream中的每個元素都是容器List<Integer>型別,並使用三個容器list初始化這個Stream物件,然後使用flatMap方法將每個容器中的元素對映到一個容器中,這時flatMap接收的引數Funciton的泛型T就是List<Integer>型別,返回型別就是T對應的Stream。最後再對這個容器使用map方法求出買個元素的平方。
然後介紹一個用於獲取統計資訊的方法
//同時獲取最大 最小 平均值等資訊
List<Integer> list1 = Arrays.asList(1, 3, 5, 7, 9, 11);
IntSummaryStatistics statistics = list1.stream().filter(integer -> integer > 2).mapToInt(i -> i * 2).skip(2).limit(2).summaryStatistics();
System.out.println(statistics.getMax());//18
System.out.println(statistics.getMin());//14
System.out.println(statistics.getAverage());//16
將list1中的資料取出大於2的,每個數進行平方計算,skip(2)忽略前兩個,limit(2)再取出前兩個,summaryStatistics對取出的這兩個數計算統計資料。mapToInt接收一個ToIntFunction型別,也就是接收一個引數返回值是int型別。
接下來看一下Stream中的一個靜態方法,generate方法
/**
* Returns an infinite sequential unordered stream where each element is
* generated by the provided {@code Supplier}. This is suitable for
* generating constant streams, streams of random elements, etc.
*
* @param <T> the type of stream elements
* @param s the {@code Supplier} of generated elements
* @return a new infinite sequential unordered {@code Stream}
*/
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
generate接收一個Supplier,適合生成連續不斷的流或者一個全部是隨機數的流
Stream.generate(UUID.randomUUID()::toString).findFirst().ifPresent(System.out::println);
使用UUID.randomUUID()::toString 方法引用的方式建立了Supplier,然後取出第一個元素,這裡的findFirst返回的是 Optional,因為流中有可能沒有元素,為了避免空指標,在使用前 ifPresent 進行是否存在的判斷。
最後再學習一下另一個靜態方法,iterate
/**
* Returns an infinite sequential ordered {@code Stream} produced by iterative
* application of a function {@code f} to an initial element {@code seed},
* producing a {@code Stream} consisting of {@code seed}, {@code f(seed)},
* {@code f(f(seed))}, etc.
*
* <p>The first element (position {@code 0}) in the {@code Stream} will be
* the provided {@code seed}. For {@code n > 0}, the element at position
* {@code n}, will be the result of applying the function {@code f} to the
* element at position {@code n - 1}.
*
* @param <T> the type of stream elements
* @param seed the initial element
* @param f a function to be applied to to the previous element to produce
* a new element
* @return a new sequential {@code Stream}
*/
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
Objects.requireNonNull(f);
final Iterator<T> iterator = new Iterator<T>() {
@SuppressWarnings("unchecked")
T t = (T) Streams.NONE;
@Override
public boolean hasNext() {
return true;
}
@Override
public T next() {
return t = (t == Streams.NONE) ? seed : f.apply(t);
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
iterate方法有兩個引數,第一個是seed也可以稱作種子,第二個是一個UnaryOperator,UnaryOperator實際上是Function的一個子介面,和Funciton區別就是引數和返回型別都是同一種型別
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
}
iterate方法第一次生成的元素是UnaryOperator對seed執行apply後的返回值,之後所有生成的元素都是UnaryOperator對上一個apply的返回值再執行apply,不斷迴圈。
f(f(f(f(f(f(n))))))......
//從1開始,每個元素比前一個元素大2,最多生成10個元素
Stream.iterate(1,item -> item + 2).limit(10).forEach(System.out::println);
我們在使用stream api時也要注意一些陷阱,比如下面這個例子
//Stream陷阱 distinct()會一直等待產生的結果去重,將distinct()和limit(6)調換位置,先限制結果集再去重就可以了
IntStream.iterate(0,i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);
如果distinct()一直等待那程式會一直執行不斷生成資料,所以需要先限制結果集再去進行去重操作就可以了。