Java-8 simplify lambda expression with embedded Streams ,並行流的 線程安全問題

Is there a more simple and performant way of doing this, At the end I would need a list of scheduleContainers (List<ScheduleContainer>)
有沒有更簡單和更高效的方法來做到這一點,最後我需要一個列表scheduleContainers (List<ScheduleContainer>)
final List<ScheduleResponseContent> scheduleResponseContents = new ArrayList<>();
scheduleResponseWrappers.parallelStream().forEach(srw -> scheduleResponseContents.addAll(srw.getScheduleResponseContents()));
final List<List<ScheduleContainer>> schedulesOfWeek = new ArrayList<>();
scheduleResponseContents.parallelStream().forEach(src -> schedulesOfWeek.addAll(src.getSchedules()));
final List<ScheduleContainer> schedules = new ArrayList<>();
schedulesOfWeek.parallelStream().forEach(s -> schedules.addAll(s));
Because of missing classes, I can just assume this is correct:
final List<ScheduleContainer> schedules = scheduleResponseWrappers.stream()
    .flatMap(srw -> srw.getScheduleResponseContents().stream())
    .flatMap(src -> src.getSchedules().stream())
    .collect(Collectors.toList());

 Java-8 parallelStream(...) -> fill ArrayList

I have tried this code:
 final List<ScheduleContainer> scheduleContainers = new ArrayList<>();
 scheduleResponseContent.getSchedules().parallelStream().forEach(s -> scheduleContainers.addAll(s));

With parallelStream I get either an ArrayIndexOutOfBoundException or a NullpointerException because some entries in scheduleContainers are null.

With ... .stream()... everything works fine. My question now would be if there is a possibiliy to fix this or did I misuse parallelStream?

使用 parallelStream 我得到 ArrayIndexOutOfBoundException 或 NullpointerException,因爲scheduleContainers中的某些條目爲空。

使用 ... .stream()... 一切正常。我現在的問題是,是否有可能解決這個問題,或者我是否濫用了 parallelStream?

Yes, you are misusing parallelStream. First of all, as you have already said twice in your previous question, you should use stream(), and not parallelStream(), by default. Going parallel has an intrinsic cost, that usually makes things less efficient than a simple sequential stream, unless you has a massive amount of data to process, and the process of each element takes time. You should have a performance problem, and measure if a parallel stream solves it, before using one. There's also a much bigger chance of screwing up with a parallel stream, as your post shows.

Read Should I always use a parallel stream when possible? for more arguments.

Second, this code is not thread-safe at all, since it uses several concurrent threads to add to a thread-unsafe ArrayList. It can be safe if you use collect() to create the final list for you instead of forEach() and add things to the list by yourself.

是的,您在濫用 parallelStream。首先,正如您在上一個問題中已經說過兩次,默認情況下您應該使用 stream() 而不是 parallelStream()。並行具有內在成本,這通常會使事情的效率低於簡單的順序流,除非您有大量數據要處理,並且每個元素的處理都需要時間。在使用並行流之前,您應該遇到性能問題,並測量並行流是否解決了它。正如您的帖子所示,並行流搞砸的可能性也更大。

閱讀我應該儘可能始終使用並行流嗎?更多論據。

其次,此代碼根本不是線程安全的,因爲它使用多個併發線程來添加到線程不安全的 ArrayList。如果您使用 collect() 而不是 forEach() 爲您創建最終列表並自行將內容添加到列表中,則可能是安全的。

The code should be

List<ScheduleContainer> scheduleContainers =
    scheduleResponseContent.getSchedules().
                           .stream()
                           .flatMap(s -> s.stream())
                           .collect(Collectors.toList());

 You could also do the following and it would still be thread safe (use .toList() instead of .collect(Collectors.toList()): List<ScheduleContainer> scheduleContainers = scheduleResponseContent.getSchedules().stream() .flatMap(s -> s.stream()).toList() 

您還可以執行以下操作,它仍然是線程安全的(使用 .toList() 而不是 .collect(Collectors.toList())): -> s.stream()).toList() 

Not sure about the cause of the error, but there are better ways to use the Stream API to create a List from multiple input Lists.

不確定錯誤的原因,但是有更好的方法可以使用 Stream API 從多個輸入列表創建一個列表。

final List<ScheduleContainer> scheduleContainers =
    scheduleResponseContent.getSchedules()
                           .parallelStream()
                           .flatMap(s->s.stream()) // assuming getSchedules() returns some 
                                                   // Collection<ScheduleContainer>, based 
                                                   // on your use of addAll(s)
                           .collect(Collectors.toList());

Too many constructor arguments in Spring Beans

Differences of Java 16's Stream.toList() and Stream.collect(Collectors.toList())?

