JAVA Streams-深入教程

java8和streams:天作之合面氓,但可能有點勢不可擋羔味。

Stream的添加是Java 8中的主要新功能之一河咽。文章是對Stream支持的許多功能的介紹,重點是簡單赋元,實際的示例忘蟹。

要理解這些需要對Java8(lambda表達式飒房、方法引用)有基本的工作知識。

介紹

首先媚值,不應(yīng)將Java 8流與Java I / O流(例如:FileInputStream等)混淆狠毯;它們之間幾乎沒有關(guān)系。

簡而言之褥芒,流是數(shù)據(jù)源周圍的包裝器嚼松,使我們能夠使用該數(shù)據(jù)源進行操作,并使批量處理方便快捷锰扶。

流不存儲數(shù)據(jù)献酗,從這個意義上說,它不是數(shù)據(jù)結(jié)構(gòu)坷牛。它也永遠不會修改基礎(chǔ)數(shù)據(jù)源凌摄。

此新功能java.util.stream-支持對元素流進行功能樣式的操作,例如對集合進行map-reduce轉(zhuǎn)換漓帅。

創(chuàng)建流

從現(xiàn)有數(shù)組中獲取流:

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);

還可以從現(xiàn)有列表中獲取流:

private static List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream();

請注意锨亏,Java 8向Collection接口添加了新的stream()方法。

我們可以使用Stream.of()從單個對象創(chuàng)建一個流:

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()是最簡單,最常見的操作捐迫;它在流元素上循環(huán)乾翔,在每個元素上調(diào)用提供的函數(shù)。

該方法非常普遍施戴,已直接在Iterable反浓,Map等中引入:

@Test
public void whenIncrementSalaryForEachEmployee_thenApplyNewSalary() {    
    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))
    ));
}

這將有效地調(diào)用salaryIncrement()中的每個元素empList

forEach()是終端操作赞哗,這意味著在執(zhí)行該操作之后雷则,流流被視為已消耗,并且無法再使用肪笋。在下一節(jié)中月劈,我們將詳細(xì)討論終端操作。

在對原始流的每個元素應(yīng)用函數(shù)之后藤乙,map()會產(chǎn)生一個新的流猜揪。新的流可以是不同的類型。

以下示例將Integer的流轉(zhuǎn)換為Employee的流:

@Test
public void whenMapIdToEmployees_thenGetEmployeeStream() {
    Integer[] empIds = { 1, 2, 3 };

    List<Employee> employees = Stream.of(empIds)
      .map(employeeRepository::findById)
      .collect(Collectors.toList());

    assertEquals(employees.size(), empIds.length);
}

在這里坛梁,我們得到一個整數(shù)雇員ID的從數(shù)組流而姐。每個Integer都傳遞給函數(shù)employeeRepository :: findById(),該函數(shù) 返回相應(yīng)的Employee對象划咐。這有效地形成了員工流拴念。

我們在前面的示例中了解了collect()的工作方式钧萍;一旦完成所有處理,它就是從流中獲取內(nèi)容的常用方法之一:

@Test
public void whenCollectStreamToList_thenGetList() {
    List<Employee> employees = empList.stream().collect(Collectors.toList());

    assertEquals(empList, employees);
}

collect()Stream實例中保存的數(shù)據(jù)元素執(zhí)行可變的折疊操作(將元素重新打包到某些數(shù)據(jù)結(jié)構(gòu)并應(yīng)用一些其他邏輯丈莺,將它們串聯(lián)等)。

此操作的策略是通過Collector接口實現(xiàn)提供的送丰。在上面的示例中缔俄,我們使用toList收集器將所有Stream元素收集到一個List實例中。

接下來器躏,讓我們看一下filter()俐载;這將產(chǎn)生一個新流,其中包含通過給定測試(由謂詞指定)的原始流的元素登失。

讓我們看一下它是如何工作的:

