天天看點

Java8新特性 - Stream流Stream流

Stream流

Stream的三個操作步驟

  • 建立Stream
  • 中間操作
  • 終止操作(終端操作)

建立Stream

建立stream的四種方式

  • 通過Collection系列集合提供的

    stream()

    方法或

    parallelStream()

List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();
		Stream<String> stream2 = list.parallelStream();
           
  • 通過靜态方法擷取一個數組流
Employee[] employees = new Employee[10];
        Stream<Employee> stream2 = Arrays.stream(employees);
           
  • 通過Stream類靜态類中的of方法
  • 建立無限流:兩種(疊代和生成)
// 4.1疊代 : 傳一個起始值和一個一進制運算
        Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
        /// 中間操作  終止操作
        stream4.limit(10).forEach(System.out::println);

        // 4.2生成(無限生成随機數)
        Stream.generate(()->Math.random()).forEach(System.out::println);
           

中間操作

中間操作:不會執行任何操作,終止操作:一次性執行全部内容,即“惰性求值”

1. 篩選與切片

方法 描述
filter 接收Lambda,從流中排除某些元素
limit 截斷流,使其元素不超過給定數量
skip(n) 跳過元素,傳回一個扔掉了前n個元素的流,若流中元素不足n個,則傳回一個空流,與limit(n)互補
distinct 篩選,通過流所生成元素的hashCode() 和 equals() 去除重複元素

例子:

  • Employee 對象
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode()
public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;
    private Status status;

    public enum Status {
        BUSY,FREE,VOCATION;
    }

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

1.1 filter:過濾