JDK 16 now includes a toList() method directly on Stream instances. In previous Java versions, you always had to use the collect method and provide a Collector instance.

The new method is obviously fewer characters to type. Are both methods interchangeable or are there subtle differences one should be aware of?

JDK 16 現在包含一個toList()直接在Stream實例上的方法。在以前的 Java 版本中,您總是必須使用該collect方法並提供一個Collector實例。

新方法顯然需要輸入的字符更少。這兩種方法是否可以互換,或者是否存在應該注意的細微差別?

var newList = someCollection.stream()
    .map(x -> mapX(x))
    .filter(x -> filterX(x))
    .toList();

// vs.

var oldList = someCollection.stream()
    .map(x -> mapX(x))
    .filter(x -> filterX(x))
    .collect(Collectors.toList());

(This question is similar to Would Stream.toList() perform better than Collectors.toList(), but focused on behavior and not (only) on performance.)

(這個問題類似於Would Stream.toList() perform better than Collectors.toList(),但重點是行爲而不是(僅)性能。) 

One difference is that Stream.toList() provides a List implementation that is immutable (type ImmutableCollections.ListN that cannot be added to or sorted) similar to that provided by List.of() and in contrast to the mutable (can be changed and sorted) ArrayList provided by Stream.collect(Collectors.toList()).

一個區別是它提供了Stream.toList()一個List不可變的(ImmutableCollections.ListN無法添加或排序的類型)實現List.of(),類似於.ArrayListStream.collect(Collectors.toList())

Demo:

import java.util.stream.Stream;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = Stream.of("Hello").toList();
        System.out.println(list);
        list.add("Hi");
    }
}

Output:

[Hello]
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
    at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
    at Main.main(Main.java:8)

Please check this article for more details.

Update:

Interestingly, Stream.toList() returns a nulls-containing list successfully.

更新:

有趣的是,成功Stream.toList()返回一個null包含s的列表。

import java.util.stream.Stream;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Object> list = Stream.of(null, null).toList();
        System.out.println(list);
    }
}

Output:

[null, null]

On the other hand, List.of(null, null) throws NullPointerException.

另一方面,List.of(null, null)拋出NullPointerException.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Object> list = List.of(null, null);
    }
}

Output:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:208)
    at java.base/java.util.ImmutableCollections$List12.<init>(ImmutableCollections.java:453)
    at java.base/java.util.List.of(List.java:827)
    at Main.main(Main.java:5)

Note: I've used openjdk-16-ea+34_osx-x64 to compile and execute the Java SE 16 code.

注意:我使用openjdk-16-ea+34_osx-x64來編譯和執行 Java SE 16 代碼。

Useful resources:

  1. JDK Bug#JDK-8180352
  2. Calling Java varargs method with single null argument?
  1. JDK 漏洞# JDK-8180352
  2. 使用單個 null 參數調用 Java varargs 方法?
IIRC, Collectors.toList() is not guaranteed to give us a mutable list either. It just happens to do so in the Java versions we have seen so far. 
Collectors.toList()也不能保證給我們一個可變列表。到目前爲止,我們看到的 Java 版本恰好是這樣做的。
 
@OleV.V. - Correct. The article linked in my answer mentions it as: Although there are no guarantees regarding the “type, mutability, serializability, or thread-safety” on the List provided by Collectors.toList(), it is expected that some may have realized it’s currently an ArrayList and have used it in ways that depend on the characteristics of an ArrayList. 
正確的。我的回答中鏈接的文章提到它是:儘管沒有關於collectors.tolist()提供的列表上的“類型,可變性,序列化或線程安全性”的保證關於陣列列表的特徵。 預計有些人可能已經意識到它是當前的陣列列表,並且以取決於陣列列表的特徵的方式使用它。
@Caramiriel The List interface was on purpose designed with a number of optional methods, that is, methods that implementing classes need not implement and that many implementations do not implement. You’re not the first to wonder. 
List接口是有意使用許多可選方法設計的,也就是說,實現類的方法不需要實現,許多實現也不需要實現。你不是第一個想知道的人
It is not really a correct statement to say that collect(toList()) returns a mutable list; the specification is very clear that it makes no guarantees as to the mutability of the returned list. The current implementation happens to return an ArrayList right now, but the spec was written explicitly to allow that to change. If you want a mutable list, use toCollection(ArrayList::new). 
collect(toList())說返回一個可變列表並不是一個正確的說法;規範非常清楚,它不保證返回列表的可變性。當前的實現恰好返回一個ArrayList right now,但規範是明確編寫的以允許更改。如果您想要一個可變列表,請使用toCollection(ArrayList::new). 
It is unfortunate that List was not divided into ReadableList with get() etc. and WritableList with set()add() etc., but it's too late to fix now. (note: I originally came here to suggest toCollection(ArrayList::new), which is what I always use when I need a mutable list, but The Man Himself beat me to it. 😂) 
 可惜當時List沒有分成ReadableListwith etc.get()WritableListwith等,但現在修復已經來不及了。(注意:我最初是來這裏建議的,這是我在需要可變列表時經常使用的方法,但是 The Man Himself 搶先了我。😂)set()add()toCollection(ArrayList::new) 