@Test
public void whenFilterEmployees_thenGetFilteredStream() {
    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的引用,然后再次應(yīng)用過濾器以僅保留薪水超過特定閾值的員工揽浙。

@Test
public void whenFindFirst_thenGetFirstEmployeeInStream() {
    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膛虫。

我們看到了如何使用collect()從流中獲取數(shù)據(jù)。如果需要從流中獲取數(shù)組钓猬,則可以簡單地使用toArray()

@Test
public void whenStreamToArray_thenGetArray() {
    Employee[] employees = empList.stream().toArray(Employee[]::new);

    assertThat(empList.toArray(), equalTo(employees));
}

語法Employee [] :: new創(chuàng)建一個空的Employee數(shù)組 -然后用流中的元素填充它稍刀。

流可以保存諸如Stream <List <String >>的復(fù)雜數(shù)據(jù)結(jié)構(gòu)。在這種情況下敞曹,flatMap()可幫助我們展平數(shù)據(jù)結(jié)構(gòu)以簡化進一步的操作:

@Test
public void whenFlatMapEmployeeNames_thenGetNameStream() {
    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);
}

在本節(jié)前面的部分中账月,我們看到了forEach(),這是一個終端操作澳迫。但是局齿,有時我們需要在應(yīng)用任何終端操作之前對流的每個元素執(zhí)行多項操作。

peek()在這種情況下很有用橄登。簡而言之项炼,它對流的每個元素執(zhí)行指定的操作,并返回一個可以進一步使用的新流示绊。peek()是一個中間操作

@Test
public void whenIncrementSalaryUsingPeek_thenApplyNewSalary() {
    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))
    ));
}

方法類型和管道

正如我們一直在討論的锭部,流操作分為中間操作和終端操作。

諸如filter()之類的中間操作返回一個新的流面褐,可以在該流上執(zhí)行進一步的處理拌禾。終端操作(例如forEach())將流標(biāo)記為已使用,此后將無法再使用該流展哭。

流管道包括一個流源湃窍,后跟零個或多個中間操作闻蛀,以及一個終端操作。

@Test
public void whenStreamCount_thenGetElementCount() {
    Long empCount = empList.stream()
      .filter(e -> e.getSalary() > 200000)
      .count();

    assertEquals(empCount, new Long(1));
}

一些操作被認(rèn)為是短路操作您市。短路操作允許對無限流的計算在有限時間內(nèi)完成:

@Test
public void whenLimitInfiniteStream_thenGetFiniteElements() {
    Stream<Integer> infiniteStream = Stream.iterate(2, i -> i * 2);

    List<Integer> collect = infiniteStream
      .skip(3)
      .limit(5)
      .collect(Collectors.toList());

    assertEquals(collect, Arrays.asList(16, 32, 64, 128, 256));
}

懶惰評估

流的最重要特征之一是它們允許通過惰性評估進行重大優(yōu)化觉痛。

僅在啟動終端操作時才執(zhí)行對源數(shù)據(jù)的計算,并且僅在需要時才使用源元素茵休。所有中間操作都是惰性的薪棒,因此直到實際需要處理結(jié)果時才執(zhí)行它們。

例如榕莺,考慮我們之前看到的findFirst()示例俐芯。此處執(zhí)行map()操作多少次?4次钉鸯,因為輸入數(shù)組包含4個元素吧史?

@Test
public void whenFindFirst_thenGetFirstEmployeeInStream() {
    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));
}

Stream執(zhí)行映射和兩次過濾操作,一次執(zhí)行一個元素唠雕。

它首先對id 1執(zhí)行所有操作贸营。由于id 1的薪水不大于100000,因此處理移至下一個元素岩睁。

Id 2滿足兩個過濾器謂詞莽使,因此流評估終端操作findFirst()并返回結(jié)果。

無法對ID 3和ID 4執(zhí)行任何操作笙僚。

