Java8的新特性--Stream API

寫(xiě)在前面

Java8中有兩大最為重要的改變:

  • Lambda表達(dá)式
  • Stream API(java.util.stream.*)

Stream是Java8中處理集合的關(guān)鍵抽象概念,它可以指定你希望對(duì)集合進(jìn)行的操作迅矛,可以執(zhí)行非常復(fù)雜的查找浦译、過(guò)濾荡碾、映射數(shù)據(jù)等操作皂岔。使用Stream API對(duì)集合數(shù)據(jù)進(jìn)行操作咽斧,就類似于使用SQL執(zhí)行數(shù)據(jù)庫(kù)查詢喂走。也可以使用Stream API來(lái)并行執(zhí)行操作捺弦。簡(jiǎn)而言之饮寞,Stream API提供了一種高效且易于使用的處理數(shù)據(jù)的方式。

Stream是什么

流是數(shù)據(jù)渠道列吼,用于操作數(shù)據(jù)源(集合幽崩、數(shù)組等)所生成的元素序列。

即流這個(gè)數(shù)據(jù)渠道冈欢,在數(shù)據(jù)傳輸過(guò)程中歉铝,對(duì)數(shù)據(jù)源做一系列流水線式的中間操作,然后產(chǎn)生一個(gè)新的流凑耻,這個(gè)流不會(huì)改變?cè)瓉?lái)的流

集合講的是數(shù)據(jù)太示,流講的是計(jì)算

注意

  1. Stream 自己不會(huì)存儲(chǔ)元素。
  2. Stream 不會(huì)改變?cè)磳?duì)象香浩。相反类缤,他們會(huì)返回一個(gè)持有結(jié)果的新Stream。
  3. Stream 操作是延遲執(zhí)行的邻吭。這意味著他們會(huì)等需要結(jié)果的時(shí)候才執(zhí)行餐弱。

Stream操作的三個(gè)步驟

1. 創(chuàng)建Stream

1.1 通過(guò)Collection集合

通過(guò)Collection系列集合提供的Stream()串行流或parallelStream()并行流。

其中,Java8中的Collection接口擴(kuò)展了膏蚓,提供了兩個(gè)獲取流的方法:

  • 返回一個(gè)順序流
    /*
    * @since 1.8
    */
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
  • 返回一個(gè)并行流
    /*
     * @since 1.8
     */
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }

下面我們寫(xiě)個(gè)通過(guò)Collection系列集合流創(chuàng)建流的例子瓢谢。

    @Test
    public void test01(){
        //通過(guò)Collection系列集合提供的Stream()或parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();
        Stream<String> stringStream = list.parallelStream();
    }

1.2 通過(guò)數(shù)組Arrays

        //通過(guò)數(shù)組Arrays 中的靜態(tài)方法stream()獲取數(shù)組
        Employee[] employees = new Employee[10];
        Stream<Employee> stream2 = Arrays.stream(employees);

通過(guò)Stream流中的of()方法來(lái)創(chuàng)建

        //通過(guò)Stream類中的靜態(tài)方法of()
        Stream<String> stream3 = Stream.of("aa", "bb", "cc", "dd");

1.3 創(chuàng)建無(wú)限流

創(chuàng)建無(wú)限流的兩種方法:迭代、生成
我們先來(lái)看一下迭代Stream.iterate()方法的定義


迭代方法的定義

我們?cè)賮?lái)看一下生成的方式驮瞧, Stream.generate()這里需要一個(gè)供給型的參數(shù)


生成

再來(lái)寫(xiě)下創(chuàng)建無(wú)限流的例子

        //創(chuàng)建無(wú)限流
        //迭代   Stream.iterate()傳倆參數(shù)氓扛,第一個(gè)是種子即起始值,第二個(gè)參數(shù)是Lambda表達(dá)式即對(duì)起始值進(jìn)行的操作论笔,
        //這里是生成偶數(shù)
        Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
        //無(wú)限流就是沒(méi)有限制采郎,需要一個(gè)終止操作限制
        stream4.limit(4).forEach(System.out::println);
        //生成的方式
        Stream<Double> generate5 = Stream.generate(() -> Math.random());
        generate5.limit(5).forEach(System.out::println);

