使用Java 8流来变换带有空的地图



我正在处理键和/或值中具有null条目的Map<String,String>

Map<String, String> headers = new HashMap<>();
headers.put("SomE", "GreETing");
headers.put("HELLO", null);
headers.put(null, "WOrLd");
headers.keySet().stream().forEach(k -> System.out.println(k + " => " + copy.get(k)));

我将获得以下输出:

SomE => GreETing
HELLO => null
null => WOrLd

我需要转换地图,因此所有非编号值都转换为小写,例如:

some => greeting
hello => null
null => world

我正在尝试使用Java 8流API,但是以下代码抛出NullPointerException

Map<String,String> copy
    = headers.entrySet()
             .stream()
             .collect(
                 Collectors.toMap(
                     it -> it.getKey() != null ? it.getKey().toLowerCase() : null,
                     it -> it.getValue() != null ? it.getValue().toLowerCase() : null));
copy.keySet().stream().forEach(k -> System.out.println(k + " => " + copy.get(k)));

如果我评论了最后两个地图条目,则该程序将执行,因此当键或值为null时,Collectors.toMap的工作原理必须存在问题。如何使用流API来解决此问题?

问题是toMap()调用正在构建的merge()函数的基础映射实现,该函数不允许值为null

来自Javadoc的Map#merge(重点是我的)

如果指定的密钥尚未与值相关联或与NULL关联,则将其与给定的非null 值相关联。否则,将关联的值替换为给定重新映射函数的结果,或者如果结果为null,则删除。

因此,使用Collectors.toMap()将不起作用。

您可以做到这一点,而无需流很好:

Map<String,String> copy = new HashMap<>();
for(Entry<String, String> entry : headers.entrySet()){
    copy.put(entry.getKey() !=null ? entry.getKey().toLowerCase() : null, 
             entry.getValue() !=null ? entry.getValue().toLowerCase() : null
            );
}

使用收集:

final Function<String, String> fn= str -> str == null ? null : str.toLowerCase();
Map<String, String> copy = headers.entrySet().stream()
   .collect(HashMap::new,
            (m, e) -> m.put(fn.apply(e.getKey()), fn.apply(e.getValue())), 
            Map::putAll);

或abacus-common

Map<String, String> copy = Stream.of(headers)
   .collect(HashMap::new, 
     (m, e) -> m.put(N.toLowerCase(e.getKey()), N.toLowerCase(e.getValue())));

在2/4上更新,或:

Map<String, String> copy = EntryStream.of(headers)
   .toMap(entry -> N.toLowerCase(entry.getKey()), entry -> N.toLowerCase(entry.getValue()));

您无法在没有NPE的情况下使用Collectors.toMap(),因为您已经在map中存在null value,如@dkatzel所述,但我仍然想使用Stream API;

>
Map<String, String> headers = new HashMap<>();
headers.put("good", "AsDf");
headers.put("SomE", "GreETing");
headers.put("HELLO", null);
headers.put(null, "WOrLd");
new HashSet<>(headers.entrySet()).stream()
    .peek(entry -> entry.setValue(Objects.isNull(entry.getValue()) ? null : entry.getValue().toLowerCase()))
    .filter(entry -> !Objects.isNull(entry.getKey()) && !entry.getKey().equals(entry.getKey().toLowerCase()))
    .forEach(entry -> {
        headers.put(entry.getKey().toLowerCase(), entry.getValue());
        headers.remove(entry.getKey());
    });
System.out.println(headers);

打印出;

{null=world, some=greeting, hello=null, good=asdf}

如果您的检查复杂,并且仍然想使用collector.tomap()而无需nulls

// convert data to a final key, value pairs using Pair.of(key, value)
.map(data -> {
    SomeKey key = makeKey(data.getKey());
    SomeVal val = makeValue(data.getValue());
    
    // validate and return 
    if(key.foo != null && val.bar != null) {
        return Pair.of(key.foo, val.bar);
    }
    return null;
})
.filter(Objects::nonNull)   // remove the ones that don't satisy criteria
.collect(Collectors.toMap(pair -> pair.getLeft(), pair -> pair.getRight(),  // then collect final key/value data without NPE checks
    (left, right) -> {
        left.merge(right); // can return null
        return left;
    }
)));

最新更新