处理流的异常

Handling exceptions with streams

提问人:AntonBoarf 提问时间:10/2/2019 最后编辑:TylerHAntonBoarf 更新时间:5/27/2021 访问量:1688

问:

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

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()))
               );

我的主要问题是每个都可能无法正确表示一个;可能存在一些问题。 可能会引发异常。如果是这种情况,我想返回 null 或空StringLongLong::valueOfMap<String,List<Long>>

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

java-8 java-stream

评论

0赞 AntonBoarf 10/2/2019
我同意 Naman 解决方案,但不幸的是,在 catch 块中,我无法检索 String -> Long 转换不正确的键 (Entry::getKey)
0赞 charles-allen 10/3/2019
类似的讨论在这里: 字符串到 int - 可能坏的数据需要避免我最终决定使用正则表达式进行预检查的异常(parseLong 文档使用相同的解析规则,如果您打算删除结果,您可能希望返回一个)LongStreamempty
0赞 charles-allen 10/3/2019
对不起,我误会了。我以为你的意思是将单个条目返回为空/null;但现在我想你的意思是整个地图!
1赞 Marco13 10/3/2019
目前还不完全清楚要点是什么 - 您想在发生错误时返回一个空地图,但仍然将出现错误的“密钥”打印到控制台?我的意思是,有关出现异常的上下文的信息通常会在异常中的调用堆栈中向上传输。不管怎样:你特别问了关于流的问题,但我强烈建议避免嵌套的“收集”调用。以后必须保持这一点的人(这很可能是未来的你!)会想知道是什么......你在那里做到了。至少引入一些正确命名的帮助程序方法。

答:

4赞 Naman 10/2/2019 #1

如何显式处理异常: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();
    }
}

评论

0赞 AntonBoarf 10/2/2019
好吧,听起来不错......但是在catch(nfe)中,我想检索key(Entry::getKey)的特定值和失败的不正确的字符串,以便我可以准确地记录出错的位置。可能吗?
0赞 Naman 10/2/2019
@AntonBoarf 如果您只想记录无法解析 String 的密钥,请使用nfe.getMessage()
1赞 Holger 10/2/2019
@AntonBoarf异常的消息将包含格式错误的输入字符串。为了获得负责的密钥,我会进行显式搜索,仅在异常发生时进行,例如input.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
0赞 AntonBoarf 10/2/2019
@Holder。谢谢。。。这似乎很复杂......我想知道在我的情况下使用标准循环 Java5 是否更好
0赞 Holger 10/2/2019
@AntonBoarf只需实现两者并进行比较......
1赞 Vikas 10/2/2019 #2

您可以创建一个有异常的 for 键,并检查是否为数字,如下所示,StringBuilderele

 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();
}
}

希望它有所帮助。

5赞 Rogue 10/3/2019 #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));

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

但是,如果您的愿望是完全不对任何错误的输入进行操作,那么将整个流包围在你试图捕获的内容中(根据 Naman 的回答)是我会采取的路线。

0赞 the_tech_maddy 10/3/2019 #4

也许你可以编写一个辅助方法,可以检查字符串中的数字,并将它们从流和空值中过滤掉,然后最终收集到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());
}