如何从Map <k,v>和List <k>创建List <t>?

时间:2015-05-29 20:38:41

标签: java java-8 java-stream

使用Java 8 lambdas,&#34; best&#34;如果有List<T>个可能的密钥和List<K>,有效创建新Map<K,V>的方法?在这种情况下,您可以获得ListMap个密钥,并且预计会生成List<T>,其中T是基于某些方面构建的某种类型的V filter,地图值类型。

我已经探索过一些并且感觉不舒服声称一种方式比另一种方式更好(可能有一个例外 - 参见代码)。我会澄清&#34;最好&#34;作为代码清晰度和运行时效率的组合。这些是我想出来的。我确信有人可以做得更好,这是这个问题的一个方面。我不喜欢大多数List方面,因为这意味着需要在名称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; } } 上创建中间结构和多次传递。现在,我选择了例6 - 一个普通的循环。 (注意:代码注释中有一些神秘的想法,特别是&#34;需要外部引用...&#34;这意味着从lambda外部。

{{1}}

3 个答案:

答案 0 :(得分:11)

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

您可以通过将操作分开一点来清理流:

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

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

这与for循环非常相似,但是,例如,如果namesToWongoMap无法同时进行变异,理论上它可以并行化。

  

我不喜欢大多数人的filter方面,因为这意味着需要在姓名List上创建中间结构和多次传递。

没有中间结构,List只有一次通过。流管道为每个元素说&#34; ...执行这个操作序列&#34;。访问每个元素一次并应用管道。

以下是java.util.stream package description的一些相关引用:

  

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

     

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

答案 1 :(得分:7)

我认为,

Radiodef's answer几乎已经钉了它。给出的解决方案:

return names.stream()
    .map(namesToWongoMap::get)
    .filter(Objects::nonNull)
    .map(Bongo::new)
    .collect(toList());

可能是Java 8中最好的。

但是,我确实想提一下这里的小皱纹。如果地图中没有该名称,则Map.get调用会返回null,之后会将其过滤掉。这个本身没有任何问题,尽管它确实将无意义 - 不存在的语义烘焙到管道结构中。

在某种意义上,我们需要一个映射器管道操作,可以选择返回零个或一个元素。使用流进行此操作的方法是使用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());

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

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

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

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

最后,JDK 9 - 在撰写本文时仍在开发中 - 添加了Stream.ofNullable,这在以下情况下非常有用:

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

另外,JDK 9还添加了Optional.stream,它从Optional创建了一个零或一个流。如果要从flatMap内调用Optional-returning函数,这非常有用。有关详细信息,请参阅this answerthis answer

答案 2 :(得分:3)

我没有看到的一种方法是SELECT DISTINCT SCode INTO #TEMP1 FROM XTransactions_01 WHERE Last_Mdt > '2012-01-01' AND SCode IS NOT NULL SELECT * FROM XSales_Code SC WHERE SC.Status = 1 AND SC.SCode NOT IN ( SELECT Scode FROM #TEMP1 ) AND SC.Last_Mdt < '2014-01-01' ORDER BY Last_Mdt desc DROP TABLE #TEMP1

{{1}}

额外的Map是最小的性能命中,因为它只是复制指向对象的指针,而不是对象本身。