Java8 Stream

java8 的 Stream

開始以前先摘抄一些概念,java

  • 流的操做類型分爲兩種:數組

    • Intermediate:一個流能夠後面跟隨零個或多個 intermediate 操做。其目的主要是打開流,作出某種程度的數據映射/過濾,而後返回一個新的流,交給下一個操做使用。這類操做都是惰性化的(lazy),就是說,僅僅調用到這類方法,並無真正開始流的遍歷。安全

    • Terminal:一個流只能有一個 terminal 操做,當這個操做執行後,流就會被,沒法再被操做。因此這一定是流的最後一個操做。Terminal 操做的執行,纔會真正開始流的遍歷,而且會生成一個結果,或者一個 side effect。app

    • short-circuitingide

      對於一個無限大(infinite/unbounded)的 Stream,返回有限的新的Stream或者能在有限的時間內返回結果性能

經過以上的概念,能夠歸類以下的方法ui

  • Intermediate:this

    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered線程

  • Terminal:code

    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:

    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

生成Stream的幾種方式

  • 從 Collection 和數組 (最經常使用)
// 1. 從Collection中生成
List list = new ArrayList<String>(){{add("A");add("B");}};
Stream<String> stream = list.stream();

// 2. 下面是從數組生成
int[] arr = new int[]{1,2,3};
IntStream stream = Arrays.stream(arr);
// or 
Stream.of(1,2,3)
  • 其餘
// 從BufferedReader中生成
java.io.BufferedReader.lines()
// 經過一些靜態的方法
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
java.nio.file.Files.lines()

Stream 概覽

先構建一個對象,下面全部的方法會用到這個基礎對象,以及構造一個簡單的persons,供下文使用

class Person{
    private String name;
    private Integer age;
    // 省略getter,setter方法
    public Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }
}
List<Person> persons = new ArrayList<>();
persons.add(new Person("jack",17));
persons.add(new Person("rose",18));
persons.add(new Person("小明",17));

map/flatMap

map會對流中的每一個元素應用map中的lambda表達式

List<String> result = persons.stream().map(
    a -> a.getName().toUpperCase()).collect(Collectors.toList());
// result中的元素爲 [JACK, ROSE, 小明]

上面的意思是取流中的每一個元素(這裏就是Person),把name屬性轉爲大寫,最後的collect(Collectors.toList())就是上面說的Terminal操做,所以咱們能夠獲取到一個List 的結果

上面是一對一的操做,若是是一對多的,則可使用flatMap

Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream());

flatMap抽取出底層的元素放在一塊兒,就是上面的例子中把三個list中的元素所有抽取出來,最後返回一個數字流

filter

List<Person> collect = persons.stream()
    .filter(a -> a.getAge() > 17).collect(Collectors.toList());
// 過濾出流中全部age > 17的元素,返回一個新的流

filter接受一個Predicate(謂詞)參數,返回符合爲true的元素

distinct

顧名思義就是去重的操

List<Integer> integers = Arrays.asList(1, 1, 1);
List<Integer> result = integers.stream().distinct().collect(Collectors.toList());
// 最終result裏面的元素只有一個[1]

須要注意的是,每一個元素去重基於的 Object#equals(Object) 方法,在一些場合使用Collection中的removeIf()默認方法更加合適

foreach

Stream提供了 foreach 方法來遍歷流中的每一個數據

persons.stream().forEach(a -> System.out.println(a.getName()));
// 更簡單的寫法以下,由於List接口中有了一個forEach默認方法,關於接口默認方法這裏就不闡述了
persons.forEach(a -> System.out.println(a.getName()));

sorted/max/min

sorted會對流中的元素作排序,默認是從小大到的順序

List<Person> result = persons.stream().sorted(Comparator.comparing(Person::getAge)).collect(Collectors.toList());

若是能夠Stream 進行各種 map、filter、limit、skip 甚至 distinct 來減小元素數量後,再排序,這序明顯縮短程序執行時間

