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() ? 有沒有人有很好的具體的例子,說明什麼時候一種或另一種更好?

Javadoc提到collect()是一個可變的減少

鑑於這是一個可變的減少,我認為它需要同步(內部),這反過來可能會損害性能。 大概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類型的可變containerMutableInt

如果您想使用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() vs reduce()

如果你有不可改變的值,比如intsdoublesStrings那麼正常的還原就可以。 但是,如果您必須將值reduce為一個List (可變數據結構),那麼您需要使用collect方法使用可變減少。


首先,返回值是不同的:

<R,A> R collect(Collector<? super T,A,R> collector)

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

所以collect返回任何Rreduce回報T - Stream的類型。

reduce是一個“ fold ”操作,它將二元運算符應用於流中的每個元素,其中第一個參數是前一個應用程序的返回值,第二個參數是當前流元素。

collection是一個聚合操作,其中創建了一個“集合”,並將每個元素“添加”到該集合中。 然後將流中不同部分的集合添加在一起。

你鏈接文件給出了有兩種不同方法的原因:

如果我們想要獲取一串字符串並將它們連接成一個單一的長字符串,我們可以通過普通的縮減來實現這一點:

 String concatenated = strings.reduce("", String::concat)  

我們會得到理想的結果,甚至可以並行工作。 但是,我們可能不會對錶演感到高興! 這樣的實現會進行大量的字符串複製,並且運行時間將會是字符數量的O(n ^ 2)。 一個更高性能的方法是將結果累加到一個StringBuilder中,這是一個用於累加字符串的可變容器。 我們可以使用相同的技術來平行化可變縮減,就像我們使用普通縮減一樣。

所以關鍵在於並行化在兩種情況下都是相同的,但是在reduce情況下,我們將函數應用到流元素本身。 在collect案例中,我們將該函數應用於可變容器。





java-8