JDK8的Stream操作


原文:https://mp.weixin.qq.com/s/N2zor5RzuHHTHQLHWVBttg

作者:思海同學  好好學java

其他資料:https://mp.weixin.qq.com/s/bSref5GwAxNbVVpSWI-66w

     https://mp.weixin.qq.com/s/2qj65eTzjUTjCEqlRahnZA

  相對於Java8之前的Java的相關操作簡直是天差地別,Java8 的流式操作的出現,也很大程度上改變了開發者對於Java的繁瑣的操作的印象,從此,Java也走向了函數式編程的道路!我相信只要長期的使用,一定可以精簡你的代碼,寫的更加順手,你也會喜歡上它的!

1 流的創建

1.1  創建流的方法

  既然需要聊聊流的操作,那么,首先還是先看看怎么創建流。創建流的方法有三種,分別是:Stream.of()、Stream.iterate()、Stream.generate(),然后,分別看一下這三個方法的聲明。

static <T> Stream<T> of(T... values)

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) static <T> Stream<T> generate(Supplier<T> s)

  Stream.of():參數很簡單,就是一系列的泛型參數。

  Stream.iterate():第一個參數是一個初始值,第二個參數是一個操作。

  Stream.generate():參數就是一個Supplier的供給型的參數。

 

1.2  創建流的方法舉例

/**
  * 創建Stream
  */