運(yùn)行結(jié)果

0
2
4
6
0.3204608858702849
0.495403965457605
0.9188968488509007
0.18726624455121932
0.3791774193868236

2. 中間操作

一個(gè)中間操作鏈,對(duì)數(shù)據(jù)源的數(shù)據(jù)進(jìn)行處理狂魔。

多個(gè)中間操作可以連接起來(lái)形成一個(gè)流水線蒜埋,除非流水線上觸發(fā)了終止操作,否則中間操作不會(huì)執(zhí)行任何的處理最楷,而在終止操作時(shí)一次性處理整份,稱為“惰性求值”或者“延遲加載”。

2.1 篩選與切片

方法 描述
filter(Predicate p) 接收Lambda籽孙,從流中排除某些元素
distinct() 篩選皂林,通過(guò)流所生成元素的hashCode()和equal()去除重復(fù)元素
limit(long maxSize) 截?cái)嗔鳎蛊湓夭怀^(guò)給定數(shù)量
skip(long n) 跳過(guò)元素蚯撩,返回一個(gè)扔掉了前n個(gè)元素的流。若流中元素不足n個(gè)烛占,則返回一個(gè)空流胎挎。與limit(n)互補(bǔ)

下面我們一個(gè)個(gè)來(lái)分析:

2.1.1 filter(Predicate p)

接收Lambda,從流中排除某些元素

    @Test
    public void test02(){
        List<Employee> employees = Arrays.asList(
                new Employee("張三",68,9000),
                new Employee("李四",38,8000),
                new Employee("王五",50,4000),
                new Employee("趙六",18,3000),
                new Employee("田七",8,1000));
        //中間操作忆家,不會(huì)執(zhí)行任何操作
        //filter(Predicate p) 接收Lambda犹菇,從流中排除某些元素
        Stream<Employee> employeeStream = employees.stream().filter((e) -> {
            System.out.println("StreamAPI的中間操作");
            return e.getAge() > 35;
        });

        //終止操作  當(dāng)只有中間操作時(shí)執(zhí)行是沒(méi)有結(jié)果的,因?yàn)橹挥袌?zhí)行終止操作以后所有的中間操作才一次性全部處理芽卿,即惰性求值
        employeeStream.forEach(System.out::println);

    }

運(yùn)行結(jié)果

StreamAPI的中間操作
Employee{name='張三', age=68, salary=9000.0}
StreamAPI的中間操作
Employee{name='李四', age=38, salary=8000.0}
StreamAPI的中間操作
Employee{name='王五', age=50, salary=4000.0}
StreamAPI的中間操作
StreamAPI的中間操作