max/min操做相似,不過他們各自返回最大值和最小值,若是是對象都是int,long,double類型,可使用IntStream、LongStream、DoubleStream,避免拆箱裝箱的性能消耗。這個時候可使用以下操做

OptionalLong max = persons.stream().mapToLong(Person::getAge).max();
System.out.println(max.getAsLong()); //輸出 18

peek

以前咱們說過流只能被terminal 操做一次,若是有相似的場景須要屢次terminal 操做,peek能夠達到目的

// peek方法方法會使用一個Consumer消費流中的元素,可是返回的流仍是包含原來的流中的元素。
OptionalLong max = persons.stream().peek(a -> System.out.println(a.getName())).mapToLong(Person::getAge).max();
System.out.println(max.getAsLong());

match

// 流中全部的元素都知足謂詞中的條件才返回true
public boolean  allMatch(Predicate<? super T> predicate)
// 流中全部的元素有一個知足謂詞中的條件就返回true
public boolean  anyMatch(Predicate<? super T> predicate)
// 流中全部的元素都不知足謂詞中的條件才返回true
public boolean  noneMatch(Predicate<? super T> predicate)

reduce

reduce方法返回單個的結果值,而且reduce操做每處理一個元素老是建立一個新值。reduce主要有以下三個方法

// 最經常使用的,identity參數是初始值,accumulator是累加器,其方法簽名是 apply(T t,U u),累加的值會被賦值給下次執行方法的第一個參數,也就是t
T reduce(T identity, BinaryOperator<T> accumulator);
// 沒有初始值,返回的是一個Optional,由於null是不安全的
Optional<T> reduce(BinaryOperator<T> accumulator);
// 第三個參數是使用並行流(parallelStream)時,合併每一個線程的操做
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

因此經常使用的方法有average, sum, min, max, count,使用reduce方法均可實現。

// 實現加法
Integer result = Stream.of(1, 2, 3).stream().reduce(0, (a, b) -> a + b); // result:6
// 實現減法
Integer result = Stream.of(1, 2, 3).reduce(10, (a, b) -> a - b); // result:10 - 6 = 4

collect(收集結果)

collect能夠把流收集起來,能夠是一個List,Map以及分組等

List<Integer> result = Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, List::addAll);
// result: [1,2,3]
  • 第一個方法生成一個新的ArrayList;
  • 第二個方法中第一個參數是前面生成的ArrayList對象,第二個參數是stream中包含的元素,方法體就是把stream中的元素加入ArrayList對象中。第二個方法被反覆調用直到原stream的元素被消費完畢;
  • 第三個方法也是接受兩個參數,這兩個都是ArrayList類型的,方法體就是把第二個ArrayList所有加入到第一個中;

代碼看起來並不清真,也不容易理解。所以,還有另外的簡便寫法,下面會提到

Collector

Collectors實現了Collector接口,提供了不少有用的方法

toList/toSet

List<Integer> result = Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, List::addAll);
// 能夠簡寫爲
List<Integer> result = Stream.of(1, 2, 3).collect(Collectors.toList());
// 若是須要去重,可使用toSet
Set<Integer> result = Stream.of(1, 1, 3).collect(Collectors.toSet());

toMap

// Function.identity() 的做用等同於  a -> a,即輸入等於輸出
Map<Integer, Person> collect = persons.stream().collect(Collectors.toMap(Person::getAge, Function.identity()));
// 這裏就要有坑了,若是key相同的話,會拋出異常,能夠經過指定第三個參數指定合併方式
Map<Integer, Person> collect = persons.stream().collect(Collectors.toMap(Person::getAge, Function.identity(),(a,b) -> a)); // 這裏咱們指定了若是有衝突,取以前的那個

覺得這樣就沒有坑了麼?不對,若是在value爲null的時候,還會拋出 NPE的異常,HashMap中的merge代碼中有以下這一行。

因此,最好不要使用這個方法,使用以下的寫法代替

Map<Integer,Person> map = new HashMap<>();
persons.forEach(a -> map.put(a.getAge(),a));
相關文章
相關標籤/搜索