天天看點

Java 8 Stream API學習總結

Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理資料。Stream API可以極大提高Java程式員的生産力,讓程式員寫出高效率、幹淨、簡潔的代碼。這種風格将要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到前面處理的結果。

這一次為什麼要系統性的總結一下

Java 8 Stream API

呢?說得簡單點,我們先不論性能,我們就是為了

裝x

,而且要讓這個

x

裝得再優秀一些,僅此而已!

Stream基礎知識

流程

建立流

流的中間操作

流的最終操作

建立流

我們需要把哪些元素放入流中,常見的api有:

// 使用List建立流
list.stream()

// 使用一個或多個元素建立流
Stream.of(T value)
Stream.of(T... values)

// 使用數組建立流
Arrays.stream(T[] array)

// 建立一個空流
Stream.empty()

// 兩個流合并
Stream.concat(Stream<? extends T> a, Stream<? extends T> b)

// 無序無限流
Stream.generate(Supplier<T> s)

// 通過疊代産生無限流
Stream.iterate(final T seed, final UnaryOperator<T> f)           

流的中間操作

// 元素過濾
filter
limit
skip
distinct

// 映射
map
flatmap

// 排序           

流的最終操作

通過流對元素的最終操作,我們想得到一個什麼樣的結果

構造測試資料

員工實體類

/**
 * 員工實體類
 * @author Erwin Feng
 * @since 2020/4/27 2:10
 */
public class Employee {

    /** 員工ID */
    private Integer id;

    /** 員工姓名 */
    private String name;

    /** 員工薪資 */
    private Double salary;
    
    /** 構造方法、getter and setter、toString */
}           

測試資料清單

[
    {
        "id":1,
        "name":"Jacob",
        "salary":1000
    },
    {
        "id":2,
        "name":"Sophia",
        "salary":2000
    },
    {
        "id":3,
        "name":"Rose",
        "salary":3000
    },
    {
        "id":4,
        "name":"Lily",
        "salary":4000
    },
    {
        "id":5,
        "name":"Daisy",
        "salary":5000
    },
    {
        "id":6,
        "name":"Jane",
        "salary":5000
    },
    {
        "id":7,
        "name":"Jasmine",
        "salary":6000
    },
    {
        "id":8,
        "name":"Jack",
        "salary":6000
    },
    {
        "id":9,
        "name":"Poppy",
        "salary":7000
    }
]           

Stream API Test

filter 過濾

需求:查找薪酬為5000的員工清單

List<Employee> employees = list.stream().filter(employee -> employee.getSalary() == 5000)
        .peek(System.out::println)
        .collect(Collectors.toList());
Assert.assertEquals(2, employees.size());           

map 映射

需求:将薪酬大于5000的員工放到Leader對象中

List<Leader> leaders = list.stream().filter(employee -> employee.getSalary() > 5000).map(employee -> {
    Leader leader = new Leader();
    leader.setName(employee.getName());
    leader.setSalary(employee.getSalary());
    return leader;
}).peek(System.out::println).collect(Collectors.toList());
Assert.assertEquals(3, leaders.size());           

flatMap 水準映射

需求:将多元的清單轉化為單維的清單

說明:

我們将薪酬在1000-3000的分為一個清單,4000-5000分為一個清單,6000-7000分為一個清單。

将這三個清單組合在一起形成一個多元清單。

List<Employee> employees = multidimensionalList.stream().flatMap(Collection::stream).collect(Collectors.toList());
Assert.assertEquals(9, employees.size());           

sorted 排序

需求:根據薪酬排序

// 薪酬從小到大排序
List<Employee> employees = list.stream().sorted(Comparator.comparing(Employee::getSalary)).peek(System.out::println).collect(Collectors.toList());

// 薪酬從大到小排序
List<Employee> employees2 = list.stream().sorted(Comparator.comparing(Employee::getSalary).reversed()).peek(System.out::println).collect(Collectors.toList());           

min 最小值

