如何从键的地图<K,V>和列表<K>创建List <T>?

Mad*_*nan 16 java java-8 java-stream

使用Java 8 lambdas,有效创建新的List<T>给定的List<K>密钥和"a "的"最佳"方法是Map<K,V>什么?在这种情况下,您将获得一些List可能的Map键,并且应该生成一个List<T>where T,这是基于Vmap值类型的某些方面构造的某种类型.

我已经探索了一些并且感到不舒服声称一种方式比另一种方式更好(可能有一个例外 - 参见代码).我将澄清"最佳"作为代码清晰度和运行时效率的组合.这些是我想出来的.我相信有人可以做得更好,这是这个问题的一个方面.我不喜欢filter大多数方面,因为它意味着需要创建中间结构和多次传递名称List.现在,我选择了例6 - 一个简单的'ol循环.(注意:代码注释中有一些神秘的想法,特别是"需要外部引用......"这意味着从lambda外部.)

public class Java8Mapping {
    private final Map<String,Wongo> nameToWongoMap = new HashMap<>();
    public Java8Mapping(){
        List<String> names = Arrays.asList("abbey","normal","hans","delbrook");
        List<String> types = Arrays.asList("crazy","boring","shocking","dead");
        for(int i=0; i<names.size(); i++){
            nameToWongoMap.put(names.get(i),new Wongo(names.get(i),types.get(i)));
        }
    }

    public static void main(String[] args) {
        System.out.println("in main");
        Java8Mapping j = new Java8Mapping();
        List<String> testNames = Arrays.asList("abbey", "froderick","igor");
        System.out.println(j.getBongosExample1(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", ")));
        System.out.println(j.getBongosExample2(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", ")));
        System.out.println(j.getBongosExample3(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", ")));
        System.out.println(j.getBongosExample4(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", ")));
        System.out.println(j.getBongosExample5(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", ")));
        System.out.println(j.getBongosExample6(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", ")));
    }

    private static class Wongo{
        String name;
        String type;
        public Wongo(String s, String t){name=s;type=t;}
        @Override public String toString(){return "Wongo{name="+name+", type="+type+"}";}
    }

    private static class Bongo{
        Wongo wongo;
        public Bongo(Wongo w){wongo = w;}
        @Override public String toString(){ return "Bongo{wongo="+wongo+"}";}
    }

    // 1:  Create a list externally and add items inside 'forEach'.
    //     Needs to externally reference Map and List
    public List<Bongo> getBongosExample1(List<String> names){
        final List<Bongo> listOne = new ArrayList<>();
        names.forEach(s -> {
                  Wongo w = nameToWongoMap.get(s);
                  if(w != null) {
                      listOne.add(new Bongo(nameToWongoMap.get(s)));
                  }
              });
        return listOne;
    }

    // 2: Use stream().map().collect()
    //    Needs to externally reference Map
    public List<Bongo> getBongosExample2(List<String> names){
        return names.stream()
              .filter(s -> nameToWongoMap.get(s) != null)
              .map(s -> new Bongo(nameToWongoMap.get(s)))
              .collect(Collectors.toList());
    }

    // 3: Create custom Collector
    //    Needs to externally reference Map
    public List<Bongo> getBongosExample3(List<String> names){
        Function<List<Wongo>,List<Bongo>> finisher = list -> list.stream().map(Bongo::new).collect(Collectors.toList());
        Collector<String,List<Wongo>,List<Bongo>> bongoCollector =
              Collector.of(ArrayList::new,getAccumulator(),getCombiner(),finisher, Characteristics.UNORDERED);

        return names.stream().collect(bongoCollector);
    }
    // example 3 helper code
    private BiConsumer<List<Wongo>,String> getAccumulator(){
        return (list,string) -> {
            Wongo w = nameToWongoMap.get(string);
            if(w != null){
                list.add(w);
            }
        };
    }
    // example 3 helper code
    private BinaryOperator<List<Wongo>> getCombiner(){
        return (l1,l2) -> {
            l1.addAll(l2);
            return l1;
        };
    }

    // 4: Use internal Bongo creation facility
    public List<Bongo> getBongosExample4(List<String> names){
        return names.stream().filter(s->nameToWongoMap.get(s) != null).map(s-> new Bongo(nameToWongoMap.get(s))).collect(Collectors.toList());
    }

    // 5: Stream the Map EntrySet.  This avoids referring to anything outside of the stream, 
    // but bypasses the lookup benefit from Map.
    public List<Bongo> getBongosExample5(List<String> names){
        return nameToWongoMap.entrySet().stream().filter(e->names.contains(e.getKey())).map(e -> new Bongo(e.getValue())).collect(Collectors.toList());
    }

    // 6: Plain-ol-java loop
    public List<Bongo> getBongosExample6(List<String> names){
        List<Bongo> bongos = new ArrayList<>();
        for(String s : names){
            Wongo w = nameToWongoMap.get(s);
            if(w != null){
                bongos.add(new Bongo(w));
            }
        }
        return bongos;
    }
}
Run Code Online (Sandbox Code Playgroud)