不必要地處理流可以避免在不需要時檢查所有數(shù)據(jù)芳肌。當(dāng)輸入流是無限且不僅很大時,此行為甚至變得更加重要肋层。

基于比較的流操作

讓我們從sorted()操作開始-這根據(jù)傳遞給我們的比較器對流元素進行排序亿笤。

例如,我們可以根據(jù)Employee的名稱對其進行排序:

@Test
public void whenSortStream_thenGetSortedStream() {
    List<Employee> employees = empList.stream()
      .sorted((e1, e2) -> e1.getName().compareTo(e2.getName()))
      .collect(Collectors.toList());

    assertEquals(employees.get(0).getName(), "Bill Gates");
    assertEquals(employees.get(1).getName(), "Jeff Bezos");
    assertEquals(employees.get(2).getName(), "Mark Zuckerberg");
}

請注意栋猖,短路不適用于sorted()净薛。

@Test
public void whenFindMin_thenGetMinElementFromStream() {
    Employee firstEmp = empList.stream()
      .min((e1, e2) -> e1.getId() - e2.getId())
      .orElseThrow(NoSuchElementException::new);

    assertEquals(firstEmp.getId(), new Integer(1));
}

還可以避免使用Comparator.comparing()定義比較邏輯:

@Test
public void whenFindMax_thenGetMaxElementFromStream() {
    Employee maxSalEmp = empList.stream()
      .max(Comparator.comparing(Employee::getSalary))
      .orElseThrow(NoSuchElementException::new);

    assertEquals(maxSalEmp.getSalary(), new Double(300000.0));
}

exclude()不接受任何參數(shù),并返回流中的distinct元素蒲拉,從而消除重復(fù)項肃拜。它使用元素的equals()方法來確定兩個元素是否相等:

@Test
public void whenApplyDistinct_thenRemoveDuplicatesFromStream() {
    List<Integer> intList = Arrays.asList(2, 5, 3, 2, 4, 3);
    List<Integer> distinctIntList = intList.stream().distinct().collect(Collectors.toList());

    assertEquals(distinctIntList, Arrays.asList(2, 5, 3, 4));
}

allMatch,anyMatch和noneMatch

這些操作都帶有一個謂詞雌团,并返回一個布爾值燃领。確定答案后,就會發(fā)生短路并停止處理:

@Test
public void whenApplyMatch_thenReturnBoolean() {
    List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8);

    boolean allEven = intList.stream().allMatch(i -> i % 2 == 0);
    boolean oneEven = intList.stream().anyMatch(i -> i % 2 == 0);
    boolean noneMultipleOfThree = intList.stream().noneMatch(i -> i % 3 == 0);

    assertEquals(allEven, false);
    assertEquals(oneEven, true);
    assertEquals(noneMultipleOfThree, false);
}

allMatch()檢查流中所有元素的謂詞是否為true锦援。在這里猛蔽,一旦遇到5 ,它就返回false,而5不能被2整除曼库。

anyMatch()檢查流中任何一個元素的謂詞是否為true区岗。在此,再次施加短路毁枯,并且在第一個元素之后立即返回true慈缔。

noneMatch()檢查是否沒有與謂詞匹配的元素。在這里种玛,一旦遇到6 藐鹤,它就簡單地返回false,該值可以被3整除蒂誉。

專用Stream

根據(jù)到目前為止的討論教藻,Stream是對象引用的流距帅。但是右锨,還有IntStreamLongStreamDoubleStream碌秸,它們分別是int绍移,longdouble的原始特化。這些在處理許多數(shù)字基元時非常方便讥电。

創(chuàng)建

創(chuàng)建IntStream的最常見方法是在現(xiàn)有流上調(diào)用mapToInt()

@Test
public void whenFindMaxOnIntStream_thenGetMaxInteger() {
    Integer latestEmpId = empList.stream()
      .mapToInt(Employee::getId)
      .max()
      .orElseThrow(NoSuchElementException::new);

    assertEquals(latestEmpId, new Integer(3));
}