double minValue = list.stream().mapToDouble(Employee::getSalary).min().orElse(0);
Assert.assertEquals(1000, minValue, 0.0);

Employee employee = list.stream().min(Comparator.comparing(Employee::getSalary)).orElse(null);
assert employee != null;
Assert.assertEquals(employee.getSalary(), minValue, 0.0);           

max 最大值

double maxValue = list.stream().mapToDouble(Employee::getSalary).max().orElse(0);
Assert.assertEquals(7000, maxValue, 0.0);           

average 平均值

double sum = list.stream().mapToDouble(Employee::getSalary).sum();
double averageValue = list.stream().mapToDouble(Employee::getSalary).average().orElse(0);
Assert.assertEquals(sum / list.size(), averageValue, 0.0);           

match 比對

// allMatch 集合中的元素都要滿足條件才會傳回true
// 薪酬都是大于等于1000的
boolean isAllMatch = list.stream().allMatch(employee -> employee.getSalary() >= 1000);
Assert.assertTrue(isAllMatch);

// anyMatch 集合中隻要有一個元素滿足條件就會傳回true
// 有沒有薪酬大于等于7000
boolean isAnyMatch = list.stream().anyMatch(employee -> employee.getSalary() >= 7000);
Assert.assertTrue(isAnyMatch);

// noneMatch 集合中沒有元素滿足條件才會傳回true
// 沒有薪酬小于1000的
boolean isNoneMatch = list.stream().noneMatch(employee -> employee.getSalary() < 1000);
Assert.assertTrue(isNoneMatch);           

distinct 去重

預設的

distinct()

不接收參數,是根據

Object#equals(Object)

去重。根據API介紹,這是一個有中間狀态的操作。

List<Employee> employees = list.stream().distinct().collect(Collectors.toList());
Assert.assertEquals(9, employees.size());           

如果我們要根據對象中的某個屬性去重的,可以使用

StreamEx

// 使用StreamEx去重
List<Employee> employees2 = StreamEx.of(list).distinct(Employee::getSalary).collect(Collectors.toList());
Assert.assertEquals(7, employees2.size());           

當然也可以使用JDK Stream API

private static <T>Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Map<Object, Boolean> result = new ConcurrentHashMap<>();
    return t -> result.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

List<Employee> employees3 = list.stream().filter(distinctByKey(Employee::getSalary)).collect(Collectors.toList());
Assert.assertEquals(7, employees3.size());           

reduce 裁減

需求:計算薪酬總和

// 先将員工清單轉換為薪酬清單
// 再計算薪酬總和
double salarySum = list.stream().map(Employee::getSalary).reduce(Double::sum).orElse(0.0);
double sum = list.stream().mapToDouble(Employee::getSalary).sum();
Assert.assertEquals(salarySum, sum, 0.0);           

另外,我們也可以設定一個累加函數的辨別值

double salarySum5 = list.stream().map(Employee::getSalary).reduce(1.00, Double::sum);
Assert.assertEquals(salarySum5, sum + 1, 0.0);           

collector 流的終止結果

// joining 拼接字元串
String employeeNames = list.stream().map(Employee::getName).collect(Collectors.joining(", "));
System.out.println(employeeNames); // Jacob, Sophia, Rose, Lily, Daisy, Jane, Jasmine, Jack, Poppy

// 傳回一個List
List<String> employeeNameList = list.stream().map(Employee::getName).collect(Collectors.toList());
System.out.println(employeeNameList);

// 傳回一個Set
Set<String> employeeNameSet = list.stream().map(Employee::getName).collect(Collectors.toSet());
System.out.println(employeeNameSet);

// 傳回一個Vector
Vector<String> employeeNameVector = list.stream().map(Employee::getName).collect(Collectors.toCollection(Vector::new));
System.out.println(employeeNameVector);

// 傳回一個Map
Map<Integer, String> employeesMap = list.stream().collect(Collectors.toMap(Employee::getId, Employee::getName));
System.out.println(employeesMap);           

