Java8StreamAPI簡單指南
首先不應將Java 8 Streams與Java I / O流混淆(例如:FileInputStream 等); 這些彼此之間沒什麼關係。簡而言之,流Stream是資料來源周圍的包裝器,允許我們使用該資料來源進行操作,並使批量處理方便快捷。
流不儲存資料,從這個意義上講,它不是資料結構。它也從不修改底層資料來源。
新功能 - ofollow,noindex">java.util.stream - 支援對元素流的函式樣式操作,例如集合上的map-reduce轉換。
現在讓我們深入研究流建立和使用的幾個簡單示例 :
流建立
我們首先從現有陣列中獲取流:
private static Employee[] arrayOfEmps = { new Employee(1, "Jeff Bezos", 100000.0), new Employee(2, "Bill Gates", 200000.0), new Employee(3, "Mark Zuckerberg", 300000.0) }; Stream.of(arrayOfEmps);
從一個List可獲得流,注意List需要指定泛型型別:
private static List<Employee> empList = Arrays.asList(arrayOfEmps); empList.stream();
我們可以使用Stream.of() 從各個物件建立一個流:
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);
或簡單使用Stream.builder() :
Stream.Builder<Employee> empStreamBuilder = Stream.builder(); empStreamBuilder.accept(arrayOfEmps[0]); empStreamBuilder.accept(arrayOfEmps[1]); empStreamBuilder.accept(arrayOfEmps[2]); Stream<Employee> empStream = empStreamBuilder.build();
流操作
現在讓我們看看我們可以執行的一些常見用法和操作,並藉助語言中的新流支援。
forEach
forEach() 是最簡單和最常見的操作; 它遍歷流元素,在每個元素上呼叫提供的函式。
該方法非常常見,直接在Iterable,Map等中引入:
empList.stream().forEach(e -> e.salaryIncrement(10.0)); assertThat(empList, contains( hasProperty("salary", equalTo(110000.0)), hasProperty("salary", equalTo(220000.0)), hasProperty("salary", equalTo(330000.0)) ));
這將有效地呼叫salaryIncrement() 中的每個元素empList 。
forEach() 是一個終端操作,這意味著在執行操作之後,流管道被認為已被佔用,並且無法再使用。我們將在下一節中詳細討論終端操作。
map
map() 在將函式應用於原始流的每個元素後生成新流。新流可以是不同型別的。
以下示例將Integer 的流轉換為Employee 的流:
Integer[] empIds = { 1, 2, 3 }; List<Employee> employees = Stream.of(empIds) .map(employeeRepository::findById) .collect(Collectors.toList()); assertEquals(employees.size(), empIds.length);
在這裡,我們從陣列中獲取員工ID 的整數 流。每個Integer 都傳遞給函式employeeRepository :: findById() - 它返回相應的Employee 物件; 這有效地形成了一個Employee 流。
collect
我們在上一個例子中看到了collect()的 工作原理; 這是我們完成所有處理後將一些東西從流中取出的常用方法之一:
List<Employee> employees = empList.stream().collect(Collectors.toList()); assertEquals(empList, employees);
collect() 對Stream 例項中儲存的資料元素執行可變摺疊操作(將元素重新打包到某些資料結構並應用一些額外的邏輯,連線它們等)。
此操作的策略通過Collector 介面實現提供。在上面的示例中,我們使用toList 收集器將所有Stream 元素收集到List 例項中。
filter
接下來,我們來看看filter() ; 這會生成一個新流,其中包含通過指定測試(由Predicate指定返回True/false)的原始流的元素。
我們來看看它是如何工作的:
Integer[] empIds = { 1, 2, 3, 4 }; List<Employee> employees = Stream.of(empIds) .map(employeeRepository::findById) .filter(e -> e != null) .filter(e -> e.getSalary() > 200000) .collect(Collectors.toList()); assertEquals(Arrays.asList(arrayOfEmps[2]), employees);
在上面的示例中,我們首先過濾掉無效員工ID的空 引用,然後再次應用過濾器,以僅保留員工的工資超過特定閾值。
FindFirst
findFirst() 為流中的第一個條目返回一個Optional ; Optional 可以是空的:
Integer[] empIds = { 1, 2, 3, 4 }; Employee employee = Stream.of(empIds) .map(employeeRepository::findById) .filter(e -> e != null) .filter(e -> e.getSalary() > 100000) .findFirst() .orElse(null); assertEquals(employee.getSalary(), new Double(200000));
在這裡,返回薪水大於100000的第一名員工。如果不存在此類員工,則返回null 。
toArray
我們看到了我們如何使用collect() 從流中獲取資料。如果我們需要從流中獲取陣列,我們可以簡單地使用toArray() :
Employee[] employees = empList.stream().toArray(Employee[]::new);
flatMap
流可以儲存複雜的資料結構,如Stream <List <String >> 。在這種情況下,flatMap() 幫助我們扁平化資料結構以簡化進一步的操作:
List<List<String>> namesNested = Arrays.asList( Arrays.asList("Jeff", "Bezos"), Arrays.asList("Bill", "Gates"), Arrays.asList("Mark", "Zuckerberg")); List<String> namesFlatStream = namesNested.stream() .flatMap(Collection::stream) .collect(Collectors.toList()); assertEquals(namesFlatStream.size(), namesNested.size() * 2);
請注意我們如何使用flatMap() API 將Stream <List <String >> 轉換為更簡單的Stream <String> 。
peek
我們在本節前面看到了forEach() ,這是一個終端操作。但是,有時我們需要在應用任何終端操作之前對流的每個元素執行多個操作。
peek() 在這種情況下很有用。簡單地說,它對流的每個元素執行指定的操作,並返回一個可以進一步使用的新流。 peek() 是一箇中間操作 :
Employee[] arrayOfEmps = { new Employee(1, "Jeff Bezos", 100000.0), new Employee(2, "Bill Gates", 200000.0), new Employee(3, "Mark Zuckerberg", 300000.0) }; List<Employee> empList = Arrays.asList(arrayOfEmps); empList.stream() .peek(e -> e.salaryIncrement(10.0)) .peek(System.out::println) .collect(Collectors.toList()); assertThat(empList, contains( hasProperty("salary", equalTo(110000.0)), hasProperty("salary", equalTo(220000.0)), hasProperty("salary", equalTo(330000.0)) ));
這裡,第一個peek() 用於增加每個員工的工資。第二個peek() 用於列印員工。最後,collect() 用作終端操作。