如何从键的 Map<K,V> 和 List<K> 创建一个 List<T>?
How to create a List<T> from Map<K,V> and List<K> of keys?
使用 Java 8 个 lambda,在给定 List<K>
个可能的键和 Map<K,V>
的情况下,"best" 有效创建新 List<T>
的方法是什么?在这种情况下,您将获得 List
个可能的 Map
键,并期望生成一个 List<T>
,其中 T
是基于某些方面构建的某种类型V
,映射值类型。
我已经探索了一些,并且觉得声称一种方法比另一种方法更好(可能有一个例外——见代码)感到不舒服。我将 "best" 解释为代码清晰度和运行时效率的结合。这些是我想出的。我相信有人可以做得更好,这是这个问题的一个方面。我不喜欢 most 的 filter
方面,因为它意味着需要创建中间结构并多次传递名称 List
。现在,我选择示例 6 —— 一个普通的 'ol 循环。 (注意:代码注释中有一些神秘的想法,尤其是 "need to reference externally..." 这意味着在 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;
}
}
如果 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
不能同时改变,理论上它可以并行化。
I don't like the filter
aspect of most as it means needing to create intermediate structures and multiple passes over the names List
.
没有中间结构,List
只有一次通过。流管道表示“对于每个元素……执行此操作序列”。每个元素被访问一次并应用管道。
以下是来自 java.util.stream
package description 的一些相关引述:
A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
Processing streams lazily allows for significant efficiencies; in a pipeline such as the filter-map-sum example above, filtering, mapping, and summing can be fused into a single pass on the data, with minimal intermediate state.
我没有看到的一种方法是 retainAll
:
public List<Bongo> getBongos(List<String> names) {
Map<String, Wongo> copy = new HashMap<>(nameToWongoMap);
copy.keySet().retainAll(names);
return copy.values().stream().map(Bongo::new).collect(
Collectors.toList());
}
额外的 Map 对性能的影响很小,因为它只是复制指向对象的指针,而不是对象本身。
我认为 非常成功。那里给出的解决方案:
return names.stream()
.map(namesToWongoMap::get)
.filter(Objects::nonNull)
.map(Bongo::new)
.collect(toList());
可能是 Java 8.
中可以做到的最好的
不过,我确实想提一下这里的一个小问题。如果名称不在地图中,Map.get
调用 returns null
,随后将其过滤掉。这个 本身 没有任何问题,尽管它确实将 null-means-not-present 语义烘焙到管道结构中。
从某种意义上说,我们希望映射器管道操作可以选择 returning 零个或一个元素。使用流来执行此操作的一种方法是使用 flatMap
。 flatmapper 函数可以 return 将任意数量的元素放入流中,但在这种情况下我们只需要零个或一个。方法如下:
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
的使用确实指向另一种方法。如果您对如何处理不存在的情况有更复杂的策略,则可以将其重构为一个辅助函数,该函数 return 是一个包含结果的 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 answer and this answer。
使用 Java 8 个 lambda,在给定 List<K>
个可能的键和 Map<K,V>
的情况下,"best" 有效创建新 List<T>
的方法是什么?在这种情况下,您将获得 List
个可能的 Map
键,并期望生成一个 List<T>
,其中 T
是基于某些方面构建的某种类型V
,映射值类型。
我已经探索了一些,并且觉得声称一种方法比另一种方法更好(可能有一个例外——见代码)感到不舒服。我将 "best" 解释为代码清晰度和运行时效率的结合。这些是我想出的。我相信有人可以做得更好,这是这个问题的一个方面。我不喜欢 most 的 filter
方面,因为它意味着需要创建中间结构并多次传递名称 List
。现在,我选择示例 6 —— 一个普通的 'ol 循环。 (注意:代码注释中有一些神秘的想法,尤其是 "need to reference externally..." 这意味着在 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;
}
}
如果 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
不能同时改变,理论上它可以并行化。
I don't like the
filter
aspect of most as it means needing to create intermediate structures and multiple passes over the namesList
.
没有中间结构,List
只有一次通过。流管道表示“对于每个元素……执行此操作序列”。每个元素被访问一次并应用管道。
以下是来自 java.util.stream
package description 的一些相关引述:
A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
Processing streams lazily allows for significant efficiencies; in a pipeline such as the filter-map-sum example above, filtering, mapping, and summing can be fused into a single pass on the data, with minimal intermediate state.
我没有看到的一种方法是 retainAll
:
public List<Bongo> getBongos(List<String> names) {
Map<String, Wongo> copy = new HashMap<>(nameToWongoMap);
copy.keySet().retainAll(names);
return copy.values().stream().map(Bongo::new).collect(
Collectors.toList());
}
额外的 Map 对性能的影响很小,因为它只是复制指向对象的指针,而不是对象本身。
return names.stream()
.map(namesToWongoMap::get)
.filter(Objects::nonNull)
.map(Bongo::new)
.collect(toList());
可能是 Java 8.
中可以做到的最好的不过,我确实想提一下这里的一个小问题。如果名称不在地图中,Map.get
调用 returns null
,随后将其过滤掉。这个 本身 没有任何问题,尽管它确实将 null-means-not-present 语义烘焙到管道结构中。
从某种意义上说,我们希望映射器管道操作可以选择 returning 零个或一个元素。使用流来执行此操作的一种方法是使用 flatMap
。 flatmapper 函数可以 return 将任意数量的元素放入流中,但在这种情况下我们只需要零个或一个。方法如下:
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
的使用确实指向另一种方法。如果您对如何处理不存在的情况有更复杂的策略,则可以将其重构为一个辅助函数,该函数 return 是一个包含结果的 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 answer and this answer。