Here is a small table that summarizes the differences between Stream.collect(Collectors.toList())Stream.collect(Collectors.toUnmodifiableList()) and Stream.toList():

Stream.collect(Collectors.toList())這是一個小表格,總結了,Stream.collect(Collectors.toUnmodifiableList())和之間的區別Stream.toList()

Method Guarantees unmodifiability Allows nulls
collect(toList()) No Yes
collect(toUnmodifiableList()) Yes No
toList() Yes Yes

Another small difference:

// Compiles
List<CharSequence> list = Stream.of("hello", "world").collect(toList());

// Error
List<CharSequence> list = Stream.of("hello", "world").toList();

 The new method Stream.toList() produces neither an unmodifiable list nor a shortcut to collect(toUnmodifiableList()), because toUnmodifiableList() doesn’t accept nulls.The implementation of Stream.toList() is not constrained by the Collector interface; therefore, Stream.toList() allocates less memory. That makes it optimal to use when the stream size is known in advance. blogs.oracle.com/javamagazine/hidden-gems-jdk16-jdk17-jep Can you Validate the above statements 

 新方法 Stream.toList() 既不生成不可修改的列表,也不生成 collect(toUnmodifiableList()) 的快捷方式,因爲 toUnmodifiableList() 不接受空值。 Stream.toList() 的實現不受 Collector 接口的約束;因此,Stream.toList() 分配的內存較少。這使得它最適合在事先知道流大小時使用。 
I would add that the first one was introduced in Java 8, the 2nd in Java 10, and the 3rd in Java 16. 
 我要補充的是,第一個是在 Java 8 中引入的,第二個是在 Java 10 中引入的,第三個是在 Java 16 中引入的。

.collect(toList()) and toList() behave different regarding sub type compatibility of the elements in the created lists.

Have a look at the following alternatives:

  • List<Number> old = Stream.of(0).collect(Collectors.toList()); works fine, although we collect a stream of Integer into a list of Number.
  • List<Number> new = Stream.of(0).toList(); is the equivalent Java 16+ version, but it doesn't compile (cannot convert from List<Integer> to List<Number>; at least in ecj, the Eclipse Java compiler).

There are at least 2 workarounds to fix the compile error:

  • Explicitly cast to the wanted type List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • allow sub types in the result collection: List<? extends Number> fix2 = Stream.of(0).toList();

To my understanding the root cause is as follows: The generic type T of the Java 16 toList() is the same as the generic type T of the Stream itself. The generic type T of Collectors.toList() however is propagated from the left hand side of the assignment. If those 2 types are different, you may see errors when replacing all the old calls.

.collect(toList())並且在創建的列表中元素的子類型兼容性方面toList()表現不同。

看看以下替代方案:

  • List<Number> old = Stream.of(0).collect(Collectors.toList());工作正常,儘管我們將Integer流收集到Number列表中。
  • List<Number> new = Stream.of(0).toList();是等效的 Java 16+ 版本,但它不編譯(cannot convert from List<Integer> to List<Number>;至少在 ecj,Eclipse Java 編譯器中)。

至少有 2 種解決方法可以修復編譯錯誤:

  • 顯式轉換爲想要的類型 List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • 在結果集合中允許子類型: List<? extends Number> fix2 = Stream.of(0).toList();

據我瞭解,根本原因如下:Java 16 的泛型 TtoList()與 Stream 本身的泛型 T 相同。然而,Collectors.toList() 的通用類型 T 從賦值的左側傳播。如果這兩種類型不同,您可能會在替換所有舊調用時看到錯誤。

Collecting Stream Elements into a List in Java | Baeldung

一、概述

在本教程中,我們將瞭解從Stream獲取List 的不同方法。我們還將討論它們之間的差異,以及何時使用每種方法。

2. 將流元素收集到列表中

Stream中獲取List是Stream管道最常用的終端操作。在 Java 16 之前,我們習慣於調用Stream.collect()方法並將其作爲參數傳遞給Collector以將元素收集到其中。Collector本身是通過調用Collectors.toList()方法創建的。

但是,已經有人要求更改直接從Stream實例獲取List的方法。隨着 Java 16 的發佈,我們現在可以調用toList(),一種直接在 Stream 上的新方法 來獲取List像StreamEx這樣的庫提供了一種直接從Stream獲取List 的便捷方式。