從一個流<Employee>開始蹂窖,通過向mapToInt提供Employee::getId來獲得一個IntStream。最后恩敌,我們調(diào)用max()瞬测,它返回最高的整數(shù)。

也可以使用IntStream.of()用于創(chuàng)建IntStream:

IntStream.of(1, 2, 3);

IntStream.range()

IntStream.range(10, 20)

這將創(chuàng)建數(shù)字10到19的IntStream纠炮。

在繼續(xù)討論下一個主題之前月趟,需要注意的一個重要區(qū)別是:

Stream.of(1, 2, 3)

這將返回Stream <Integer>而不是IntStream

同樣恢口,使用map()而不是mapToInt()返回Stream <Integer>而不是IntStream孝宗。

empList.stream().map(Employee::getId);

專用的操作

與標(biāo)準(zhǔn)相比,專用流提供了額外的操作-在處理數(shù)字時非常方便耕肩。

例如sum()因妇,average(),range()等:

@Test
public void whenApplySumOnIntStream_thenGetSum() {
    Double avgSal = empList.stream()
      .mapToDouble(Employee::getSalary)
      .average()
      .orElseThrow(NoSuchElementException::new);

    assertEquals(avgSal, new Double(200000));
}

減少操作

歸約運算(也稱為折疊)采用一系列輸入元素猿诸,并通過重復(fù)應(yīng)用組合操作將它們組合為單個匯總結(jié)果婚被。我們已經(jīng)看到了很少的歸約運算,例如findFirst()梳虽,min()max()摔寨。

讓我們來看一下通用的reduce()操作。

reduce()的最常見形式是:

T reduce(T identity, BinaryOperator<T> accumulator)

在這里怖辆,標(biāo)識是起始值是复,累加器是我們重復(fù)應(yīng)用的二進制運算删顶。

@Test
public void whenApplyReduceOnStream_thenGetValue() {
    Double sumSal = empList.stream()
      .map(Employee::getSalary)
      .reduce(0.0, Double::sum);

    assertEquals(sumSal, new Double(600000));
}

在這里,我們從初始值0開始淑廊,然后在流的元素上重復(fù)應(yīng)用Double :: sum()逗余。通過在Stream上應(yīng)用reduce(),我們有效地實現(xiàn)了DoubleStream.sum()季惩。

進階收集

我們已經(jīng)了解了如何使用Collectors.toList()從流中獲取列表÷剂唬現(xiàn)在讓我們看看從流中收集元素的更多方法。

@Test
public void whenCollectByJoining_thenGetJoinedString() {
    String empNames = empList.stream()
      .map(Employee::getName)
      .collect(Collectors.joining(", "))
      .toString();

    assertEquals(empNames, "Jeff Bezos, Bill Gates, Mark Zuckerberg");
}

還可以使用toSet()從流元素中獲取一個集合:

@Test
public void whenCollectBySet_thenGetSet() {
    Set<String> empNames = empList.stream()
            .map(Employee::getName)
            .collect(Collectors.toSet());

    assertEquals(empNames.size(), 3);
}

toCollection

@Test
public void whenToVectorCollection_thenGetVector() {
    Vector<String> empNames = empList.stream()
            .map(Employee::getName)
            .collect(Collectors.toCollection(Vector::new));

    assertEquals(empNames.size(), 3);
}

在這里画拾,在內(nèi)部創(chuàng)建一個空集合啥繁,并在流的每個元素上調(diào)用其add()方法。

summarizingDouble

summarizingDouble()是另一個有趣的收集器-對每個輸入元素應(yīng)用雙重生成的映射函數(shù)青抛,并返回一個特殊類旗闽,該類包含有關(guān)所得值的統(tǒng)計信息:

@Test
public void whenApplySummarizing_thenGetBasicStats() {
    DoubleSummaryStatistics stats = empList.stream()
      .collect(Collectors.summarizingDouble(Employee::getSalary));

    assertEquals(stats.getCount(), 3);
    assertEquals(stats.getSum(), 600000.0, 0);
    assertEquals(stats.getMin(), 100000.0, 0);
    assertEquals(stats.getMax(), 300000.0, 0);
    assertEquals(stats.getAverage(), 200000.0, 0);
}

請注意,我們?nèi)绾畏治雒總€員工的薪水并獲取有關(guān)該數(shù)據(jù)的統(tǒng)計信息蜜另,例如最小值适室,最大值,平均值等举瑰。

當(dāng)我們使用一種特殊的流時捣辆,summaryStatistics()可用于生成類似的結(jié)果:

@Test
public void whenApplySummaryStatistics_thenGetBasicStats() {
    DoubleSummaryStatistics stats = empList.stream()
      .mapToDouble(Employee::getSalary)
      .summaryStatistics();

    assertEquals(stats.getCount(), 3);
    assertEquals(stats.getSum(), 600000.0, 0);
    assertEquals(stats.getMin(), 100000.0, 0);
    assertEquals(stats.getMax(), 300000.0, 0);
    assertEquals(stats.getAverage(), 200000.0, 0);
}

partitioningBy

我們可以根據(jù)元素是否滿足特定條件將流分成兩部分。

讓我們將數(shù)據(jù)列表分為偶數(shù)和奇數(shù):

@Test
public void whenStreamPartition_thenGetMap() {
    List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8);
    Map<Boolean, List<Integer>> isEven = intList.stream().collect(
      Collectors.partitioningBy(i -> i % 2 == 0));

    assertEquals(isEven.get(true).size(), 4);
    assertEquals(isEven.get(false).size(), 1);
}

在這里此迅,流被劃分為一個Map汽畴,偶數(shù)和奇數(shù)存儲為真和假鍵。

分組

groupingBy()提供了高級分區(qū)功能-在這里我們可以將流劃分為兩個以上的組耸序。

它以分類函數(shù)為參數(shù)忍些。此分類功能應(yīng)用于流的每個元素。

該函數(shù)返回的值用作從groupingBy收集器獲取的映射的鍵:

@Test
public void whenStreamGroupingBy_thenGetMap() {
    Map<Character, List<Employee>> groupByAlphabet = empList.stream().collect(
      Collectors.groupingBy(e -> new Character(e.getName().charAt(0))));

    assertEquals(groupByAlphabet.get('B').get(0).getName(), "Bill Gates");
    assertEquals(groupByAlphabet.get('J').get(0).getName(), "Jeff Bezos");
    assertEquals(groupByAlphabet.get('M').get(0).getName(), "Mark Zuckerberg");
}

在此快速示例中佑吝,我們根據(jù)員工名字的首字母對員工進行分組坐昙。

在上一節(jié)中討論過的groupingBy()使用Map來對流的元素進行分組。

但是芋忿,有時我們可能需要將數(shù)據(jù)分組為除元素類型以外的其他類型炸客。

這就是我們可以做到的;我們可以使用mapping()戈钢,它實際上可以使收集器適應(yīng)其他類型-使用映射函數(shù):

@Test
public void whenStreamMapping_thenGetMap() {
    Map<Character, List<Integer>> idGroupedByAlphabet = empList.stream().collect(
      Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
        Collectors.mapping(Employee::getId, Collectors.toList())));

    assertEquals(idGroupedByAlphabet.get('B').get(0), new Integer(2));
    assertEquals(idGroupedByAlphabet.get('J').get(0), new Integer(1));
    assertEquals(idGroupedByAlphabet.get('M').get(0), new Integer(3));
}

在這里痹仙,mapping()使用getId()映射函數(shù)將流元素Employee僅映射到雇員ID(它是Integer )。這些ID仍根據(jù)員工名字的首字母進行分組殉了。