從運(yùn)行結(jié)果可以看出揭芍,迭代操作不是我們做的,是由Stream API 幫我們完成的卸例,再也不需要我們自己完成這個(gè)迭代操作了称杨,這也叫內(nèi)部迭代。與內(nèi)部迭代相對(duì)應(yīng)的是外部迭代筷转,也就是我們自己寫(xiě)的迭代姑原。

    //外部迭代,我們自己寫(xiě)的迭代
    @Test
    public void test03(){
        Iterator<Employee> iterator = employees.iterator();
        if(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
2.1.2 limit(long maxSize)

limit()呜舒,下面我們過(guò)濾公司中薪資大于5000的雇員之后锭汛,獲取其中前2個(gè)

    //limit()過(guò)濾公司中薪資大于5000的雇員之后,獲取其中前2個(gè)雇員的信息
    @Test
    public void test04(){
         employees.stream()
                .filter((e) -> {
                    System.out.println("===短路===");
                    return e.getSalary()>5000;
                })
                .limit(2)
                .forEach(System.out::println);
    }

運(yùn)行結(jié)果


運(yùn)行結(jié)果

從運(yùn)行結(jié)果我們看出,迭代操作只執(zhí)行了兩次唤殴,也就是說(shuō)只要找到滿足條件的結(jié)果之后般婆,就不再進(jìn)行迭代了,這個(gè)過(guò)程就叫“短路”朵逝。所以說(shuō)它也可以提高效率蔚袍,跟我們之前學(xué)的短路&&和||有點(diǎn)類似。

2.1.3 skip(long n)

扔掉也即跳過(guò)前幾個(gè)元素

    List<Employee> employees = Arrays.asList(
            new Employee("張三",68,9000),
            new Employee("李四",38,8000),
            new Employee("王五",50,4000),
            new Employee("趙六",18,3000),
            new Employee("田七",8,1000));
    //skip()過(guò)濾公司中薪資大于5000的雇員之后廉侧,跳過(guò)前2個(gè)雇員的信息
    @Test
    public void test05(){
        employees.stream()
                .filter((e) -> e.getSalary()>2000)
                .skip(2)
                .forEach(System.out::println);
    }

運(yùn)行結(jié)果页响,跳過(guò)了8000和9000的工資

Employee{name='王五', age=50, salary=4000.0}
Employee{name='趙六', age=18, salary=3000.0}
2.1.4 distinct()

我們首先給employees集合中添加幾個(gè)重復(fù)的元素趙六

    @Test
    public void test06(){
        List<Employee> employees1 = Arrays.asList(
                new Employee("張三",68,9000),
                new Employee("李四",38,8000),
                new Employee("王五",50,4000),
                new Employee("趙六",18,3000),
                new Employee("趙六",18,3000),
                new Employee("趙六",18,3000),
                new Employee("趙六",18,3000),
                new Employee("田七",8,1000));
        employees1.stream()
                .filter((e) -> e.getSalary()>2000)
                .distinct()
                .forEach(System.out::println);
    }

運(yùn)行結(jié)果


運(yùn)行結(jié)果

我們發(fā)現(xiàn)結(jié)果并沒(méi)有去重,因?yàn)槎翁埽峭ㄟ^(guò)流所生成元素的hashCode()和equals()來(lái)去除重復(fù)元素的闰蚕。所以,要想去除重復(fù)元素连舍,必須得重寫(xiě)Employee實(shí)體類中的hashCode()和equals()這兩個(gè)方法没陡。

重寫(xiě)后的Employee如下

package com.cqq.java8.lambda;

import java.util.Objects;

/**
 * @author caoqianqian
 * @Description:
 * @date 2021/3/6 9:24
 */
public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee() {
    }

    public Employee(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

我們?cè)龠\(yùn)行一下結(jié)果,已經(jīng)是去重了的索赏。


運(yùn)行結(jié)果

2.2 映射

方法 描述
map(Function f) 接收一個(gè)函數(shù)作為參數(shù)盼玄,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的元素
mapToDouble(ToDoubleFunction f) 接收一個(gè)函數(shù)作為參數(shù)潜腻,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上埃儿,并將其映射成一個(gè)新的DoubleStream
mapToInt(ToIntFunction f) 接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上融涣,并將其映射成一個(gè)新的IntStream
mapToLong(ToLongFunction f) 接收一個(gè)函數(shù)作為參數(shù)童番,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上,并將其映射成一個(gè)新的LongStream
flatMap(Function f) 接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流威鹿,然后把左右流連成一個(gè)流
2.2.1 map(Function f)

接收Lambda剃斧,將元素轉(zhuǎn)換成其他形式或提取信息。接收一個(gè)函數(shù)作為參數(shù)忽你,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上幼东,并將其映射成一個(gè)新的元素

    @Test
    public void test07(){
        //將字母轉(zhuǎn)大寫(xiě)
        List<String> list = Arrays.asList("aa","bb","cc");
        list.stream()
                .map(str -> str.toUpperCase())
                .forEach(System.out::println);
        //提取員工名字
        employees.stream()
                .map((e) -> e.getName())
                .forEach(System.out::println);
    }

運(yùn)行結(jié)果

AA
BB
CC
張三
李四
王五
趙六
田七
2.2.2 flatMap(Function f)

接收一個(gè)函數(shù)作為參數(shù),將流中的每個(gè)值都換成另一個(gè)流科雳,然后把所有流連成另一個(gè)流根蟹。

我們先來(lái)寫(xiě)一個(gè)方法來(lái)解析字符串,并把字符串中的一個(gè)一個(gè)的字符給單獨(dú)提取出來(lái)糟秘,放到集合中娜亿。

    public static Stream<Character> filterCharacter(String str){
        List<Character> list  = new ArrayList<>();
        for (Character c:str.toCharArray()) {
            list.add(c);
        }

        return list.stream();
    }

再來(lái)看這個(gè)測(cè)試?yán)?/p>

    //faltMap
    @Test
    public void test08(){
        //提取list里的一個(gè)個(gè)字符串的每一個(gè)字符
        List<String> list = Arrays.asList("aa","bb","cc");

        //通過(guò)map提取到的是一個(gè)stream流里還是stream流
        Stream<Stream<Character>> streamStream = list.stream()
                .map(TestStreamAPI::filterCharacter);
        //這里得嵌套遍歷才可以循環(huán)出每個(gè)字符串的結(jié)果
        streamStream.forEach(
                s -> s.forEach(System.out::println)
        );

        //通過(guò)flatMap提取到的是一個(gè)stream流
        Stream<Character> streamStream2 = list.stream()
                .flatMap(TestStreamAPI::filterCharacter);
        //這樣就不用了嵌套遍歷了
        streamStream2.forEach(System.out::println);

    }