List<Employee> employees = Arrays.asList(
            new Employee(1, "張三", 18, 1111.11),
            new Employee(2, "李四", 49, 2222.22),
            new Employee(3, "趙六", 57, 3333.33),
            new Employee(4, "田七", 35, 4444.44),
            new Employee(4, "田七", 35, 4444.44)
    );	

    /**
     * 中間操作:不會執行任何操作,終止操作:一次性執行全部内容,即“惰性求值”
     * 1.篩選與切片:filter
     */
	@Test
    public void test1() {
        // 内部疊代:疊代操作由Stream API完成
        employees.stream()
                .filter(e -> e.getAge() > 35)
                .forEach(System.out::println);

        // 外部疊代
        Iterator<Employee> it = employees.iterator();

        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
           

1.2 limit:截取

package cn.luis.stream.intermediate;

import cn.luis.stream.Employee;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class LimitTest {

    List<Employee> employees = Arrays.asList(
            new Employee(1, "張三", 18, 1111.11),
            new Employee(3, "趙六", 57, 3333.33),
            new Employee(4, "田七", 35, 4444.44),
            new Employee(2, "李四", 49, 2222.22),
            new Employee(5, "田七", 35, 4444.44)
    );

    /**
     * 中間操作:不會執行任何操作,終止操作:一次性執行全部内容,即“惰性求值”
     * 2.篩選與切片:limit
     */
    @Test
    public void test2() {
        // limit;短路,找到兩個符合的就終止了
        employees.stream()
                .filter(e -> {
                    //System.out.println("不符合條件的或者直接抛棄了!" + e);
                    return e.getAge() > 20;
                })
                .limit(2)
                .forEach(System.out::println);
    }

}
           

1.3 skip:跳過

package cn.luis.stream.intermediate.shanxuanqiepian;

import cn.luis.stream.common.Employee;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

public class SkipTest {

    List<Employee> employees = Arrays.asList(
            new Employee(1, "張三", 18, 1111.11),
            new Employee(2, "李四", 49, 2222.22),
            new Employee(3, "趙六", 57, 3333.33),
            new Employee(4, "田七", 35, 4444.44),
            new Employee(5, "田七", 35, 4444.44)
    );

    /**
     * 中間操作:不會執行任何操作,終止操作:一次性執行全部内容,即“惰性求值”
     * 1.篩選與切片:skip
     */
    @Test
    public void test3() {
        // limit;跳過前兩個
        employees.stream()
                .filter(e -> e.getAge() > 18)
                .skip(2)
                .forEach(System.out::println);
    }
}
           

1.4 distinct:去重

package cn.luis.stream.intermediate.shanxuanqiepian;

import cn.luis.stream.common.Employee;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

public class DistinctTest {

    List<Employee> employees = Arrays.asList(
            new Employee(1, "張三", 18, 1111.11),
            new Employee(2, "李四", 49, 2222.22),
            new Employee(3, "趙六", 57, 3333.33),
            new Employee(4, "田七", 35, 4444.44),
            new Employee(5, "田七", 35, 4444.44)
    );

    /**
     * 篩選與切片:distinct : 篩選,通過流所生成元素的hashCode() 和 equals() 去除重複元素
     */
    @Test
    public void test4() {
        // distinct;去重 【employees要重寫hashcode和equals】
        employees.stream()
                .filter(e -> {
                    return e.getAge() > 25;
                })
                .distinct()
                .forEach(System.out::println);
    }

}
           

2. 映射

map裡傳入Function函數型接口,傳入一個參數傳回一個值
方法 描述
map 接收lambda,将元素轉換成其他形式或提取資訊,接收一個函數作為參數,該函數會被應用到每個元素上,并将其映射成一個新的元素。
flatMap 接收一個函數作為參數,将該流中的每一個值都換成另一個流,然後把所有流連成一個流

例子:

package cn.luis.stream.intermediate.yingshe;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class MapTest {

    List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");

    /**
     * map
     */
    @Test
    public void test1() {
        list.stream()
                .map(s -> s.toUpperCase())
                .forEach(System.out::println);

        System.out.println("--------------------------");


        // 這種套娃式可以用flatMap替代 (見test2)
        Stream<Stream<Character>> stream = list.stream().map(MapTest::filterCharacter);
        stream.forEach(sm -> {
            sm.forEach(System.out::println);
        });
    }

    /**
     * flatMap: 扁平化map
     */
    @Test
    public void test2() {
        // 扁平化map:原來把流放入map流,現在是直接将流中的元素放入flatmap流中
        // 把{a,a,a},{b,b,b} ... 轉換成了 {a,a,a,b,b,b,c,c,c}
        Stream<Character> characterStream = list.stream()
                .flatMap(MapTest::filterCharacter);

        characterStream.forEach(System.out::println);
    }

    /**
     * 将字元串拆分
     * @param str
     * @return
     */
    public static Stream<Character> filterCharacter(String str) {
        List<Character> list = new ArrayList<>();
        for (Character ch : str.toCharArray()) {
            list.add(ch);
        }
        return list.stream();
    }
    
}

           

3. 排序

方法 描述
sorted() 自然排序(Comparable)
sorted(Comparator com) 定制排序(Comparator)

例子:

package cn.luis.stream.intermediate.sort;

import cn.luis.stream.common.Employee;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class SortedTest {

    List<Employee> employees = Arrays.asList(
            new Employee(1, "張三", 18, 1111.11),
            new Employee(2, "李四", 49, 2222.22),
            new Employee(3, "趙六", 57, 3333.33),
            new Employee(4, "田七", 35, 4444.44),
            new Employee(5, "田七", 35, 4444.44)
    );

    /**
     * 3. 排序
     * sorted() -- 自然排序(Comparable)
     * sorted(Comparator) -- 定制排序(Comparator)
     */
    @Test
    public void test1() {

        // 自然排序ba
        Stream<Employee> sorted = employees.stream().sorted();

        // 按年齡排序,年齡相同按名字排序
        employees.stream()
                .sorted((e1, e2) -> {
                    if (e1.getAge() == e2.getAge()) {
                        return e1.getName().compareTo(e2.getName());
                    } else {
                        Integer x = e1.getAge();
                        return x.compareTo(e2.getAge());
                    }
                }).forEach(System.out::println);
    }

}
           

終止操作

1. 查找與比對

方法 描述
allMatch 檢查是否比對所有元素
anyMatch 檢查是否至少比對一個元素
noneMatch 檢查是否沒有比對所有元素
findFirst 傳回第一個元素
findAny 傳回目前流中的任意元素
count 傳回流中元素的總個數
max 傳回流中的最大值
min 傳回流中的最小值

例子:

  • 查找的對象
List<Employee> employees = Arrays.asList(
            new Employee(1, "張三", 18, 1111.11, Status.BUSY),
            new Employee(2, "李四", 49, 2222.22, Status.FREE),
            new Employee(3, "趙六", 57, 3333.33, Status.BUSY),
            new Employee(4, "田七", 35, 4444.44, Status.VOCATION),
            new Employee(2, "李十四", 49, 2222.22, Status.FREE),
            new Employee(2, "李十四", 49, 2222.22, Status.FREE),
            new Employee(2, "李十四", 49, 2222.22, Status.FREE)
    );
           

1.1 match:比對

@Test
    public void matchTest() {

        // 檢查是否比對所有元素
        boolean b = employees.stream()
                .allMatch(e -> Employee.Status.BUSY.equals(e.getStatus()));
        System.out.println(b);

        // 檢查是否至少比對一個元素
        boolean b2 = employees.stream()
                .anyMatch(e -> Employee.Status.BUSY.equals(e.getStatus()));
        System.out.println(b2);

        // 檢查是否沒有比對所有元素
        boolean b3 = employees.stream()
                .noneMatch(e -> Employee.Status.BUSY.equals(e.getStatus()));
        System.out.println(b3);
    }
           

1.2 find:查找

@Test
    public void findTest() {

        // 傳回第一個元素
        Optional<Employee> first = employees.stream()
                .sorted(Comparator.comparingDouble(Employee::getSalary))
                .findFirst();
        System.out.println(first.get());

        // 傳回目前流中的任意元素
        Optional<Employee> any = employees.stream()
                .filter(e -> Employee.Status.FREE.equals(e.getStatus()))
                .findAny();
        System.out.println(any.get());

        // 【并行】 傳回目前流中的任意元素
        Optional<Employee> any2 = employees.parallelStream()
                .filter(e -> Employee.Status.FREE.equals(e.getStatus()))
                .findAny();
        System.out.println(any2.get());
    }
           

1.3 最值

@Test
    public void numTest() {

        // 傳回流中元素的總個數
        long count = employees.stream()
                .count();
        System.out.println(count);

        // 傳回流中的最大值
        Optional<Employee> max = employees.stream()
                .max(Comparator.comparingDouble(Employee::getAge));
        System.out.println(max.get());

        // 傳回流中的最小值
        Optional<Double> min = employees.stream()
                .map(Employee::getSalary)
                .min(Double::compare);
        System.out.println(min.get());
    }
           

2. 規約

方法 描述
reduce 可以将流中的元素反複結合起來,得到一個值
  • 準備資料
public class EmpData {

    public static List<Employee> findAllEmployee() {
        return Arrays.asList(
                new Employee(1, "張三", 18, 1111.11, Employee.Status.BUSY),
                new Employee(2, "李四", 49, 2222.22, Employee.Status.FREE),
                new Employee(3, "趙六", 57, 3333.33, Employee.Status.BUSY),
                new Employee(4, "田七", 35, 4444.44, Employee.Status.VOCATION),
                new Employee(2, "李十四", 49, 2222.22, Employee.Status.FREE),
                new Employee(2, "李十四", 49, 2222.22, Employee.Status.FREE),
                new Employee(2, "李十四", 49, 2222.22, Employee.Status.FREE)
        );
    }
}
           
  • 例子:
/**
 * 規約
 */
public class ReduceTest {

    List<Employee> employees = EmpData.findAllEmployee();

    /**
     * reduce(起始值,二進制運算): 規約
     */
    @Test
    public void test3() {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        Integer a = list.stream()
                .reduce(0, (x, y) -> x + y);
        System.out.println(a);

        Integer b = list.stream()
                .reduce(0, Integer::sum);
        System.out.println(b);

        // 優化寫法(map-reduce模式)
        Optional<Integer> c = list.stream()
                .reduce(Integer::sum);
        System.out.println(c.get());

        // 優化寫法(map-reduce模式) + orElse
        Integer d = list.stream()
                .reduce(Integer::sum).orElse(0);
        System.out.println(d);
    }

}
           

3. 收集

包括集合、計算、分組、分區
方法 描述
collect 将流轉換為其他形式,接收一個Collectot接口的實作,用于給Stream中的元素彙總的方法

3.1 集合

  • 集合操作
方法 描述
Collectors.toList() 轉成 List
Collectors.toSet() 轉成 Set
Collectors.toCollection(HashSet::new) 放到其他類型集合
@Test
    public void test4() {
        List<String> nameList = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());

        nameList.forEach(System.out::println);

        System.out.println("-----------過濾重複資料--------------");
        Set<String> nameSet = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());

        nameSet.forEach(System.out::println);

        System.out.println("-----------放到其他類型集合--------------");
        HashSet<String> nameHashSet = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));

        nameHashSet.forEach(System.out::println);
    }
           

