使用流处理异常


10

我有一个,Map<String,List<String>>并希望将其变成,Map<String,List<Long>>因为String列表中的每个代表一个Long

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(Long::valueOf)
                                                      .collect(toList()))
               );

我主要的问题是每个人String可能不能正确代表一个Long; 可能有一些问题。Long::valueOf可能会引发例外情况。如果是这种情况,我想返回一个null或空Map<String,List<Long>>

因为我想遍历这张output地图。但是我不能接受任何错误转换。甚至没有一个。关于如何在不正确的String-> Long转换的情况下如何返回空输出的任何想法?


我同意Naman解决方案,但是不幸的是,在catch块中,我无法检索到字符串->长转换不正确的键(Entry :: getKey)
AntonBoarf

这里类似的讨论:字符串到整数-可能坏的数据需要避免出现异常,最终我决定使用regex进行预检查(parseLong文档使用相同的解析规则,并且LongStream如果您打算删除empty结果,则可能要返回a )
AjahnCharles

对不起,我误会了。我以为您要返回的单个条目为空/空;但是现在我想你是说整个地图!
AjahnCharles

1
目前尚不清楚主要要点是什么-您想在出现错误的情况下返回一个空的映射,但仍然在控制台出现错误的地方打印“键”吗?我的意思是,有关异常出现的上下文的信息通常异常中沿调用堆栈传输。无论如何:您专门询问了流,但是我强烈建议您避免嵌套的“收集”调用。后来不得不维护它的人(这很可能会成为您的未来!)会想知道您在这里做了什么。至少介绍一些正确命名的辅助方法。
Marco13

Answers:


4

明确catch的例外情况如何:

private Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    try {
        return input.entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                        .map(Long::valueOf)
                        .collect(Collectors.toList())));
    } catch (NumberFormatException nfe) {
        // log the cause
        return Collections.emptyMap();
    }
}

好的听起来不错...但是在catch(nfe)中,我想检索键(Entry :: getKey)和不正确的String的特定值,该值会失败,因此我可以准确地记录错误的地方。可能吗 ?
AntonBoarf,

@AntonBoarf如果您只想记录密钥,但无法解析该字符串,请使用nfe.getMessage()
Naman

1
@AntonBoarf异常的消息将包含格式错误的输入字符串。为了获得负责任的密钥,只有在发生异常时,我才进行显式搜索,例如input.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger

@Holder。谢谢...这似乎很复杂...我想知道在我的情况下使用
斯坦达特

@AntonBoarf只需实现两者并进行比较……
Holger

3

我个人喜欢提供有关Optional数字解析的输入:

public static Optional<Long> parseLong(String input) {
    try {
        return Optional.of(Long.parseLong(input));
    } catch (NumberFormatException ex) {
        return Optional.empty();
    }
}

然后,使用您自己的代码(并忽略错误的输入):

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(MyClass::parseLong)
                                                      .filter(Optional::isPresent)
                                                      .map(Optional::get)
                                                      .collect(toList()))
               );

此外,请考虑使用辅助方法来使其更加简洁:

public static List<Long> convertList(List<String> input) {
    return input.stream()
        .map(MyClass::parseLong).filter(Optional::isPresent).map(Optional::get)
        .collect(Collectors.toList());
}

public static List<Long> convertEntry(Map.Entry<String, List<String>> entry) {
    return MyClass.convertList(entry.getValue());
}

然后,您可以在流的收集器中过滤结果:

Map<String, List<Long>> converted = input.entrySet().stream()
    .collect(Collectors.toMap(Entry::getKey, MyClass::convertEntry));

您还可以将空Optional对象保留在列表中,然后通过将它们在新索引List<Optional<Long>>(而不是索引)中的索引List<Long>与原始索引进行比较List<String>,可以找到导致任何错误输入的字符串。您也可以将这些失败记录在MyClass#parseLong

但是,如果您希望根本不对任何错误的输入进行操作,那么按照您的尝试(按照纳曼的回答),将整个流程围绕在您尝试捕获的内容中(按照纳曼的回答)。


2

您可以创建StringBuilder带有例外的for键,然后检查是否ele为数字,如下所示,

 public static Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    StringBuilder sb = new StringBuilder();
    try {
    return input.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                    .map(ele->{
                        if (!StringUtils.isNumeric(ele)) {
                            sb.append(e.getKey()); //add exception key
                            throw new NumberFormatException();
                        }
                        return Long.valueOf(ele);
                    })
                    .collect(Collectors.toList())));
} catch (NumberFormatException nfe) {
    System.out.println("Exception key "+sb);
    return Collections.emptyMap();
}
}

希望能帮助到你。


0

可能是您可以编写一个辅助方法,该方法可以检查字符串中的数字并从流中过滤掉它们,还可以将null值过滤掉,然后最终收集到Map中。

// StringUtils.java
public static boolean isNumeric(String string) {
    try {
        Long.parseLong(string);
        return true;
    } catch(NumberFormatException e) {
        return false;
    }
}

这会照顾好一切。

并在您的流中使用它。

Map<String, List<Long>> newMap = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> mapToLongValues(entry.getValue())));

public List<Long> mapToLongValues(List<String> strs) {
    return strs.stream()
        .filter(Objects::nonNull)
        .filter(StringUtils::isNumeric)
        .map(Long::valueOf)
        .collect(Collectors.toList());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.