Java 流 API - 按对象内部列表的项目分组
Java Stream API - Group by items of an object's inner list
有没有办法实现以下示例代码,利用 Java 流 API 而不是必须创建 HashMap
并将其填充到双 forEach
es 中?我试着玩 groupingBy
和 flatMap
但找不到出路。
有一个电影列表,其中每个电影都有一个流派列表(字符串)...
class Movie {
List<String> genres;
}
List<Movie> movies = new ArrayList<>();
...我想按流派对所有电影进行分组
Map<String, List<Movie>> moviesByGenre = new HashMap();
movies.stream()
.forEach(movie -> movie.getGenres()
.stream()
.forEach(genre -> moviesByGenre
.computeIfAbsent(genre, k -> new ArrayList<>())
.add(movie)));
这个很棘手,因为您不能为每个键定义一个键 Movie
,因为这样的对象可以出现在多个键下。
据我所知,最佳 解决方案与您的相同:
Map<String, List<Movie>> groupedMovies = new HashMap<>();
movies.forEach(movie -> {
movie.getGenres().forEach(genre ->
groupedMovies.computeIfAbsent(genre, g -> new ArrayList<>()).add(movie)
);
});
如果您想将此片段“转换”为 java-stream,您必须从 您拥有的 开始 - 即各个流派。使用 flatMap
和 distinct
从每个 Movie
中提取它们以避免重复。然后使用 Collector.toMap
获得所需的输出。
- Key:
Function.identity()
将每个唯一的 genre
映射为键本身。
- 值: 使用另一个
Stream
过滤掉包含特定 genre
的电影以将它们分配给键。
Map<String, List<Movie>> groupedMovies = movies.stream()
.map(Movie::getGenres)
.flatMap(List::stream)
.distinct()
.collect(Collectors.toMap(
Function.identity(),
genre -> movies.stream()
.filter(movie -> movie.getGenres().contains(genre))
.collect(Collectors.toList())));
第一个片段中的过程方法更快,更易于阅读和理解。我不建议在这里使用java-stream。
注意.. 在 stream
之后使用 forEach
没有任何意义:list.stream().forEach(...)
的序列可以改为 list.forEach(...)
。
首先,我们可能想创建一个 Pair
或 Tuple
,如下所示:
public static class Pair {
Movie movie;
String genre;
public Pair(Movie movie, String genre) {
this.movie = movie;
this.genre = genre;
}
// Getters omitted
}
这也可以是 Record
以及 Java 17.
接下来,我们可以用toMap
进行以下操作:
Map<String, List<Movie>> map = movies.stream()
.flatMap(movie -> movie.getGenres().stream().map(genre -> new Pair(movie, genre)))
.collect(Collectors.toMap(Pair::getGenre, pair -> new ArrayList<>(List.of(pair.getMovie())),
(existing, current) -> {
existing.addAll(current);
return existing;
}));
或者我们可以使用groupBy
:
Map<String, List<Movie>> map = movies.stream()
.flatMap(movie -> movie.getGenres().stream().map(genre -> new Pair(movie, genre)))
.collect(Collectors.groupingBy(Pair::getGenre,
HashMap::new,
Collectors.mapping(Pair::getMovie, Collectors.toList())));
本质上,我们为每部电影创建了一个 Pair
,其中包含所有可能的 genre
。在我们有了这对之后,我们将它们分组以摆脱 Pair
.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.groupingBy;
class Movie {
String type;
String name;
Movie(String type, String name)
{
this.type = type;
this.name = name;
}
public Movie(String type) {
this.type = type;
}
public String getType() {
return type;
}
public String getName()
{
return name;
}
public static void main(String... args) {
List<Movie> posts = new ArrayList<>();
posts.add( new Movie("HORROR", "movie1"));
posts.add( new Movie("HORROR", "movie1"));
posts.add( new Movie("HORROR", "movie1"));
posts.add( new Movie("COMEDY", "movie1"));
posts.add( new Movie("COMEDY", "movie2"));
posts.add( new Movie("COMEDY", "movie3"));
posts.add( new Movie("COMEDY", "movie2"));
Map<String, List<Movie>>postsPerType = posts.stream()
.collect(groupingBy(Movie::getType));
postsPerType.forEach((k,v) -> System.out.println("Key = "
+ k + ", Value = " + v.size()));
Map<String, List<Movie>>postsPerName = posts.stream()
.collect(groupingBy(Movie::getName));
postsPerName.forEach((k,v) -> System.out.println("Key = "
+ k + ", Value = " + v.size()));
}
}
Key = HORROR, Value = 3
Key = COMEDY, Value = 4
Key = movie3, Value = 1
Key = movie2, Value = 2
Key = movie1, Value = 4
有没有办法实现以下示例代码,利用 Java 流 API 而不是必须创建 HashMap
并将其填充到双 forEach
es 中?我试着玩 groupingBy
和 flatMap
但找不到出路。
有一个电影列表,其中每个电影都有一个流派列表(字符串)...
class Movie {
List<String> genres;
}
List<Movie> movies = new ArrayList<>();
...我想按流派对所有电影进行分组
Map<String, List<Movie>> moviesByGenre = new HashMap();
movies.stream()
.forEach(movie -> movie.getGenres()
.stream()
.forEach(genre -> moviesByGenre
.computeIfAbsent(genre, k -> new ArrayList<>())
.add(movie)));
这个很棘手,因为您不能为每个键定义一个键 Movie
,因为这样的对象可以出现在多个键下。
据我所知,最佳 解决方案与您的相同:
Map<String, List<Movie>> groupedMovies = new HashMap<>();
movies.forEach(movie -> {
movie.getGenres().forEach(genre ->
groupedMovies.computeIfAbsent(genre, g -> new ArrayList<>()).add(movie)
);
});
如果您想将此片段“转换”为 java-stream,您必须从 您拥有的 开始 - 即各个流派。使用 flatMap
和 distinct
从每个 Movie
中提取它们以避免重复。然后使用 Collector.toMap
获得所需的输出。
- Key:
Function.identity()
将每个唯一的genre
映射为键本身。 - 值: 使用另一个
Stream
过滤掉包含特定genre
的电影以将它们分配给键。
Map<String, List<Movie>> groupedMovies = movies.stream()
.map(Movie::getGenres)
.flatMap(List::stream)
.distinct()
.collect(Collectors.toMap(
Function.identity(),
genre -> movies.stream()
.filter(movie -> movie.getGenres().contains(genre))
.collect(Collectors.toList())));
第一个片段中的过程方法更快,更易于阅读和理解。我不建议在这里使用java-stream。
注意.. 在 stream
之后使用 forEach
没有任何意义:list.stream().forEach(...)
的序列可以改为 list.forEach(...)
。
首先,我们可能想创建一个 Pair
或 Tuple
,如下所示:
public static class Pair {
Movie movie;
String genre;
public Pair(Movie movie, String genre) {
this.movie = movie;
this.genre = genre;
}
// Getters omitted
}
这也可以是 Record
以及 Java 17.
接下来,我们可以用toMap
进行以下操作:
Map<String, List<Movie>> map = movies.stream()
.flatMap(movie -> movie.getGenres().stream().map(genre -> new Pair(movie, genre)))
.collect(Collectors.toMap(Pair::getGenre, pair -> new ArrayList<>(List.of(pair.getMovie())),
(existing, current) -> {
existing.addAll(current);
return existing;
}));
或者我们可以使用groupBy
:
Map<String, List<Movie>> map = movies.stream()
.flatMap(movie -> movie.getGenres().stream().map(genre -> new Pair(movie, genre)))
.collect(Collectors.groupingBy(Pair::getGenre,
HashMap::new,
Collectors.mapping(Pair::getMovie, Collectors.toList())));
本质上,我们为每部电影创建了一个 Pair
,其中包含所有可能的 genre
。在我们有了这对之后,我们将它们分组以摆脱 Pair
.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.groupingBy;
class Movie {
String type;
String name;
Movie(String type, String name)
{
this.type = type;
this.name = name;
}
public Movie(String type) {
this.type = type;
}
public String getType() {
return type;
}
public String getName()
{
return name;
}
public static void main(String... args) {
List<Movie> posts = new ArrayList<>();
posts.add( new Movie("HORROR", "movie1"));
posts.add( new Movie("HORROR", "movie1"));
posts.add( new Movie("HORROR", "movie1"));
posts.add( new Movie("COMEDY", "movie1"));
posts.add( new Movie("COMEDY", "movie2"));
posts.add( new Movie("COMEDY", "movie3"));
posts.add( new Movie("COMEDY", "movie2"));
Map<String, List<Movie>>postsPerType = posts.stream()
.collect(groupingBy(Movie::getType));
postsPerType.forEach((k,v) -> System.out.println("Key = "
+ k + ", Value = " + v.size()));
Map<String, List<Movie>>postsPerName = posts.stream()
.collect(groupingBy(Movie::getName));
postsPerName.forEach((k,v) -> System.out.println("Key = "
+ k + ", Value = " + v.size()));
}
}
Key = HORROR, Value = 3
Key = COMEDY, Value = 4
Key = movie3, Value = 1
Key = movie2, Value = 2
Key = movie1, Value = 4