public static void createStream(){ //利用Stream.of方法創建流 Stream<String> stream = Stream.of("hello", "world", "Java8"); stream.forEach(System.out::println); System.out.println("##################");
//利用Stream.iterate方法創建流,limit限制Stream的長度 List<Integer> stream2 = Stream.iterate(10, n -> n + 1).limit(5).collect(Collectors.toList()); stream2.forEach(System.out::println); System.out.println("##################"); //利用Stream.generate方法創建流 List<Double> stream3 = Stream.generate(Math::random).limit(5).collect(Collectors.toList()); stream3.forEach(System.out::println); System.out.println("##################"); //從現有的集合中創建流 List<String> strings = Arrays.asList("hello", "world", "Java8"); String string = strings.stream().collect(Collectors.joining(",")); System.out.println(string); }

  在上面的例子中,Stream.of()方法的參數是幾個字符串;Stream.iterate()方法的第一個參數是初始值 10,第二個參數是在10 的基礎上每次加 1 的操作;Stream.generate()的參數是用 Random 方法產生隨機數。

 

1.3 流的創建總結

  流的創建有三種方法,分別是Stream.of()、Stream.iterate()、Stream.generate(),這幾個都是 Stream 類的靜態方法,所以,使用起來非常的方便。

 

2. 流的操作

  我們知道怎么創建流了,接下來,我們就看看對流可以進行哪些操作,使用了 Stream 流之后,是否會比 Java8 之前方便很多呢?

2.1  裝箱流

  在處理對象流的時候,可以利用 Collectors 類的靜態方法轉換為集合,例如,將字符串流轉換為 List<String> ,這種方式是沒有問題的。但是,如果遇到 double流想要轉換為 List時,這是就會報錯。

DoubleStream.of(1.0, 2.0, 3.0).collect(Collectors.toList());//錯誤的寫法

  這種方式就是錯誤的,編譯是不能通過的。別慌,對於這種問題,有 3 種比較好的解決方法。

  boxed 方法

  利用 boxed 方法,可以將 DoubleStream 轉換為 Stream<Double> ,這樣就解決了上面的問題,例如:

DoubleStream.of(1.0, 2.0, 3.0).boxed().collect(Collectors.toList());

  mapToObj 方法

  利用 mapToObj 方法也可以實現上面的功能,另外,也提供了 mapToInt、mapToLong、mapToDouble 等方法將基本類型流轉換為相關包裝類型。 

DoubleStream.of(1.0, 2.0, 3.0).mapToObj(Double::valueOf).collect(Collectors.toList());

  collect 方法

   一般情況下,我們利用 collect 方法的時候,都是用於將流的數據收集為基本類型的集合,例如: 

stream.collect(Collectors.toList())

  然而,collect 方法其實還有一種更加一般化的形式,如下:

<R> R collect(Supplier<R> supplier, ObjIntConsumer<R> accumulator, BiCnsumer<R,R> combiner)

  上面這種方法的第一個參數是一個供給器,相當於初始化一個容器,第二個參數是累加器,相當於給初始化的容器賦值,第三個參數是組合器,相當於將這些元素全部組合到一個容器。

  下面,我們通過一個簡單的例子來看看到底是怎么使用的

List<Double> list = DoubleStream.of(1.0, 2.0, 3.0).collect(ArrayList<Double>::new, ArrayList::add, ArrayList::addAll);

  上面的例子我們可以看到,第一個參數:使用一個靜態方法初始化一個 List 容器,第二個參數:使用靜態方法 add ,添加元素,第三個參數:使用靜態方法 addAll ,用於聯合所有的元素。

  從最后的返回值為 List<Double>,我們也可以看出,全部組合成一個初始化的 List 集合中了。

 

2.2 字符串與流之間的轉換

  這一小節主要講解一下字符串與流之間的轉換,將 String 轉為流有兩種方法,分別是 java.lang.CharSequence 接口定義的默認方法 chars 和 codePoints ,而將流轉為字符串就是我們前面已經講解到的方法 collect 。

public void testString2Stream() {
    //使用codePoints把字符串轉成Stream,在使用collect轉成字符串
    String s = "hello world Java8".codePoints()
           .collect(StringBuffer::new, StringBuffer::appendCodePoint, StringBuffer::append).toString();
    //使用chars把字符串轉成Stream,在使用collect轉成字符串
    String s1 = "hello world Java8".chars()
            .collect(StringBuffer::new, StringBuffer::appendCodePoint, StringBuffer::append).toString();
}

  在上面的例子中,先用chars 和 codePoints 方法轉換為流,然后都是利用 collect 方法再轉回字符串。

 

2.3 流的映射 map 與 flatMap

  流的映射是什么意思呢,我們先將一個在 Java8 之前的例子,我們常常需要將一個集合的對象的某一個字段取出來,然后再存到另外一個集合中,這種場景我們在 Java8 之前我們會這樣實現。

public void mapTest() {
    List<Person> list = new ArrayList<>();
    list.add(new Person("Java5"));
    list.add(new Person("Java6"));
    list.add(new Person("Java7"));

    List<String> strings = new ArrayList<>();
    for (Person p : list) {
        strings.add(p.getName());
    }
}

  是不是這樣很麻煩,這也就是以前大家一直所說的 Python 用一招,Java 需要用花招!

  但是,Java8 卻改變了這種現實,我們來看一看怎么使用 map 和 flatMap。

  首先,我們先看一下這倆個方法的聲明;

<R> Stream<R> map(Function<? super T,? extends R> mapper)

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

  接下來,我們用這兩個方法改寫上面的方式,先看看 map 方法;

public void mapTest() {
    List<Person> list = new ArrayList<>();
    list.add(new Person("Java5"));
    list.add(new Person("Java6"));
    list.add(new Person("Java7"));

    List<String> strings = list.stream().map(Person::getName).collect(Collectors.toList());
}

  通過使用 map 方法,參數給定 Person::getName 映射出 name,然后再用 collect 收集到 List 中,就完成了上面的負責的操作,是不是很舒服。

  但是,如果我們用 map 方法想要映射出集合屬性,會遇到一個問題;

List<List<Friend>> collect = list.stream().map(Person::getFriends).collect(Collectors.toList());

  我們發現,上面的返回值是 List<List<Friend>>,這種形式集合里面還包着集合,處理有點麻煩,但是,不是還有另外 flatMap 沒有使用嗎,這個方法正好能夠解決這個問題

List<Friend> collect1 = list.stream().flatMap(friend -> friend.getFriends().stream()).collect(Collectors.toList());

  發現,這個方法的返回值是 List<Friend>,正如我們看到的,flatMap 的方法能夠“展平”包裹的流,這就是 map 和 flatMap 的區別。

  mapping函數,也是映射字段。

public static void main(String[] args) {
    ArrayList<OrderInfo> orderArrayList = Lists.newArrayList();
    orderArrayList.add(new OrderInfo("order111", "AAAA"));
    orderArrayList.add(new OrderInfo("order111", "BBBB"));
    orderArrayList.add(new OrderInfo("order222", "cccc"));
    orderArrayList.add(new OrderInfo("order222", "DDDD"));

    Map<String, List<String>> collect = orderArrayList.stream().collect(Collectors.groupingBy(OrderInfo::getOrderNo,
                Collectors.mapping(OrderInfo::getGoodsName, Collectors.toList())));
    System.out.println(" ===" + JSONUtil.toJson(collect));
}

  上面的例子中,OrderInfo對象有兩個字段,orderNo,goodsName,要根據orderNo對結果進行分組,goodsName組裝成List,最終結果返回格式是Map<String, List<String>>

 

2.4 流的連接

  流的連接有兩種方式,如果是兩個流的連接,使用 Stream.concat 方法,如果是三個及三個以上的流的連接,就使用 Stream.flatMap 方法

public void testConcatStream() {
    //兩個流的連接
    Stream<String> first = Stream.of("sihai", "sihai2", "sihai3");
    Stream<String> second = Stream.of("sihai4", "sihai5", "sihai6");
    Stream<String> third = Stream.of("siha7", "sihai8", "sihai9");
    Stream<String> concat = Stream.concat(first, second);
    //多個流的連接
    Stream<String> stringStream = Stream.of(first, second, third).flatMap(Function.identity());
}

 

3 流的規約操作

  流的規約操作幾種類型,這里都講一下。

  內置的規約操作

  基本類型流都有內置的規約操作。包括average、count、max、min、sum、summaryStatistics,前面的幾個方法相信不用說了,summaryStatistics 方法是前面的幾個方法的結合,下面我們看看他們如何使用。

public void testReduce1() {
    String[] strings = {"hello", "sihai", "hello", "Java8"};
    long count = Arrays.stream(strings).map(String::length).count();
    System.out.println(count);
System.out.println(
"##################"); int sum = Arrays.stream(strings).mapToInt(String::length).sum(); System.out.println(sum); System.out.println("##################"); OptionalDouble average = Arrays.stream(strings).mapToInt(String::length).average(); System.out.println(average); System.out.println("##################"); OptionalInt max = Arrays.stream(strings).mapToInt(String::length).max(); System.out.println(max); System.out.println("##################"); OptionalInt min = Arrays.stream(strings).mapToInt(String::length).min(); System.out.println(min); DoubleSummaryStatistics statistics = DoubleStream.generate(Math::random).limit(1000).summaryStatistics(); System.out.println(statistics); }

 

  基本的規約操作

  基本的規約操作是利用前面講過的 reduce 方法實現的,IntStream 接口定義了三種 reduce 方法的重載形式,如下;

OptionalInt reduce(IntBinaryOperator op)

int reduce(int identity, IntBianryOperator op)

<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BianryOperator<U> combiner)

  上面的 identity 參數就是初始化值的意思,IntBianryOperator 類型的參數就是操作,例如 lambda 表達式;BianryOperator<U> combiner是一個組合器,在前面有講過。

  下面我們通過一個例子來講解一下。

