java 8 for loop
Java 8 Streams-收集vs減少 (5)
這裡是代碼示例
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().reduce((x,y) -> {
System.out.println(String.format("x=%d,y=%d",x,y));
return (x + y);
}).get();
的System.out.println(總和);
這是執行結果:
x=1,y=2
x=3,y=3
x=6,y=4
x=10,y=5
x=15,y=6
x=21,y=7
28
Reduce函數處理兩個參數,第一個參數是前一個返回值int流,第二個參數是當前流中的計算值,它將第一個值和當前值相加作為下一個計算中的第一個值。
你什麼時候使用collect()
vs reduce()
? 有沒有人有很好的具體的例子,說明什麼時候一種或另一種更好?
鑑於這是一個可變的減少,我認為它需要同步(內部),這反過來可能會損害性能。 大概reduce()
更容易並行化,但是必須創建一個新的數據結構以便在reduce中的每一步之後返回。
上面的陳述是猜測,但我很樂意專家在這裡發出邀請。
原因很簡單:
-
collect()
只能使用可變結果對象。 -
reduce()
被設計用於處理 不可變的結果對象。
“用不可變的reduce()
”例子
public class Employee {
private Integer salary;
public Employee(String aSalary){
this.salary = new Integer(aSalary);
}
public Integer getSalary(){
return this.salary;
}
}
@Test
public void testReduceWithImmutable(){
List<Employee> list = new LinkedList<>();
list.add(new Employee("1"));
list.add(new Employee("2"));
list.add(new Employee("3"));
Integer sum = list
.stream()
.map(Employee::getSalary)
.reduce(0, (Integer a, Integer b) -> Integer.sum(a, b));
assertEquals(new Integer(6), sum);
}
“ collect()
with mutable”的例子
例如,如果您想使用collect()
手動計算總和,則無法使用BigDecimal
但只能使用來自org.apache.commons.lang.mutable
MutableInt
。 看到:
public class Employee {
private MutableInt salary;
public Employee(String aSalary){
this.salary = new MutableInt(aSalary);
}
public MutableInt getSalary(){
return this.salary;
}
}
@Test
public void testCollectWithMutable(){
List<Employee> list = new LinkedList<>();
list.add(new Employee("1"));
list.add(new Employee("2"));
MutableInt sum = list.stream().collect(
MutableInt::new,
(MutableInt container, Employee employee) ->
container.add(employee.getSalary().intValue())
,
MutableInt::add);
assertEquals(new MutableInt(3), sum);
}
這是因為accumulator container.add(employee.getSalary().intValue());
不應該返回一個帶有結果的新對象,而是改變MutableInt
類型的可變container
的MutableInt
。
如果您想使用BigDecimal
代替container
,則不能使用collect()
方法作為container.add(employee.getSalary());
不會更改container
因為BigDecimal
是不可變的。 (除了這個BigDecimal::new
不會工作,因為BigDecimal
沒有空的構造函數)
根據文檔
reduce()收集器在groupingBy或partitioningBy下游用於多級縮減時最為有用。 要對流執行簡單縮減,請改用Stream.reduce(BinaryOperator)。
所以基本上你只能在收集中強制使用reducing()
。 這是另一個example :
For example, given a stream of Person, to calculate the longest last name
of residents in each city:
Comparator<String> byLength = Comparator.comparing(String::length);
Map<String, String> longestLastNameByCity
= personList.stream().collect(groupingBy(Person::getCity,
reducing("", Person::getLastName, BinaryOperator.maxBy(byLength))));
根據本教程,減少有時效率較低
減少操作總是返回一個新值。 但是,累加器函數每次處理流的元素時都會返回一個新值。 假設您想將流的元素減少為更複雜的對象,如集合。 這可能會妨礙您的應用程序的性能。 如果您的reduce操作涉及向集合添加元素,那麼每當您的累加器函數處理元素時,它都會創建一個包含該元素的新集合,效率低下。 相反,更新現有集合會更高效。 您可以使用Stream.collect方法執行此操作,下一節將介紹該方法...
因此,在減少的情況下,身份被“重複使用”,所以如果可能的話,稍微高效地使用.reduce
。
正常的減少是為了結合兩個不可變的值,如int,double等,並產生一個新的; 這是一個不可改變的減少。 相比之下,收集方法旨在改變容器以積累它應該產生的結果。
為了說明這個問題,讓我們假設你想用下面的簡單歸約實現Collectors.toList()
List<Integer> numbers = stream.reduce( new ArrayList<Integer>(),
(List<Integer> l, Integer e) -> {
l.add(e);
return l;
},
(List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2); return l1; });
這相當於Collectors.toList()
。 但是,在這種情況下,您可以修改List<Integer>
。 正如我們所知, ArrayList
不是線程安全的,在迭代時添加/刪除值也不是安全的,所以當您更新列表時,您將獲得併發異常或arrayIndexOutBound異常或任何類型的異常(特別是在並行運行時)或者組合器嘗試合併列表,因為您通過累積(添加)整數來改變列表。 如果你想使這個線程安全,你需要每次傳遞一個新的列表,這會損害性能。
相反, Collectors.toList()
以類似的方式工作。 但是,當您將值累加到列表中時,它可以確保線程安全。 從collect
方法的文檔:
使用收集器對此流的元素執行可變減少操作。 如果流是並行的,並且收集器是並發的,並且流是無序的或者收集器是無序的,那麼將執行並發的減少。 當並行執行時,可以實例化,填充和合併多個中間結果,以保持可變數據結構的隔離。 因此,即使與非線程安全的數據結構(如ArrayList)並行執行,也不需要額外的同步來進行並行減少。 link
所以要回答你的問題:
你什麼時候使用
collect()
vsreduce()
?
如果你有不可改變的值,比如ints
, doubles
, Strings
那麼正常的還原就可以。 但是,如果您必須將值reduce
為一個List
(可變數據結構),那麼您需要使用collect
方法使用可變減少。
首先,返回值是不同的:
<R,A> R collect(Collector<? super T,A,R> collector)
T reduce(T identity, BinaryOperator<T> accumulator)
所以collect
返回任何R
而reduce
回報T
- Stream
的類型。
reduce
是一個“ fold ”操作,它將二元運算符應用於流中的每個元素,其中第一個參數是前一個應用程序的返回值,第二個參數是當前流元素。
collection
是一個聚合操作,其中創建了一個“集合”,並將每個元素“添加”到該集合中。 然後將流中不同部分的集合添加在一起。
如果我們想要獲取一串字符串並將它們連接成一個單一的長字符串,我們可以通過普通的縮減來實現這一點:
String concatenated = strings.reduce("", String::concat)
我們會得到理想的結果,甚至可以並行工作。 但是,我們可能不會對錶演感到高興! 這樣的實現會進行大量的字符串複製,並且運行時間將會是字符數量的O(n ^ 2)。 一個更高性能的方法是將結果累加到一個StringBuilder中,這是一個用於累加字符串的可變容器。 我們可以使用相同的技術來平行化可變縮減,就像我們使用普通縮減一樣。
所以關鍵在於並行化在兩種情況下都是相同的,但是在reduce
情況下,我們將函數應用到流元素本身。 在collect
案例中,我們將該函數應用於可變容器。