我們可以使用以下方法將Stream元素累積到List中:

  • Stream.collect(Collectors.toList()):從 Java 8 開始
  • Stream.collect( Collectors.toUnmodifiableList() ):從 Java 10 開始
  • Stream.toList():從 Java 16 開始

我們將按照發布的時間順序使用這些方法。

 

3. 分析列表

我們將首先使用上一節中描述的方法創建列表。然後我們將分析它們的屬性。

 我們將在所有示例中使用以下國家/地區代碼流:

Stream.of(Locale.getISOCountries());
複製

3.1. 創建列表

現在我們將使用不同的方法從給定的國家代碼流創建一個列表

首先,我們將使用Collectors:toList()創建一個列表

List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toList());複製

 然後我們將使用Collectors.toUnmodifiableList()收集它:

 
List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toUnmodifiableList());複製

這裏,在這些方法中,我們通過 Collector接口將Stream累積成一個List 這會導致額外的分配和複製,因爲我們不直接使用Stream。

 接下來,我們將使用Stream.toList()重複收集:

List<String> result = Stream.of(Locale.getISOCountries()).toList();複製

在這裏,我們直接從Stream 中獲取List 從而避免了額外的分配和複製。

因此,與其他兩個調用相比, 直接在 Stream上使用toList()更加簡潔、整潔、方便和最佳。

3.2. 檢查累積列表

讓我們首先檢查我們創建的List的類型。

 

Collectors.toList()將Stream元素 收集到ArrayList中:

java.util.ArrayList複製

Collectors.toUnmodifiableList()將Stream元素 收集到一個不可修改的列表中:

java.util.ImmutableCollections.ListN複製

Stream.toList() 將元素收集到不可修改的列表中:

java.util.ImmutableCollections.ListN複製

儘管Collectors.toList()的當前實現創建了一個可變的List,但該方法的規範本身並不保證 List 的類型、可變性、可序列化性或線程安全性

另一方面,Collectors.toUnmodifiableList() 和Stream.toList()都 生成不可修改的列表。

這意味着我們可以對Collectors.toList() 的元素進行添加和排序等操作,但不能對Collectors.toUnmodifiableList()Stream.toList()的元素進行操作。 

3.3. 允許列表中的空元素

儘管 Stream.toList()生成一個不可修改的List,但它仍然與 Collectors.toUnmodifiableList() 不同這是因爲Stream.toList()允許元素,而Collectors.toUnmodifiableList()不允許元素。但是,Collectors.toList()允許 元素。

當 收集包含空元素的Stream時, Collectors.toList()不會拋出異常:

 
Assertions.assertDoesNotThrow(() -> {
    Stream.of(null,null).collect(Collectors.toList());
});複製

當我們收集 包含 空元素的Stream時, Collectors.toUnmodifiableList()會拋出 NulPointerException

Assertions.assertThrows(NullPointerException.class, () -> {
    Stream.of(null,null).collect(Collectors.toUnmodifiableList());
});複製

當我們嘗試收集包含 空元素的Stream時, Stream.toList()不會拋出NulPointerException

Assertions.assertDoesNotThrow(() -> {
    Stream.of(null,null).toList();
});複製

因此,在將我們的代碼從 Java 8 遷移到 Java 10 或 Java 16 時,這是需要注意的。我們不能盲目地使用Stream.toList()來代替Collectors.toList()Collectors.toUnmodifiableList()。

3.4. 分析總結

下表總結了我們分析的列表的異同:

流列表摘要

4. 何時使用不同的toList()方法

添加Stream.toList()的主要目的是減少Collector API的冗長。

如前所示,使用Collectors方法獲取List非常冗長另一方面,使用Stream.toList()方法可以使代碼簡潔明瞭。

然而,如前幾節所示,Stream.toList()不能用作Collectors.toList()Collectors.toUnmodifiableList()的快捷方式。

其次,  Stream.toList()使用較少的內存,因爲它的實現獨立於 Collector 接口。它將Stream元素直接累積到List中。因此,如果我們事先知道流的大小,那麼使用Stream.toList() 將是最佳選擇。

 

我們也知道Stream API 只提供了toList()方法的實現 。它不包含用於獲取地圖或集合的類似方法。因此,如果我們想要一種統一的方法來獲取任何轉換器,如 list、map 或 set,我們將繼續使用Collector API。這也將保持一致性並避免混淆。

最後,如果我們使用低於 Java 16 的版本,我們必須繼續使用Collectors方法。

下表總結了給定方法的最佳使用:

比較

5.結論

在本文中,我們分析了三種最流行的從Stream獲取List的方法。然後我們研究了主要的區別和相似之處。我們還討論瞭如何以及何時使用這些方法。

與往常一樣,本文中使用的示例的源代碼可在 GitHub 上獲得。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章