3.2 計算

  • 計算
方法 描述
Collectors.counting() 總數
Collectors.averagingDouble() 平均值
Collectors.maxBy() 最大值
Collectors.minBy() 最小值
Collectors.summingDouble() 總和
Collectors.summarizingDouble() 得到的對象可以用來繼續計算(getAverage(),getMax(),getCount())
@Test
    public void test5() {
        // 總數
        Long collect = employees.stream()
                .collect(Collectors.counting());
        System.out.println(collect);

        // 平均值
        Double avg = employees.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(avg);

        // 最大值
        Optional<Double> max = employees.stream()
                .map(Employee::getSalary)
                .collect(Collectors.maxBy(Double::compare));
        System.out.println(max.get());

        // 最小值
        Optional<Double> min = employees.stream()
                .map(Employee::getSalary)
                .collect(Collectors.minBy(Double::compare));
        System.out.println(min.get());

        // 總和
        Double sum = employees.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(sum);

        // Double:強大計算
        DoubleSummaryStatistics salary = employees.stream()
                .collect(Collectors.summarizingDouble(Employee::getSalary));
        System.out.println(salary.getAverage());
        System.out.println(salary.getMax());
        System.out.println(salary.getCount());
    }
           

3.3 分組、分區

  • 分區、分組
方法 描述
Collectors.groupingBy() 分組
Collectors.partitioningBy() 分區
  • 分組