減少()是類似于減少() - 這是我們之前探討开仰。它只是返回一個收集器,該收集器將減少其輸入元素:

@Test
public void whenStreamReducing_thenGetValue() {
    Double percentage = 10.0;
    Double salIncrOverhead = empList.stream().collect(Collectors.reducing(
        0.0, e -> e.getSalary() * percentage / 100, (s1, s2) -> s1 + s2));

    assertEquals(salIncrOverhead, 60000.0, 0);
}

在這里reduce()獲取每個雇員的薪水增量并返回總和。

當(dāng)在groupingBy()partitioningBy()的下游進行多級歸約時众弓,reducing()最有用恩溅。要對流執(zhí)行簡單的還原,請使用reduce()谓娃。

例如脚乡,讓我們看看如何將reduce()groupingBy()結(jié)合使用

@Test
public void whenStreamGroupingAndReducing_thenGetMap() {
    Comparator<Employee> byNameLength = Comparator.comparing(Employee::getName);

    Map<Character, Optional<Employee>> longestNameByAlphabet = empList.stream().collect(
      Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
        Collectors.reducing(BinaryOperator.maxBy(byNameLength))));

    assertEquals(longestNameByAlphabet.get('B').get().getName(), "Bill Gates");
    assertEquals(longestNameByAlphabet.get('J').get().getName(), "Jeff Bezos");
    assertEquals(longestNameByAlphabet.get('M').get().getName(), "Mark Zuckerberg");
}

在這里,我們根據(jù)員工名字的首字母對員工進行分組滨达。在每個組中奶稠,我們找到姓名最長的員工。

并行流

使用對并行流的支持捡遍,我們可以并行執(zhí)行流操作锌订,而無需編寫任何樣板代碼;我們只需要將流指定為并行:

@Test
public void whenParallelStream_thenPerformOperationsInParallel() {
    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().parallel().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()將在該料流的多個元素画株,平行通過簡單地將得到執(zhí)行并行()的語法辆飘。

當(dāng)然,如果您需要對操作的性能特征進行更多控制污秆,則可以進一步調(diào)整和配置此功能劈猪。

與編寫多線程代碼一樣昧甘,在使用并行流時良拼,我們需要注意一些事項:

  1. 我們需要確保代碼是線程安全的。如果并行執(zhí)行的操作修改了共享數(shù)據(jù)充边,則需要格外小心庸推。
  2. 如果執(zhí)行操作的順序或輸出流中返回的順序很重要,則不應(yīng)使用并行流浇冰。例如贬媒,在并行流的情況下,諸如findFirst()之類的操作可能會生成不同的結(jié)果肘习。
  3. 另外际乘,我們應(yīng)該確保值得讓代碼并行執(zhí)行。當(dāng)然漂佩,特別要了解操作的性能特征脖含,而且要了解整個系統(tǒng)的性能特征,這一點自然很重要投蝉。

無限流

有時养葵,我們可能想在元素仍在生成時執(zhí)行操作。我們可能事先不知道我們需要多少個元素瘩缆。與使用listmap填充所有元素不同关拒,我們可以使用無限流,也稱為無界流。

有兩種生成無限流的方法:

我們提供了一個供應(yīng)商產(chǎn)生()每當(dāng)新流元素需要生成其中被調(diào)用:

@Test
public void whenGenerateStream_thenGetInfiniteStream() {
    Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);
}

在這里着绊,我們將Math:: random()作為Supplier傳遞谐算,它返回下一個隨機數(shù)。

對于無限流归露,我們需要提供一個條件以最終終止處理氯夷。一種常見的實現(xiàn)方法是使用limit()。在上面的示例中靶擦,我們將流限制為5個隨機數(shù)腮考,并在生成它們時打印它們。

請注意玄捕,傳遞給generate()Supplier可能是有狀態(tài)的踩蔚,并且在并行使用時,此類流可能不會產(chǎn)生相同的結(jié)果枚粘。

