自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java8 Stream流API使用簡介

開發(fā) 開發(fā)工具
流API是一套功能強大但易于理解的工具,用于處理元素序列。如果使用得當,它可以減少大量代碼,創(chuàng)建更可讀的程序,并提高應用程序的生產(chǎn)力。在應用程序中,不要讓實例化的流未被使用,避免導致內存泄漏。

概述

本文介紹Java8 Streams從創(chuàng)建到并行執(zhí)行的實際使用例子,涉及 Java8(lambda表達式、Optional、方法引用)和流API的基本知識。

流創(chuàng)建

有很多方法可以創(chuàng)建不同源的流實例。一旦創(chuàng)建,實例將不會修改其源,因此允許從單個源創(chuàng)建多個實例。

  • 空流
Stream<String> streamEmpty = Stream.empty();

常在創(chuàng)建時使用empty方法,以避免對沒有元素的流返回null:

public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}
  • 集合流

可以創(chuàng)建任何類型的集合(集合、列表、數(shù)組)的流:

Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();

Stream<String> streamOfArray = Stream.of("a", "b", "c");

還可以從現(xiàn)有數(shù)組或數(shù)組的一部分創(chuàng)建流:

String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
  • Stream.builder()

當使用builder時,應該在語句的右側部分額外指定所需的類型,否則build方法將創(chuàng)建Stream<Object>的實例:

Stream<String> streamBuilder =
  Stream.<String>builder().add("a").add("b").add("c").build();
  • Stream.generate()

generate方法接受Supplier<T>來生成元素。由于生成的流是無限的,開發(fā)人員應該指定所需的大?。?/p>

Stream<String> streamGenerated =
  Stream.generate(() -> "element").limit(10);
  • Stream.iterate()
Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);

結果流的第一個元素是iterate方法的第一個參數(shù)。創(chuàng)建之后的每個元素時,指定的函數(shù)將應用于前一個元素。在上面的例子中,第二個元素將是42。

Primitives基元流

Java8提供了從三種基本類型創(chuàng)建流的可能性:int、long和double。由于Stream<T>是一個泛型接口,并且無法將基元用作泛型的類型參數(shù),因此創(chuàng)建了三個新的特殊接口:IntStream、LongStream和DoubleStream。

使用該接口可以減少不必要的自動裝箱,從而提高效率:

IntStream intStream = IntStream.range(1, 3);
LongStream longStream = LongStream.rangeClosed(1, 3);

range(int startInclusive,int endExclusive)方法創(chuàng)建從第一個參數(shù)到第二個參數(shù)的有序流。它以等于1的步長遞增后續(xù)元素的值。結果不包括最后一個參數(shù),它只是序列的一個上界。

rangeClosed(int startInclusive,int endInclusive)方法執(zhí)行相同的操作,但只有一個區(qū)別,即包括第二個元素。我們可以使用這兩種方法來生成三種類型的基元流中的任何一種。

自Java 8以來,Random類提供了一系列用于生成基元流的方法。例如,以下代碼創(chuàng)建了一個DoubleStream,它有三個元素:

Random random = new Random();
DoubleStream doubleStream = random.doubles(3);
  • 字符串流

在String類的chars()方法的幫助下,我們還可以使用String作為創(chuàng)建流的源。由于JDK中沒有CharStream的接口,因此我們使用IntStream來表示字符流。

IntStream streamOfChars = "abc".chars();

以下示例根據(jù)指定的RegEx將字符串分解為子字符串:

Stream<String> streamOfString =
  Pattern.compile(", ").splitAsStream("a, b, c");
  • 文件流

此外,Java NIO類Files允許我們通過line()方法生成文本文件的Stream<String>。文本的每一行都成為流的一個元素:

Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = 
  Files.lines(path, Charset.forName("UTF-8"));

引用流

記住Java 8流是不能重用的,這一點非常重要。

Stream<String> stream = 
  Stream.of("a", "b", "c").filter(element -> element.contains("b"));
Optional<String> anyElement = stream.findAny();
Optional<String> firstElement = stream.findFirst();

嘗試重用相同的引用將觸發(fā)IllegalStateException:這種行為是合乎邏輯的。流的設計是為了以函數(shù)樣式將有限的操作序列應用于元素源,而不是存儲元素。

因此,為了使以前的代碼正常工作,應該進行一些更改:

List<String> elements =
  Stream.of("a", "b", "c").filter(element -> element.contains("b"))
    .collect(Collectors.toList());
Optional<String> anyElement = elements.stream().findAny();
Optional<String> firstElement = elements.stream().findFirst();

懶調用

使用流的正確和最方便的方法是通過流管道,它是流源、中間操作和終端操作的鏈:

List<String> list = Arrays.asList("abc1", "abc2", "abc3");
long size = list.stream().skip(1)
  .map(element -> element.substring(0, 3)).sorted().count();

中間操作是惰性的。這意味著只有在終端操作執(zhí)行需要時才會調用它們。

例如,讓我們調用方法wasCalled(),它每次調用時都會增加一個內部計數(shù)器:

private long counter;
 
private void wasCalled() {
    counter++;
}

現(xiàn)在,讓我們從操作filter()中調用方法wasCalled():

List<String> list = Arrays.asList(“abc1”, “abc2”, “abc3”);
counter = 0;
Stream<String> stream = list.stream().filter(element -> {
    wasCalled();
    return element.contains("2");
});

由于我們有三個元素的源,可以假設filter()方法將被調用三次,計數(shù)器變量的值將為3。然而,運行此代碼根本不會更改計數(shù)器,它仍然為零,因此filter()方法甚至沒有被調用過一次,缺少終端操作的原因。

讓我們通過添加map()操作和終端操作findFirst()來稍微重寫一下這段代碼。我們還將在日志記錄的幫助下添加跟蹤方法調用順序的功能:

Optional<String> stream = list.stream().filter(element -> {
    log.info("filter() was called");
    return element.contains("2");
}).map(element -> {
    log.info("map() was called");
    return element.toUpperCase();
}).findFirst();

生成的日志顯示,我們調用了filter()方法兩次,調用了map()方法一次。這是因為管道是垂直執(zhí)行的。

在示例中,流的第一個元素不滿足過濾器的謂詞。然后,調用了第二個元素的filter()方法,通過管道進入map()方法,findFirst()操作只滿足一個元素,因此調用結束返回。

因此,在這個特定的例子中,惰性調用使我們能夠避免兩個方法調用,一個用于filter(),另一個用于map()。

執(zhí)行順序

從性能的角度來看,正確的順序是流管道中鏈操作最重要的方面之一:

long size = list.stream().map(element -> {
    wasCalled();
    return element.substring(0, 3);
}).skip(2).count();

執(zhí)行此代碼將使計數(shù)器的值增加3,這意味著我們調用了流的map()方法三次,但返回的值是1。因此,生成的流只有一個元素,而無緣無故地執(zhí)行了三次中的兩次昂貴的map()操作。

如果我們改變skip()和map()方法的順序,計數(shù)器將只增加一個。因此,我們將只調用map()方法一次:

long size = list.stream().skip(2).map(element -> {
    wasCalled();
    return element.substring(0, 3);
}).count();

這就引出了以下規(guī)則:減少流大小的中間操作應該放在應用于每個元素的操作之前。因此,我們需要將skip()、filter()和distinct()等方法保留在流管道的頂部。

reduce()流聚合

流API默認提供了一些流聚合的操作:count()、max(),min()和sum(),如果需要自定義聚合,可以使用reduce()和collect()。

reduce具有以下參數(shù):

  • identity:累加器的初始值,如果流為空并且沒有任何可累加的內容,則為默認值;
  • accumulator累加器:一個指定元素聚合邏輯的函數(shù)。由于累加器為每一個步驟創(chuàng)建一個新值,所以新值的數(shù)量等于流的大小,只有最后一個值是有用的。
  • combiner組合器:一個聚合累加器結果的函數(shù)。只在并行模式下調用組合器。

現(xiàn)在,讓我們看看這三種方法的作用:

OptionalInt reduced =
  IntStream.range(1, 4).reduce((a, b) -> a + b);

reduced = 6 (1 + 2 + 3)

int reducedTwoParams =
  IntStream.range(1, 4).reduce(10, (a, b) -> a + b);

reducedTwoParams = 16 (10 + 1 + 2 + 3)

int reducedParams = Stream.of(1, 2, 3)
  .reduce(10, (a, b) -> a + b, (a, b) -> {
     log.info("combiner was called");
     return a + b;
  });

結果將與前面的示例(16)相同,這意味著沒有調用合并器。要使組合器工作,流應該是并行的:

int reducedParallel = Arrays.asList(1, 2, 3).parallelStream()
    .reduce(10, (a, b) -> a + b, (a, b) -> {
       log.info("combiner was called");
       return a + b;
    });

結果:36,組合器被調用了兩次:流的每個元素添加到累加器運行三次,并且是并行進行的。因此,它們具有(10+1=11;10+2=12;10+3=13;)。現(xiàn)在組合器可以合并這三個結果。它需要兩次迭代(12+13=25;25+11=36)。

collect()收集器

流API已經(jīng)為大多數(shù)常見操作創(chuàng)建了預定義的收集器。

List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
  new Product(14, "orange"), new Product(13, "lemon"),
  new Product(23, "bread"), new Product(13, "sugar"));