count 統計

需求:薪酬為5000的員工數

不使用流

int count2 = 0;
for (Employee employee : list) {
    if (employee.getSalary() == 5000) {
        count2++;
    }
}
System.out.println(count2);           

使用流

long count3 = list.stream().filter(employee -> employee.getSalary() == 5000).count();
Assert.assertEquals(count3, count2);           

summarizingDouble 統計分析

DoubleSummaryStatistics employeeSalaryStatistics = list.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("employee salary statistics:" + employeeSalaryStatistics);

DoubleSummaryStatistics employeeSalaryStatistics2 = list.stream().mapToDouble(Employee::getSalary).summaryStatistics();
System.out.println("employee salary statistics2:" + employeeSalaryStatistics2);           
{count=9, sum=39000.000000, min=1000.000000, average=4333.333333, max=7000.000000}

partitioningBy 分區

分成滿足條件(true)和不滿足條件(false)兩個區

需求:找出薪酬大于5000的員工

Map<Boolean, List<Employee>> map = list.stream().collect(Collectors.partitioningBy(employee -> employee.getSalary() > 5000));
System.out.println("true:" + map.get(Boolean.TRUE));
System.out.println("false:" + map.get(Boolean.FALSE));           
true:[Employee{id=7, name='Jasmine', salary=6000.0}, Employee{id=8, name='Jack', salary=6000.0}, Employee{id=9, name='Poppy', salary=7000.0}]
false:[Employee{id=1, name='Jacob', salary=1000.0}, Employee{id=2, name='Sophia', salary=2000.0}, Employee{id=3, name='Rose', salary=3000.0}, Employee{id=4, name='Lily', salary=4000.0}, Employee{id=5, name='Daisy', salary=5000.0}, Employee{id=6, name='Jane', salary=5000.0}]

groupingBy 分組

需求:根據員工薪酬分組

Map<Double, List<Employee>> map = list.stream().collect(Collectors.groupingBy(Employee::getSalary));
System.out.println(map);           

再舉一個例子:薪酬 一> 總和(薪酬*員工數)

Map<Double, Double> map3 = list.stream().collect(Collectors.groupingBy(Employee::getSalary, Collectors.summingDouble(Employee::getSalary)));
System.out.println(map3);           

parallel 平行計算

簡單的說,就是啟動多個線程計算

private static void cal(Employee employee) {
    try {
        long sleepTime = employee.getSalary().longValue();
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        logger.info("employee name: {}", employee.getName());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

list.stream().parallel().forEach(StreamTest::cal);           
2020-05-15 01:47:14.231 [ForkJoinPool.commonPool-worker-4] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jacob
2020-05-15 01:47:15.226 [ForkJoinPool.commonPool-worker-2] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Sophia
2020-05-15 01:47:16.226 [ForkJoinPool.commonPool-worker-1] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Rose
2020-05-15 01:47:17.226 [ForkJoinPool.commonPool-worker-3] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Lily
2020-05-15 01:47:18.225 [main] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jane
2020-05-15 01:47:18.228 [ForkJoinPool.commonPool-worker-7] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Daisy
2020-05-15 01:47:19.226 [ForkJoinPool.commonPool-worker-5] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jack
2020-05-15 01:47:19.228 [ForkJoinPool.commonPool-worker-6] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Jasmine
2020-05-15 01:47:21.234 [ForkJoinPool.commonPool-worker-4] INFO  com.fengwenyi.study_stream.StreamTest - employee name: Poppy           

file 檔案操作

try (PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(Paths.get(tempFilePath)))) { // 使用 try 自動關閉流
    list.forEach(printWriter::println);
    list.forEach(employee -> printWriter.println(employee.getName())); // 将員工的姓名寫到檔案中
}

// 從檔案中讀取員工的姓名
List<String> s = Files.lines(Paths.get(tempFilePath)).peek(System.out::println).collect(Collectors.toList());           

測試代碼

Study Java 8 Stream API

學習連結