iterate()具有兩個參數(shù):一個初始值(稱為種子元素)和一個使用前一個值生成下一個元素的函數(shù)馅闽。根據(jù)設(shè)計,iterate()是有狀態(tài)的馍迄,因此在并行流中可能沒有用:

@Test
public void whenIterateStream_thenGetInfiniteStream() {
    Stream<Integer> evenNumStream = Stream.iterate(2, i -> i * 2);

    List<Integer> collect = evenNumStream
      .limit(5)
      .collect(Collectors.toList());

    assertEquals(collect, Arrays.asList(2, 4, 8, 16, 32));
}

在這里福也,我們傳遞2作為種子值,它成為流的第一個元素攀圈。此值作為輸入傳遞給lambda暴凑,然后返回4。此值又作為輸入傳遞給下一個迭代赘来。

這一直持續(xù)到我們生成由limit()指定的作為終止條件的元素數(shù)量為止现喳。

文件操作

讓我們看看如何在文件操作中使用流。

文件寫入操作

@Test
public void whenStreamToFile_thenGetFile() throws IOException {
    String[] words = {
      "hello", 
      "refer",
      "world",
      "level"
    };

    try (PrintWriter pw = new PrintWriter(
      Files.newBufferedWriter(Paths.get(fileName)))) {
        Stream.of(words).forEach(pw::println);
    }
}

在這里犬辰,我們使用forEach()通過調(diào)用PrintWriter.println()將流的每個元素寫入文件署驻。

文件讀取操作

private List<String> getPalindrome(Stream<String> stream, int length) {
    return stream.filter(s -> s.length() == length)
      .filter(s -> s.compareToIgnoreCase(
        new StringBuilder(s).reverse().toString()) == 0)
      .collect(Collectors.toList());
}

@Test
public void whenFileToStream_thenGetStream() throws IOException {
    List<String> str = getPalindrome(Files.lines(Paths.get(fileName)), 5);
    assertThat(str, contains("refer", "level"));
}

getPalindrome()在流上工作结缚,完全不知道流是如何生成的肖抱。這也提高了代碼的可重用性并簡化了單元測試滞磺。

結(jié)論

在本文中,專注于Java 8中新的Stream功能的細(xì)節(jié)涵卵。我們看到了所支持的各種操作以及如何使用lambda和管道來編寫簡潔的代碼浴栽。

還看到了流的一些特性,例如惰性求值缘厢,并行流和無限流吃度。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贴硫,隨后出現(xiàn)的幾起案子椿每,更是在濱河造成了極大的恐慌伊者,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件间护,死亡現(xiàn)場離奇詭異亦渗,居然都是意外死亡,警方通過查閱死者的電腦和手機汁尺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門法精,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痴突,你說我怎么就攤上這事搂蜓。” “怎么了辽装?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵帮碰,是天一觀的道長。 經(jīng)常有香客問我拾积,道長殉挽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任拓巧,我火速辦了婚禮斯碌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肛度。我一直安慰自己傻唾,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布贤斜。 她就那樣靜靜地躺著策吠,像睡著了一般逛裤。 火紅的嫁衣襯著肌膚如雪瘩绒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天带族,我揣著相機與錄音锁荔,去河邊找鬼。 笑死蝙砌,一個胖子當(dāng)著我的面吹牛阳堕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播择克,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼恬总,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肚邢?” 一聲冷哼從身側(cè)響起壹堰,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤拭卿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贱纠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峻厚,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年谆焊,在試婚紗的時候發(fā)現(xiàn)自己被綠了惠桃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡辖试,死狀恐怖辜王,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情罐孝,我是刑警寧澤誓禁,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站肾档,受9級特大地震影響摹恰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怒见,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一俗慈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遣耍,春花似錦闺阱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纪隙,卻和暖如春赊豌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绵咱。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工碘饼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悲伶。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓艾恼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親麸锉。 傳聞我的和親對象是個殘疾皇子钠绍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容