【问题标题】:Java 8 convert List to Lookup MapJava 8 将 List 转换为 Lookup Map
【发布时间】:2017-04-06 14:15:11
【问题描述】:

我有一个电台列表,每个电台都有一个收音机列表。我需要创建一个电台到电台的查找地图。我知道如何使用 Java 8 流 forEach 来做到这一点:

stationList.stream().forEach(station -> {
    Iterator<Long> it = station.getRadioList().iterator();
    while (it.hasNext()) {
        radioToStationMap.put(it.next(), station);
    }
});

但我相信应该有更简洁的方式,比如使用Collectors.mapping()

有人可以帮忙吗?

【问题讨论】:

  • 是否保证没有电台映射到多个电台?
  • stationList.stream().forEach(station -&gt; station.getRadioList().stream().forEach(rl -&gt; radioToStationMap.put(rl, station))) 不够好?
  • @ZhenyaM 使用流操作和collect 比使用forEach 写入可变列表更好。
  • 我很确定最“简洁的方法”是根本不使用 lambdas 或 Streams。

标签: java java-8 java-stream lookup collectors


【解决方案1】:

这应该可以工作,您不需要第三方。

stationList.stream()
    .map(s -> s.getRadioList().stream().collect(Collectors.toMap(b -> b, b -> s)))
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