public void testReduce2() {
    int sum = IntStream.range(1, 20).reduce((x, y) -> x + y).orElse(0);
    System.out.println(sum);
    System.out.println("##################");

    int sum2 = IntStream.range(1, 20).reduce(0, (x, y) -> x + 2 * y);
    System.out.println(sum2);
    System.out.println("##################");

int sum3 = IntStream.range(1, 20).reduce(0, Integer::sum); System.out.println(sum3); }

  例子中的第一個是1到20累加的操作,第二個以0為初始值,然后2倍累加,第三個是以0為初始值,累加。

  

  流的計數

  流的數量統計有兩種方法,分別是 Stream.count() 方法和 Collectors.counting() 方法。

public void testStatistics() {
    //統計數量
    String[] strings = {"hello", "sihai", "hello", "Java8"};
    long count = Arrays.stream(strings).count();
    System.out.println(count);
    System.out.println("##################");

    Long count2 = Arrays.stream(strings).collect(Collectors.counting());
    System.out.println(count2);
}

  

  流的查找

  流的查找 Stream 接口提供了兩個方法 findFirst 和 findAny。findFirst 方法返回流中的第一個元素的 Optional,而 findAny 方法返回流中的某個元素的 Optional。

  我們來看一個例子。

String[] strings = {"hello", "sihai", "hello", "Java8"};
Optional<String> first = Arrays.stream(strings).findFirst();
System.out.println(first.get());
System.out.println("##################");

Optional<String> any = Arrays.stream(strings).findAny();
System.out.println(any.get());

 

  流的匹配

  流的匹配 Stream 接口提供了三個方法,分別是 anyMatch(任何一個元素匹配,返回 true)、allMatch(所有元素匹配,返回 true)、noneMatch(沒有一個元素匹配,返回 true)。

boolean b = Stream.of(1, 2, 3, 4, 5, 10).anyMatch(x -> x > 5);
System.out.println(b);
System.out.println("##################");

boolean b2 = Stream.of(1, 2, 3, 4, 5, 10).allMatch(x -> x > 5);
System.out.println(b2);
System.out.println("##################");

boolean b3 = Stream.of(1, 2, 3, 4, 5, 10).noneMatch(x -> x > 5);
System.out.println(b3);


免責聲明!

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



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