其實(shí)map方法就相當(dāng)于Collaction的add方法,如果add的是個(gè)集合的話就會(huì)變成二維數(shù)組蚌堵,而flatMap 的話就相當(dāng)于Collaction的addAll方法买决,參數(shù)如果是集合的話沛婴,只是將2個(gè)集合合并,而不是變成二維數(shù)組督赤。

2.3 排序

方法 描述
sorted() 產(chǎn)生一個(gè)新流嘁灯,其中按自然順序排序
sorted(Comparator comp) 產(chǎn)生一個(gè)新流,其中按比較器順序排序

下面我們編寫(xiě)測(cè)試?yán)佣闵啵紫任覀兿葎?chuàng)建一個(gè)員工的集合:

 List<Employee> employees = Arrays.asList(
            new Employee("張三",68,9000),
            new Employee("李四",38,8000),
            new Employee("王五",50,4000),
            new Employee("趙六",18,3000),
            new Employee("田七",8,1000));

用sort() 自然排序?qū)崿F(xiàn)一串字符list的自然排序丑婿。用sorted(Comparator comp) 比較器排序,實(shí)現(xiàn)員工按年齡排序没卸,如果年齡相等按姓名排羹奉。

    //排序
    //sort() 自然排序
    //sorted(Comparator comp) 比較器排序
    @Test
    public void test09(){
        //sort() 將字母按自然醒順序排序
        List<String> list = Arrays.asList("bb","aa","cc");
        list.stream()
                .sorted()
                .forEach(System.out::println);

        //sorted(Comparator comp) 比較器排序 按年齡排序,如果年齡相等按姓名排
        employees.stream()
                .sorted((e1,e2) -> {
                    if(e1.getAge()== e2.getAge()){
                        return e1.getName().compareTo(e2.getName());
                    }else {
                        return Integer.compare(e1.getAge(),e2.getAge());
                    }
                })
                .forEach(System.out::println);
    }

運(yùn)行結(jié)果


aa
bb
cc
Employee{name='田七', age=8, salary=1000.0}
Employee{name='趙六', age=18, salary=3000.0}
Employee{name='李四', age=38, salary=8000.0}
Employee{name='王五', age=50, salary=4000.0}
Employee{name='張三', age=68, salary=9000.0}