【讨论】:

    【解决方案2】:

    根据问题,考虑将实体RadioStation 定义为:

    @lombok.Getter
    class Radio {
        ...attributes with corresponding 'equals' and 'hashcode'
    }
    
    @lombok.Getter
    class Station {
        List<Radio> radios;
        ... other attributes
    }
    

    可以使用以下实用程序从List&lt;Station&gt; 作为输入创建查找映射:

    private Map<Radio, Station> createRadioToStationMap(final List<Station> stations) {
        return stations.stream()
                // create entries with each radio and station
                .flatMap(station -> station.getRadios().stream()
                        .map(radio -> new AbstractMap.SimpleEntry<>(radio, station)))
                // collect these entries to a Map assuming unique keys
                .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
                        AbstractMap.SimpleEntry::getValue));
    }
    

    与此行为略有不同,如果对于跨多个Stations 的相同(相等)Radio 元素,想要对所有此类站点进行分组,则可以使用groupingBy 而不是toMap 来实现,例如:

    public Map<Radio, List<Station>> createRadioToStationGrouping(final List<Station> stations) {
        return stations.stream()
                .flatMap(station -> station.getRadios().stream()
                        .map(radio -> new AbstractMap.SimpleEntry<>(radio, station)))
                // grouping the stations of which each radio is a part of
                .collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey,
                        Collectors.mapping(AbstractMap.SimpleEntry::getValue, Collectors.toList())));
    }
    

    【讨论】:

      【解决方案3】:

      如果你愿意使用第三方库,有groupByEach from Eclipse Collections的方法:

      Multimap<Radio, Station> multimap = 
          Iterate.groupByEach(stationList, Station::getRadioList);
      

      这也可以使用 Java 8 Streams 和 Eclipse Collections 中的 Collectors2 实用程序编写:

      Multimap<Radio, Station> multimap =
              stationList.stream().collect(
                      Collectors2.groupByEach(
                              Station::getRadioList,
                              Multimaps.mutable.list::empty));
      

      注意:我是 Eclipse Collections 的提交者。

      【讨论】:

        【解决方案4】:

        与像这样的混合解决方案相比,我认为您无法使用收集器以更简洁的方式完成此操作

            stationList.stream().forEach(station -> {
                for ( Long radio : station.getRadioList() ) {
                    radioToStationMap.put(radio, station);
                }
            });
        

            stationList.forEach(station -> {
                station.getRadioList().forEach(radio -> {
                    radioToStationMap.put(radio, station);
                });
            });
        

        (可以直接在集合上调用.forEach,不需要经过.stream()

        我能想出的最短的完全“功能性”的解决方案是这样的

         stationList.stream().flatMap(
             station -> station.getRadioList().stream().map(radio -> new Pair<>(radio, station)))
         .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
        

        使用第三方库中可用的任何 Pair 类。与 Xtend 或 Groovy 等方言相比,Java 8 对于简单操作非常冗长。

        【讨论】:

        • 如果您在radio 中有重复项,您将获得java.lang.IllegalStateException: Duplicate key。为了防止它,您可以在您的解决方案中添加像 Collectors.toMap(Pair::getKey, Pair::getValue, (station, station2) -&gt; station2) 这样的合并
        • 如果有重复,应该用Multimap代替。
        【解决方案5】:

        怎么样:

        radioToStationMap = StreamEx.of(stationList)
                .flatMapToEntry(s -> StreamEx.of(s.getRadioList()).mapToEntry(r -> s).toMap())
                .toMap();
        

        StreamEx

        【讨论】:

          【解决方案6】:

          就像在 Artur Biesiadowski 的回答中一样,我认为您必须创建一个配对列表,然后将它们分组,至少如果您想满足每个电台不是唯一的收音机的情况。

          在 C# 中,您可以使用实用的匿名类,但在 Java 中,您必须至少定义 Pair 类的接口

          interface Radio{ }
          interface Station {
              List<Radio> getRadioList();
          }
          interface RadioStation{
              Station station();
              Radio radio();
          }
          
          List<Station> stations = List.of();
          
          Map<Radio,List<Station>> result= stations
             .stream()
             .flatMap( s-> s.getRadioList().stream().map( r->new RadioStation() {
                  @Override
                  public Station station() {
                      return s;
                  }
          
                  @Override
                  public Radio radio() {
                      return r;
                  }
              }  )).collect(groupingBy(RadioStation::radio, mapping(RadioStation::stations, toUnmodifiableList())));
          

          【讨论】:

            【解决方案7】:

            我们可以通过直接转换为SimpleEntry的Stream,将collectiong的中间步骤保存到Map中,例如:

            Map<Long, Station> result = stationList.stream()
                            .flatMap(station -> station.getRadioList().stream().map(radio -> new SimpleEntry<>(radio, station)))
                            .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
            

            【讨论】:

              【解决方案8】:

              当然,您可以在没有 Streams 的情况下执行此操作,这可能会使其更具可读性。

              Map<Radio, Station> LOOK_UP = new HashMap<>();
              List<Station> stations = ...
              
              
              stations.forEach(station -> {
                  station.getRadios().forEach(radio -> {
                       LOOK_UP.put(radio, station);
                  });
              });
              

              这与普通循环没有太大区别:

              for (Station station : stations) {
                   for (Radio radio : station.getRadios()) {
                        LOOK_UP.put(radio, station);
                   }
              }
              

              这里明显的问题是LOOK_UP::put 将始终替换某个键的值,从而隐藏了您曾经有重复项的事实。例如:

              [StationA = {RadioA, RadioB}]
              [StationB = {RadioB}]
              

              当您搜索RadioB 时,您应该得到什么结果?

              如果你能有这样的场景,显而易见的事情是改变LOOK-UP的定义并使用Map::merge

                  Map<Radio, List<Station>> LOOK_UP = new HashMap<>();
                  List<Station> stations = new ArrayList<>();
              
                  stations.forEach(station -> {
                      station.getRadios().forEach(radio -> {
                          LOOK_UP.merge(radio,
                                        Collections.singletonList(station),
                                        (left, right) -> {
                                            List<Station> merged = new ArrayList<>(left);
                                            merged.addAll(right);
                                            return merged;
                                        });
                      });
                  });
              

              另一种可能性是在有这些映射的时候抛出异常:

              stations.forEach(station -> {
                     station.getRadios().forEach(radio -> {
                          LOOK_UP.merge(radio, station, (left, right) -> {
                               throw new RuntimeException("Duplicate Radio");
                          });
                     });
               });
              

              最后一个 sn-p 的问题在于,您无法真正记录应归咎于非唯一性的 radioleftrightStationss。如果您也想这样做,您将需要使用内部不依赖 Map::mergemerger,例如 this answer

              所以您可以看到,这完全取决于您需要处理的具体方式和内容。

              【讨论】:

                【解决方案9】:

                结果有点不同,但我们可以使用 Java9 提供的 flatMapping 收集器来做到这一点。

                这是你的站课 -

                class Station {
                public List<String> getRadioList() {
                    return radioList;
                }
                
                private List<String> radioList = new ArrayList<>();
                }
                

                以及您要映射的车站列表 -

                        List<Station> list = new ArrayList<>();
                

                下面是让您使用 flatMapping 收集器映射它的代码。

                list.stream().collect(Collectors.flatMapping(station ->
                                    station.getRadioList().stream()
                                            .map(radio ->Map.entry( radio, station)),
                            Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue), (radio, radio2) -> radio2)));
                
                1. 我们会将它们转换为 Map.Entry
                2. 我们将与平面映射收集器一起收集所有这些

                如果不想用flatMapping,其实可以先用FlatMap再collect,这样可读性会更好。

                list.stream().flatMap(station -> station.getRadioList().stream().map(s -> Map.entry(s, station)))
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (radio, radio2) -> radio2)));
                

                【讨论】:

                  猜你喜欢
                  • 2019-01-23
                  • 2015-03-20
                  • 2019-07-31
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多