【问题标题】:Java Stream API - Group by items of an object's inner listJava Stream API - 按对象内部列表的项目分组
【发布时间】:2021-10-31 22:24:58
【问题描述】:

有没有办法实现以下示例代码,利用 Java Stream API 而不必创建 HashMap 并将其填充到双 forEaches 中?我试图玩弄groupingByflatMap,但找不到出路。

有一个电影列表,其中每一个都有一个流派列表(字符串)...

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)));

【问题讨论】:

    标签: java java-stream


    【解决方案1】:

    这很棘手,因为您不能为每个 Movie 定义一个键,因为这样的对象可以出现在多个键下。

    据我所知,最佳解决方案与您的解决方案相同:

    Map<String, List<Movie>> groupedMovies = new HashMap<>();
    movies.forEach(movie -> {
        movie.getGenres().forEach(genre ->
            groupedMovies.computeIfAbsent(genre, g -> new ArrayList<>()).add(movie)
        );
    });
    

    如果你想把这个 sn-p “转换”成,你必须从你所拥有的开始——这是个别的流派。使用flatMapdistinct 从每个Movie 中提取它们以避免重复。然后使用Collector.toMap 获得所需的输出。

    • 密钥: 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())));
    

    第一个 sn-p 中的程序方法更快,更易于阅读和理解。我不建议在这里使用


    注意..在stream之后使用forEach没有任何意义:list.stream().forEach(...)的序列可以改为list.forEach(...)

    【讨论】:

      【解决方案2】:

      首先我们可能要创建一个PairTuple,如下所示:

          public static class Pair {
              Movie movie;
              String genre;
      
              public Pair(Movie movie, String genre) {
                  this.movie = movie;
                  this.genre = genre;
              }
             
             // Getters omitted
      
          }
      

      这在 Java 17 中也可以是 Record

      接下来,我们可以用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

      【讨论】:

        【解决方案3】:
        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-08
          • 1970-01-01
          • 2012-04-25
          • 2017-09-26
          相关资源
          最近更新 更多