3. 終止操作(終端操作)

一個(gè)終止操作约计,執(zhí)行中間操作鏈诀拭,并產(chǎn)生結(jié)果。
即終止操作會(huì)從流的流水線生成結(jié)果煤蚌,其結(jié)果可以是任何不是流的值耕挨,例如List、Integer尉桩、甚至是void筒占。

3.1 查找與匹配

方法 描述
allMatch(Predicate p) 檢查是否匹配所有元素
anyMatch(Predicate p) 檢查是否至少匹配一個(gè)元素
noneMatch(Predicate p) 檢查是否沒(méi)有匹配所有元素
findFirst() 返回第一個(gè)元素
findAny() 返回當(dāng)前流中的任意元素
count() 返回流中元素的總個(gè)數(shù)
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 內(nèi)部迭代(使用Collection接口需要用戶去做迭代,成為外部迭代蜘犁。相反翰苫,Stream API使用內(nèi)部迭代--它幫你把迭代做好了)
3.1.1 allMatch(Predicate p)檢查是否匹配所有元素

下面寫(xiě)一個(gè)例子,檢查公司中所有員工是否處于空閑狀態(tài)这橙。

    @Test
    public void test10(){
        List<Employee> employees1 = Arrays.asList(
                new Employee("張三",68,9000, Employee.Status.FREE),
                new Employee("李四",38,8000,Employee.Status.BUSY),
                new Employee("王五",50,4000,Employee.Status.VOCATION),
                new Employee("趙六",18,3000,Employee.Status.BUSY),
                new Employee("田七",8,1000,Employee.Status.FREE));
        boolean b = employees1.stream()
                .allMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println("===allMatch===="+b);

    }

Employee 的實(shí)體類中加個(gè)狀態(tài)和對(duì)應(yīng)添加這個(gè)參數(shù)的構(gòu)造方法

package com.cqq.java8.lambda;

import java.util.Objects;

public class Employee {
    private String name;
    private int age;
    private double salary;
    private Status status;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(String name, int age, double salary,Status status) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }

    public Employee() {
    }

    public Employee(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public enum Status{
        FREE,
        BUSY,
        VOCATION;
    }
}

3.1.2 anyMatch(Predicate p)檢查是否至少匹配一個(gè)元素
    //anyMatch
    @Test
    public void test11(){
        boolean c = employees1.stream()
                .anyMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println("===anyMatch===="+c);
    }
3.1.3 noneMatch(Predicate p)檢查是否沒(méi)有匹配所有元素
    //noneMatch
    @Test
    public void test12(){
        boolean d = employees1.stream()
                .noneMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println("===anyMatch===="+d);

    }
3.1.4 findFirst()返回第一個(gè)元素
    //findFirst
    @Test
    public void test13(){
        Optional<Employee> first = employees1.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .findFirst();
        System.out.println(first.get());
    }

注意:上面findFirst返回的是一個(gè)Optional對(duì)象革骨,它將我們的Employee封裝了一層,這是為了避免了空指針析恋。而且這個(gè)對(duì)象為我們提供了orElse方法,也就是當(dāng)我們拿到的這個(gè)對(duì)象為空的時(shí)候盛卡,我們可以傳入一個(gè)新的對(duì)象去代替它助隧。

3.1.5 findAny()返回當(dāng)前流中任意元素
    //findAny
    @Test
    public void test14(){
        Optional<Employee> first = employees1.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .findAny();
        System.out.println(first.get());
    }
3.1.6 count()返回流中的總個(gè)數(shù)
    //count
    @Test
    public void test15(){
         long count = employees1.stream()
                .count();
        System.out.println(count);
    }
