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是對象引用的流距帅。但是右锨,還有IntStream,LongStream和DoubleStream碌秸,它們分別是int绍移,long和double的原始特化。這些在處理許多數(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)整和配置此功能劈猪。
與編寫多線程代碼一樣昧甘,在使用并行流時良拼,我們需要注意一些事項:
- 我們需要確保代碼是線程安全的。如果并行執(zhí)行的操作修改了共享數(shù)據(jù)充边,則需要格外小心庸推。
- 如果執(zhí)行操作的順序或輸出流中返回的順序很重要,則不應(yīng)使用并行流浇冰。例如贬媒,在并行流的情況下,諸如findFirst()之類的操作可能會生成不同的結(jié)果肘习。
- 另外际乘,我們應(yīng)該確保值得讓代碼并行執(zhí)行。當(dāng)然漂佩,特別要了解操作的性能特征脖含,而且要了解整個系統(tǒng)的性能特征,這一點自然很重要投蝉。
無限流
有時养葵,我們可能想在元素仍在生成時執(zhí)行操作。我們可能事先不知道我們需要多少個元素瘩缆。與使用list或map填充所有元素不同关拒,我們可以使用無限流,也稱為無界流。
有兩種生成無限流的方法:
我們提供了一個供應(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和管道來編寫簡潔的代碼浴栽。
還看到了流的一些特性,例如惰性求值缘厢,并行流和無限流吃度。