一、為什麼引入Stream流
流是一系列與特定儲存機制無關的元素——實際上,流並沒有“儲存”之說。使用流,無需迭代集合中的元素,就可以從管道提取和操作元素。這些管道通常被組合在一起,形成一系列對流進行操作的管道。
在大多數情況下,將物件儲存在集合中是為了處理他們,因此你將會發現你將程式設計的主要焦點從集合轉移到了流上,流的一個核心的好處是,它使得程式更加短小並且更易理解。當Lambda表示式和方法引用和流一起使用的時候會讓人感覺自成一體。
二、如何使用Stream流
流操作的型別有三種:建立流,修改流元素(中間操作 Intermediate Operations),消費流元素(終端操作 Terminal Operations)
建立Stream流
-
使用
Arrays.stream()
方法建立Integer[] arr = new Integer[]{1,2,3,4,5}; Arrays.stream(arr).filter(num -> num > 3);
-
使用
Stream.of ()
方法建立Integer[] arr = new Integer[]{1,2,3,4,5}; Stream.of(arr).filter(num -> num > 3);
檢視
of()
的原始碼中得知,該方法也是呼叫了Arrays.stream()
方法實現的/** * Returns a sequential ordered stream whose elements are the specified values. * * @param <T> the type of stream elements * @param values the elements of the new stream * @return the new stream */ @SafeVarargs @SuppressWarnings("varargs") // Creating a stream from an array is safe public static<T> Stream<T> of(T... values) { return Arrays.stream(values); }
-
使用
Collection.stream()
方法建立List<String> list = new ArrayList<>(1); list.stream().forEach(str -> System.out.println(str));
-
使用
Stream.iterate()
方法建立Stream.iterate(1, num -> num + 2).limit(10).forEach(num -> System.out.println(num));
-
使用
Stream.generate()
方法建立Stream.generate(() -> Arrays.asList(arr)).limit(1).forEach(num -> System.out.println(num));
修改流元素(中間操作 Intermediate Operations)
中間操作用於從一個流中獲取物件,並將物件作為另一個流從後端輸出,以連線到其他操作。
1、跟蹤和除錯
peek()
操作的目的是幫助除錯,允許你無修改地檢視流中的元素
// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}
輸出結果:
Well WELL well it IT it s S s so SO so
因為 peek()
符合無返回值的 Consumer 函式式介面,所以我們只能觀察,無法使用不同的元素來替換流中的物件。
2、流元素排序
sorted()
方法是需要遍歷整個流的,並在產生任何元素之前對它進行排序。因為有可能排序後集合的第一個元素會在未排序集合的最後一位。
@Test
public void sortedTest() {
List<Integer> numList = Lists.newArrayList();
numList.add(8);
numList.add(2);
numList.add(6);
numList.add(9);
numList.add(1);
List<Integer> sortList = numList.stream().sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println(sortList);
}
輸出結果:
[1, 2, 6, 8, 9]
3、移除元素
-
distinct()
可用於消除流中的重複元素。相比建立一個 Set 集合,該方法的工作量要少得多。@Test public void distinctTest() { Stream.of(6, 8, 9, 6, 2, 8).distinct().forEach(i -> System.out.print(i + ", ")); }
輸出結果:
6, 8, 9, 2,
-
filter(Predicate)
:若元素傳遞給過濾函式產生的結果為true
,則過濾操作保留這些元素。@Test public void filterTest() { Stream.of(6, 9, 2, 8).filter(num -> num > 5).sorted().forEach(i -> System.out.print(i + ", ")); }
輸出結果:
6, 8, 9,
4、對映,應用函式到元素
-
map(Function)
:將函式操作應用在輸入流的元素中,對一個流中的值進行某種形式的轉換,並將返回值傳遞到輸出流中@Test public void mapTest() { Stream.of("abc", "qw", "mnkh").map(String::length).forEach(n -> System.out.format("%d ", n)); }
輸出結果:
3 2 4
-
mapToInt(ToIntFunction)
:操作同上,但結果是IntStream
Stream.of("5", "7", "9").mapToInt(Integer::parseInt).forEach(n -> System.out.format("%d ", n));
-
mapToLong(ToLongFunction)
:操作同上,但結果是LongStream
Stream.of("17", "19", "23").mapToLong(Long::parseLong).forEach(n -> System.out.format("%d ", n));
-
mapToDouble(ToDoubleFunction)
:操作同上,但結果是DoubleStream
Stream.of("17", "1.9", ".23").mapToDouble(Double::parseDouble).forEach(n -> System.out.format("%f ", n));
-
flatMap()
做了兩件事:將產生流的函式應用在每個元素上(與map()
所做的相同),然後將每個流都扁平化為元素,因而最終產生的僅僅是元素。List<Integer> listA = Lists.newArrayList(); listA.add(1); listA.add(6); List<Integer> listB = Lists.newArrayList(); listB.add(10); listB.add(2); Map<String, List<Integer>> abMap = Maps.newHashMap(); abMap.put("A", listA); abMap.put("B", listB); // 需獲取A和B集合中大於5的元素 abMap.values().stream().flatMap(num -> num.stream().filter(n -> n > 5)).collect(Collectors.toList()) .forEach(System.out::println);
輸出結果:
6 10
-
flatMapToInt(Function)
:當Function
產生IntStream
時使用。 -
flatMapToLong(Function)
:當Function
產生LongStream
時使用。 -
flatMapToDouble(Function)
:當Function
產生DoubleStream
時使用。
5、集合流切片,可實現分頁
-
limit(n)
方法會返回一個包含n個元素的新的流(若總長小於n則返回原始流)。 -
skip(n)
方法正好相反,它會丟棄掉前面的n個元素。// 查詢第二頁的資料 Integer pageNumber = 2; Integer pageSize = 10; Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).skip((pageNumber - 1) * pageSize).limit(pageSize) .forEach(System.out::println);
輸出結果:
11 12
消費流元素(終端操作 Terminal Operations)
終端操作總是我們在流管道中所做的最後一件事,該操作將會獲取流的最終結果。
1、陣列結果輸出
toArray()
:將流轉換成適當型別的陣列。toArray(generator)
:在特殊情況下,生成自定義型別的陣列。
2、迴圈結果輸出
-
forEach(Consumer)
常見如System.out::println
作為 Consumer 函式。 -
forEachOrdered(Consumer)
: 保證forEach
在並行流處理時按照原始流順序操作。Arrays.stream(new Random(45).ints(0, 1000).limit(100).toArray()).limit(10).parallel().forEachOrdered(n -> System.out.format("%d ", n));
3、collect收集結果
-
collect(Collector)
:使用 Collector 收集流元素到結果集合中。 -
collect(Supplier, BiConsumer, BiConsumer)
:同上,第一個引數 Supplier 建立了一個新的結果集合,第二個引數 BiConsumer 將下一個元素收集到結果集合中,第三個引數 BiConsumer 用於將兩個結果集合合併起來。Collectorts
類為我們提供了常用的收集類的各個工廠方法:
-
將一個流收集到一個List中可以這樣用
Lists.newArrayList().stream().collect(Collectors.toList());
-
收集到Set中可以這樣用
Lists.newArrayList().stream().collect(Collectors.toSet());
-
收集到Map中可以這樣用
Lists.newArrayList(new User("Johnson", "重慶")).stream().collect(Collectors.toMap(User::getName, User::getAddress));
-
收集到Set時,控制Set的型別可以這樣用
Lists.newArrayList().stream().collect(Collectors.toCollection(TreeSet::new));
-
將字流中的字串連線並收集起來
Lists.newArrayList().stream().collect(Collectors.joining(","));
-
各種聚合操作
// 獲取流中的總和,平均值,最大值,最小值,一次性收集流中的結果 List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5); listA.stream().collect(Collectors.summingInt(Integer::intValue)); listA.stream().collect(Collectors.averagingInt(Integer::intValue)); listA.stream().collect(Collectors.maxBy(Integer::compareTo)); listA.stream().collect(Collectors.minBy(Integer::compareTo)); listA.stream().collect(Collectors.summarizingInt(Integer::intValue)); // 分組分片,返回結果:{"重慶渝北":[{"address":"重慶渝北","name":"Johnson"},{"address":"重慶渝北","name":"Jack"}],"重慶江北":[{"address":"重慶江北","name":"Tom"}]} List<User> listB = Lists.newArrayList(new User("Johnson", "重慶渝北"), new User("Tom", "重慶江北"), new User("Jack", "重慶渝北")); System.out.println(JSON.toJSONString(listB.stream().collect(Collectors.groupingBy(User::getAddress))));
4、組合流中元素
-
reduce(BinaryOperator)
:使用 BinaryOperator 來組合所有流中的元素。因為流可能為空,其返回值為 Optional// 結果為15 System.out.println(Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y).get());
-
reduce(identity, BinaryOperator)
:功能同上,但是使用 identity 作為其組合的初始值。因此如果流為空,identity 就是結果// 設定初始值為10則結果為25 System.out.println(Stream.of(1, 2, 3, 4, 5).reduce(10, (x, y) -> x + y)); // 集合流為空,則結果預設為初始值a List<String> list = Lists.newArrayList(); System.out.println(list.stream().reduce("a", (x, y) -> x.length() > 1 ? x : y));
-
reduce(identity, BiFunction, BinaryOperator)
:在序列流(stream)中,該方法跟第二個方法一樣,即第三個引數不會起作用。在並行流中,我們知道流被fork join出多個執行緒進行執行,此時每個執行緒的執行流程就跟第二個方法reduce(identity,BiFunction)
一樣,而第三個引數BinaryOperator
函式,則是將每個執行緒的執行結果當成一個新的流,然後使用第一個方法reduce(BinaryOperator)
流程進行規約// 第三個引數在並行流中起效,將每個執行緒的執行結果當成一個新的流 List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5); // 序列流執行結果:15 System.out.println(listA.stream().reduce(0, (x, y) -> x + y, (i, j) -> i * j)); // 並行流執行結果:120 System.out.println(listA.parallelStream().reduce(0, (x, y) -> x + y, (i, j) -> i * j));
5、流中元素匹配
-
allMatch(Predicate)
:如果流的每個元素提供給 Predicate 都返回 true ,結果返回為 true。在第一個 false 時,則停止執行計算。// 陣列中第一個元素小於2,則停止匹配返回結果:flase System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 2)); // 陣列中所有元素都大於0,則停止匹配返回結果:true System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 0));
-
anyMatch(Predicate)
:如果流的任意一個元素提供給 Predicate 返回 true ,結果返回為 true。在第一個 true 是停止執行計算。// 陣列中第三個元素大於2,則停止匹配返回結果:true System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(n -> n > 2));
-
noneMatch(Predicate)
:如果流的每個元素提供給 Predicate 都返回 false 時,結果返回為 true。在第一個 true 時停止執行計算。// 陣列中第三個元素大於2,則停止匹配返回結果:true System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(n -> n > 2));
6、流中元素查詢
-
findFirst()
:返回第一個流元素的 Optional,如果流為空返回 Optional.empty。// 根據條件過濾後取第一個元素 System.out.println(Stream.of(1, 2, 3, 4, 5).filter(n -> n > 2).findFirst().get());
-
findAny()
:返回含有任意流元素的 Optional,如果流為空返回 Optional.empty。// 根據條件過濾後找到任何一個所匹配的元素,就返回,此方法在對流並行執行時效率高 System.out.println(Stream.of(1, 2, 3, 4, 5).parallel().filter(n -> n > 2).findAny().get());
7、收集流資訊
-
count()
:流中的元素個數。 -
max(Comparator)
:根據所傳入的 Comparator 所決定的“最大”元素。 -
min(Comparator)
:根據所傳入的 Comparator 所決定的“最小”元素。 -
average()
:求取流元素平均值。 -
sum()
:對所有流元素進行求和。 -
summaryStatistics()
:生成有關此流元素的各種摘要資料。// 獲取流中元素數量,返回結果:5 System.out.println(Stream.of(1,2,3,4,5).count()); // 獲取流中最大值,返回結果:5 System.out.println(Stream.of(1,2,3,4,5).max(Integer::compareTo).get()); // 獲取流中最小值,返回結果:1 System.out.println(Stream.of(1,2,3,4,5).min(Integer::compareTo).get()); // 獲取流中元素平均值,返回結果:3.0 System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).average().getAsDouble()); // 獲取流中各種摘要資料,返回結果:IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5} System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).summaryStatistics()); // 獲取流中元素總和,返回結果:15 System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).sum());
三、總結
流式操作改變並極大地提升了 Java 語言的可程式設計性,並可能極大地阻止了 Java 程式設計人員向諸如 Scala 這種函式式語言的流轉。