將流轉換為集合:

List<String> collectorCollection = 
  productList.stream().map(Product::getName).collect(Collectors.toList());

還原為字符串:

String listToString = productList.stream().map(Product::getName)
  .collect(Collectors.joining(", ", "[", "]"));

joiner()方法可以有1到3個參數(shù)(分隔符、前綴、后綴)。

處理流中所有數(shù)字元素的平均值:

int summingPrice = productList.stream()
  .collect(Collectors.summingInt(Product::getPrice));

方法averagingXX()、summingXX()和summaryzingXX()可以處理基元(int、long、double)及其包裝類(Integer、long、double)。這些方法的一個更強大的功能是提供映射。因此,開發(fā)人員不需要在collect()方法之前使用額外的map()操作。

IntSummaryStatistics statistics = productList.stream()
  .collect(Collectors.summarizingInt(Product::getPrice));

結果將是一個與此“IntSummaryStatistics{count=5,sum=86,min=13,average=17,max=23}”相同的字符串

根據(jù)指定的函數(shù)對流的元素進行分組:

Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
  .collect(Collectors.groupingBy(Product::getPrice));

根據(jù)一些謂詞將流的元素分組:

Map<Boolean, List<Product>> mapPartioned = productList.stream()
  .collect(Collectors.partitioningBy(element -> element.getPrice() > 15));

推進收集器時可執(zhí)行附加轉換:

Set<Product> unmodifiableSet = productList.stream()
  .collect(Collectors.collectingAndThen(Collectors.toSet(),
  Collections::unmodifiableSet));

如果出于某種原因應該創(chuàng)建自定義收集器,那么最簡單的方法是使用收集器類型的of()方法。

Collector<Product, ?, LinkedList<Product>> toLinkedList =
  Collector.of(LinkedList::new, LinkedList::add, 
    (first, second) -> { 
       first.addAll(second); 
       return first; 
    });

LinkedList<Product> linkedListOfPersons =
  productList.stream().collect(toLinkedList);

并行流

Java 8引入了一種以函數(shù)風格實現(xiàn)并行的方法。API允許我們創(chuàng)建并行流,以并行模式執(zhí)行操作。當流的源是Collection或數(shù)組時,可以借助parallelStream()方法實現(xiàn):

Stream<Product> streamOfCollection = productList.parallelStream();
boolean isParallel = streamOfCollection.isParallel();
boolean bigPrice = streamOfCollection
  .map(product -> product.getPrice() * 12)
  .anyMatch(price -> price > 200);

如果流的源不是Collection或數(shù)組,則應使用parallel()方法:

IntStream intStreamParallel = IntStream.range(1, 150).parallel();
boolean isParallel = intStreamParallel.isParallel();

在后臺,Stream API自動使用ForkJoin框架并行執(zhí)行操作。默認情況下,將使用公共線程池。

在并行模式下使用流時,請避免阻塞操作。當任務需要類似的執(zhí)行時間時,最好使用并行模式。如果一項任務的持續(xù)時間比另一項長得多,則可能會減慢整個應用程序的工作流程。

并行模式下的流可以使用sequencial()方法轉換回順序模式:

IntStream intStreamSequential = intStreamParallel.sequential();
boolean isParallel = intStreamSequential.isParallel();

結論

流API是一套功能強大但易于理解的工具,用于處理元素序列。如果使用得當,它可以減少大量代碼,創(chuàng)建更可讀的程序,并提高應用程序的生產(chǎn)力。在應用程序中,不要讓實例化的流未被使用,避免導致內存泄漏。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2015-08-28 09:43:49

Java 8新特性處理集合

2023-03-15 17:37:26

Java8ListMap

2014-04-15 09:40:04

Java8stream

2020-05-25 16:25:17

Java8Stream函數(shù)式接口

2023-07-26 00:20:20

Java 8數(shù)組方式

2023-07-24 08:20:11

StreamJava方式

2014-07-16 16:42:41

Java8streamreduce

2019-06-27 10:32:57

Java開發(fā)代碼

2021-10-25 08:25:52

java8Stream api后端開發(fā)

2014-12-22 10:14:31

Java8

2019-11-18 14:45:13

代碼開發(fā)工具

2024-10-09 08:42:03

2022-12-09 07:48:10

Java8Stream表達式

2022-12-30 09:24:23

Java8Stream操作

2017-10-31 20:45:07

JavaJava8Optional

2023-04-07 08:46:41

Stream流map()Java8

2023-01-10 08:27:35

Java8APIJava

2022-04-14 15:12:40

Java8Stream列表

2020-12-01 07:18:35

Java8日期時間

2015-09-30 09:34:09

java8字母序列
點贊
收藏

51CTO技術棧公眾號