Rad*_*def 11

如果namesToWongoMap是实例变量,则无法真正避免捕获lambda.

您可以通过拆分操作来清理流:

return names.stream()
    .map(n -> namesToWongoMap.get(n))
    .filter(w -> w != null)
    .map(w -> new Bongo(w))
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)
return names.stream()
    .map(namesToWongoMap::get)
    .filter(Objects::nonNull)
    .map(Bongo::new)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

这样你就不会打get两次电话.

这非常类似于for循环,但是,例如,如果namesToWongoMap不能同时进行变异,它理论上可以并行化.

我不喜欢filter大多数方面,因为它意味着需要创建中间结构和多次传递名称List.

没有中间结构,只有一个通过List.流管道说"对于每个元素......执行这个操作序列".访问每个元素一次并应用管道.

以下是java.util.stream包描述中的一些相关引用:

流不是存储元素的数据结构; 相反,它通过计算操作管道传递来自诸如数据结构,数组,生成器函数或I/O通道的源的元素.

懒惰地处理流可以显着提高效率; 在诸如上面的filter-map-sum示例的流水线中,过滤,映射和求和可以融合到数据的单个传递中,具有最小的中间状态.


Stu*_*rks 7

我认为,Radiodef的答案几乎已经成功了.给出的解决方案:

return names.stream()
    .map(namesToWongoMap::get)
    .filter(Objects::nonNull)
    .map(Bongo::new)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

可能是Java 8中最好的.

不过,我确实想提一下小皱纹.如果地图中不存在该名称,则Map.get返回该调用null,并且随后将其过滤掉.这本身没有任何问题,尽管它确实将null-means-not-present语义加入到管道结构中.

在某种意义上,我们需要一个映射器管道操作,可以选择返回零个或一个元素.使用流来实现此目的的方法是flatMap.flatmapper函数可以将任意数量的元素返回到流中,但在这种情况下,我们只需要零或一个.以下是如何做到这一点:

return names.stream()
    .flatMap(name -> {
        Wongo w = nameToWongoMap.get(name);
        return w == null ? Stream.empty() : Stream.of(w);
    })
    .map(Bongo::new)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

我承认这很笨重,所以我不建议这样做.这是一个略好但有些模糊的方法:

return names.stream()
    .flatMap(name -> Optional.ofNullable(nameToWongoMap.get(name))
                             .map(Stream::of).orElseGet(Stream::empty))
    .map(Bongo::new)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

但我仍然不确定我是否会按原样推荐这个.

然而,使用flatMap确实指向了另一种方法.如果你有一个更复杂的如何处理不存在的情况的策略,你可以将它重构为一个辅助函数,它返回一个包含结果的Stream或一个空流,如果没有结果.

最后,JDK 9 - 在撰写本文时仍处于开发阶段 - 已经添加了Stream.ofNullable在这些情况下非常有用:

return names.stream()
    .flatMap(name -> Stream.ofNullable(nameToWongoMap.get(name)))
    .map(Bongo::new)
    .collect(toList());
Run Code Online (Sandbox Code Playgroud)

另外,还增加了JDK 9 Optional.stream,它创建了一个零或一个流Optional.这在您想要从内部调用Optional-returning函数的情况下非常有用flatMap.有关更多讨论,请参阅此答案此答案.

  • 我已添加[StreamEx.ofNullable(obj)](http://amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html#ofNullable-T-)和[StreamEx.of(Optional)] (http://amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html#of-java.util.Optional-)到我的图书馆.实际上,即使没有第三方库并转移到JDK9,任何人都可以在某些项目特定的实用程序类中创建类似的静态方法并使用它们. (2认同)