3.1.7 max()返回流中最大的元素
    //max
    @Test
    public void test16(){
        //兩種方法
        Optional<Employee> max1 = employees1.stream()
                .max((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
        Optional<Employee> max2 = employees1.stream()
                .max(Comparator.comparingInt(Employee::getAge));
        System.out.println(max1);
    }
3.1.8 min()返回流中最小的元素
    //min
    @Test
    public void test17(){
        //兩種方法
        Optional<Employee> max1 = employees1.stream()
                .min((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
        Optional<Employee> max2 = employees1.stream()
                .min(Comparator.comparingInt(Employee::getAge));
        System.out.println(max1);
    }
3.1.9 forEach(Consumer c)

這個(gè)我們很熟悉了,每個(gè)測(cè)試?yán)佣佳h(huán)打印輸出

3.2 歸約

方法 描述
reduce(T identity,Binaryoperator b) 可以將流中元素反復(fù)結(jié)合在一起滑沧,得到一個(gè)值并村。返回T
reduce(Binaryoperator b) 可以將流中元素反復(fù)結(jié)合在一起,得到一個(gè)值滓技。返回Optional<T>
3.2.1 reduce(T identity,Binaryoperator b)
    //reduce()
    @Test
    public void test18(){
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        //首先哩牍,需要傳一個(gè)起始值,然后令漂,傳入的是一個(gè)二元運(yùn)算膝昆。
        Integer count = list.stream().reduce(0, (x, y) -> x + y);
        System.out.println(count);
    }

reduce 歸約丸边,把流中的元素按照(x, y) -> x + y的形式進(jìn)行累加操作。首先0是一個(gè)起始值荚孵,然后從流中取出第一個(gè)元素1作為y進(jìn)行累加即結(jié)果為1妹窖,再將這個(gè)結(jié)果1作為x,再?gòu)牧髦腥〕鲆粋€(gè)元素2作為y進(jìn)行累加收叶,結(jié)果為3骄呼,一次類推,反復(fù)結(jié)合判没,最終得到一個(gè)新值蜓萄。

3.2.2 reduce(Binaryoperator b)
    //reduce()
    @Test
    public void test19(){
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        //沒(méi)有起始值,則有可能結(jié)果為空澄峰,所以返回的值會(huì)被封裝到Optional中嫉沽。
        Optional<Integer> reduce = list.stream().reduce((x, y) -> x + y);
        //另一種寫(xiě)法
        Optional<Integer> reduce1 = list.stream().reduce(Integer::sum);

        System.out.println(reduce);
        System.out.println(reduce1);

    }

沒(méi)有起始值,則有可能結(jié)果為空摊阀,所以返回的值會(huì)被封裝到Optional中耻蛇。

備注:map和reduce的連接通常稱為map-reduce模式,因Google用它來(lái)進(jìn)行網(wǎng)絡(luò)搜索而出名胞此。

3.3 收集

方法 描述
collect(Collect c) 將流轉(zhuǎn)化為其他形式臣咖,接收一個(gè)Collector接口的實(shí)現(xiàn),用于給Stream中元素做匯總的方法

Collector接口中方法的實(shí)現(xiàn)漱牵,決定了如何對(duì)流執(zhí)行收集操作(如收集到List夺蛇、Set及Map中),而且Collectors實(shí)用類中提供了許多靜態(tài)方法酣胀,可以方便的創(chuàng)建常見(jiàn)的收集器實(shí)例刁赦。Collectors實(shí)用類中常用的靜態(tài)方法如下表所示:

方法 返回類型 作用
toList() List<T> 把流中的元素收集到List集合中
toSet() Set<T> 把流中的元素收集到Set集合中
toCollection(...) Collection<T> 把流中的元素收集到創(chuàng)建好的集合中
counting() Long 計(jì)算流中元素個(gè)數(shù)
averagingDouble(...) Double 計(jì)算流中元素Double類型的平均值
summingDouble(...) Double 計(jì)算流中元素Double類型的總和
maxBy(...) Optional<T> 根據(jù)比較器選擇最大值
minBy(...) Optional<T> 根據(jù)比較器選擇最小值
groupingBy(...) Map<K,List<T>> 根據(jù)某屬性的值對(duì)流分組,屬性為K闻镶,結(jié)果為V
partitioningBy(...) Map<Boolean,List<T>> 根據(jù)True或false進(jìn)行分區(qū)
summarizingBy(...) DoubleSummaryStatistics 收集流(流中元素為Double類型)的統(tǒng)計(jì)值甚脉。如:平均值
joining(...) String 連接流中每個(gè)字符串
3.3.1 toList() 將流轉(zhuǎn)換成List
    //toList()
    @Test
    public void test20(){
        List<Integer> list = employees.stream().map(Employee::getAge).collect(Collectors.toList());
        list.forEach(System.out::println);
    }
3.3.2 toSet() 將流轉(zhuǎn)換成Set
    //toSet()
    @Test
    public void test21(){
        Set<Integer> collect = employees.stream().map(Employee::getAge).collect(Collectors.toSet());
        collect.forEach(System.out::println);
    }
3.3.3 toCollection() 將流轉(zhuǎn)換成其他類型的集合

將流轉(zhuǎn)換成HashSet

    //toCollection()
    @Test
    public void test22(){
        //換成HashSet
        HashSet<Integer> collect = employees.stream().map(Employee::getAge).collect(Collectors.toCollection(HashSet::new));
        collect.forEach(System.out::println);
    }
3.3.4 counting()計(jì)算流中元素個(gè)數(shù)
    //counting()
    @Test
    public void test23(){
        //總數(shù)
        Long collect = employees.stream().collect(Collectors.counting());
        System.out.println(collect);
    }
3.3.5 averagingDouble(...)計(jì)算流中元素Double類型的平均值
    //averagingDouble()
    @Test
    public void test24(){
        //平均值
        Double collect1 = employees.stream().collect(Collectors.averagingDouble(Employee::getAge));
        System.out.println(collect1);
    }
3.3.6 summingDouble(...)計(jì)算流中元素Double類型的總和

IntSummaryStatistics里包含{count=, sum=, min=, average=, max=}這幾項(xiàng)需要哪個(gè)值,通過(guò)get獲取就行

summarizingInt

    //summingDouble()
    @Test
    public void test25(){
        //平均值
        DoubleSummaryStatistics collect1 = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
        IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
        System.out.println(collect);
        System.out.println(collect.getSum());
    }

再來(lái)看下summarizingInt()

    //summarizingInt()
    @Test
    public void test26(){
        //平均值  IntSummaryStatistics里包含{count=5, sum=182, min=8, average=36.400000, max=68}這幾項(xiàng)需要哪個(gè)獲取就行
        IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
        System.out.println(collect);
        //從IntSummaryStatistics里取sum
        System.out.println(collect.getSum());
    }

執(zhí)行結(jié)果

IntSummaryStatistics{count=5, sum=182, min=8, average=36.400000, max=68}
182

Collectors.summingLong()同上一樣铆农,傳輸傳入Long型即可牺氨。

3.3.7 minBy(...)根據(jù)比較器選擇最小值
    //minBy()
    @Test
    public void test28(){
        Optional<Employee> min = employees.stream().collect(Collectors.minBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
        System.out.println(min.get());
    }
3.3.8 maxBy(...)根據(jù)比較器選擇最小值
    //maxBy()
    @Test
    public void test27(){
        Optional<Employee> max = employees.stream().collect(Collectors.maxBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
        System.out.println(max.get());
        //等價(jià)于這種形式
        Optional<Employee> max1 = employees.stream().max((x, y) -> Integer.compare(x.getAge(), y.getAge()));
        System.out.println(max1.get());

    }
3.3.9 groupingBy(...)根據(jù)某屬性的值對(duì)流分組,屬性為K墩剖,結(jié)果為V

分組分為一級(jí)分組和多級(jí)分組猴凹,下面例子分別進(jìn)行了演示:

一級(jí)分組的例子


 //groupingBy()一級(jí)分組
    @Test
    public void test29(){
        Map<Employee.Status, List<Employee>> collect = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(collect);
    }

運(yùn)行結(jié)果

{
BUSY=[Employee{name='張三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}, Employee{name='趙六', age=18, salary=3000.0}],
VOCATION=[Employee{name='王五', age=50, salary=4000.0}],
FREE=[Employee{name='田七', age=8, salary=1000.0}]
}

多級(jí)分組的例子


    //多級(jí)分組
    @Test
    public void test30(){
       Map<Employee.Status,Map<String,List<Employee>>> map =  employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus,Collectors.groupingBy(e -> {
                    if(e.getAge()<35){
                        return "青年";
                    }else if(e.getAge()<50){
                        return "中年";
                    }else {
                        return "老年";
                    }
                })));
        System.out.println(map);
    }

運(yùn)行結(jié)果

{
FREE={青年=[Employee{name='田七', age=8, salary=1000.0}]},
BUSY={青年=[Employee{name='趙六', age=18, salary=3000.0}], 老年=[Employee{name='張三', age=68, salary=9000.0}], 中年=[Employee{name='李四', age=38, salary=8000.0}]},
VOCATION={老年=[Employee{name='王五', age=50, salary=4000.0}]}
}

3.3.10 partitioningBy(...)根據(jù)True或false進(jìn)行分區(qū)
    //partitioningBy()
    @Test
    public void test31(){
        Map<Boolean, List<Employee>> map = employees.stream()
                .collect(Collectors.partitioningBy(e -> e.getSalary() > 5000));
        System.out.println(map);
    }

運(yùn)行結(jié)果

{
false=[Employee{name='王五', age=50, salary=4000.0}, Employee{name='趙六', age=18, salary=3000.0}, Employee{name='田七', age=8, salary=1000.0}],
true=[Employee{name='張三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}]
}

3.3.12 joining(...)連接流中每個(gè)字符串
    //joining()
    @Test
    public void test32(){
        String collect = employees.stream().map(Employee::getName)
                .collect(Collectors.joining(","));
        System.out.println(collect);
    }

運(yùn)行結(jié)果

張三,李四,王五,趙六,田七

至此,我們Stream的基本操作差不多就結(jié)束了岭皂,我們只需要多加練習(xí)郊霎,熟練使用就能提高效率啦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爷绘,一起剝皮案震驚了整個(gè)濱河市书劝,隨后出現(xiàn)的幾起案子进倍,更是在濱河造成了極大的恐慌,老刑警劉巖庄撮,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件背捌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡洞斯,警方通過(guò)查閱死者的電腦和手機(jī)毡庆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)烙如,“玉大人么抗,你說(shuō)我怎么就攤上這事⊙翘” “怎么了蝇刀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)徘溢。 經(jīng)常有香客問(wèn)我吞琐,道長(zhǎng),這世上最難降的妖魔是什么然爆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任站粟,我火速辦了婚禮,結(jié)果婚禮上曾雕,老公的妹妹穿的比我還像新娘奴烙。我一直安慰自己,他們只是感情好剖张,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布切诀。 她就那樣靜靜地躺著,像睡著了一般搔弄。 火紅的嫁衣襯著肌膚如雪幅虑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天顾犹,我揣著相機(jī)與錄音倒庵,去河邊找鬼。 笑死蹦渣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的貌亭。 我是一名探鬼主播柬唯,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼圃庭!你這毒婦竟也來(lái)了锄奢?” 一聲冷哼從身側(cè)響起失晴,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拘央,沒(méi)想到半個(gè)月后涂屁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灰伟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拆又,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栏账。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帖族,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挡爵,到底是詐尸還是另有隱情竖般,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布茶鹃,位于F島的核電站涣雕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏闭翩。R本人自食惡果不足惜挣郭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望男杈。 院中可真熱鬧丈屹,春花似錦、人聲如沸伶棒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肤无。三九已至先蒋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宛渐,已是汗流浹背竞漾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窥翩,地道東北人业岁。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寇蚊,于是被迫代替她去往敵國(guó)和親笔时。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容