List<Employee> employees = EmpData.findAllEmployee();

    /**
     * collect 之 分組
     */
    @Test
    public void groupTest1() {
        // 分組
        Map<Employee.Status, List<Employee>> statusListMap = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        statusListMap.keySet().forEach(System.out::println);
        statusListMap.entrySet().forEach(System.out::println);
        System.out.println(statusListMap);

    }
           
  • 結果:
VOCATION
BUSY
FREE
VOCATION=[Employee(id=4, name=田七, age=35, salary=4444.44, status=VOCATION)]
BUSY=[Employee(id=1, name=張三, age=18, salary=1111.11, status=BUSY), Employee(id=3, name=趙六, age=57, salary=3333.33, status=BUSY)]
FREE=[Employee(id=2, name=李四, age=49, salary=2222.22, status=FREE), Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE), Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE), Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)]
{VOCATION=[Employee(id=4, name=田七, age=35, salary=4444.44, status=VOCATION)], BUSY=[Employee(id=1, name=張三, age=18, salary=1111.11, status=BUSY), Employee(id=3, name=趙六, age=57, salary=3333.33, status=BUSY)], FREE=[Employee(id=2, name=李四, age=49, salary=2222.22, status=FREE), Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE), Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE), Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)]}
           
  • 多級分組
List<Employee> employees = EmpData.findAllEmployee();
    @Test
    public void groupTest2() {
        // 多級分組
        Map<String, List<Employee>> emMap = employees.stream()
                .collect(Collectors.groupingBy((employee) -> {
                    if (employee.getAge() <= 35) {
                        return "青年";
                    } else if (employee.getAge() > 20) {
                        return "老年";
                    } else {
                        return "少年";
                    }
                }));

        // 檢視分組(第一種方式)
        emMap.keySet().forEach(key -> {
            System.out.println("======" + key + "======");
            emMap.get(key).forEach(System.out::println);
        });

        // 檢視分組(第二種方式)
        emMap.forEach((k, v) -> {
            System.out.println("======" + k + "======");
            v.forEach(map -> {
                System.out.println(map);
            });
        });
    }

           
  • 結果:
======青年======
Employee(id=1, name=張三, age=18, salary=1111.11, status=BUSY)
Employee(id=4, name=田七, age=35, salary=4444.44, status=VOCATION)
======老年======
Employee(id=2, name=李四, age=49, salary=2222.22, status=FREE)
Employee(id=3, name=趙六, age=57, salary=3333.33, status=BUSY)
Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)
Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)
Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)

           
  • 分區
/**
     * collect 之 分區
     */
    @Test
    public void area1() {

        // 分區: 滿足條件的分一個區,不滿足的分一個區
        Map<Boolean, List<Employee>> emMap = employees.stream()
                .collect(Collectors.partitioningBy(e -> e.getSalary() > 2000.0));

        // 檢視分組(第二種方式)
        emMap.forEach((k, v) -> {
            System.out.println("======" + k + "======");
            v.forEach(map -> {
                System.out.println(map);
            });
        });
    }
           
  • 結果
======false======
Employee(id=1, name=張三, age=18, salary=1111.11, status=BUSY)
======true======
Employee(id=2, name=李四, age=49, salary=2222.22, status=FREE)
Employee(id=3, name=趙六, age=57, salary=3333.33, status=BUSY)
Employee(id=4, name=田七, age=35, salary=4444.44, status=VOCATION)
Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)
Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)
Employee(id=2, name=李十四, age=49, salary=2222.22, status=FREE)
           

3.4 字元串操作

  • 字元串操作
方法 描述
Collectors.joining(",") 字元串拼接
@Test
    public void str() {
        // 字元串拼接
        String str = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(","));
        System.out.println(str);
    }
           
  • 結果
張三,李四,趙六,田七,李十四,李十四,李十四
           

繼續閱讀