【问题标题】:Nested Loop to Java Stream Puzzle嵌套循环到 Java 流拼图
【发布时间】:2018-07-19 00:38:26
【问题描述】:

我是 Java 8 中的流的新手。我正在努力弄清楚如何使用流在列表上实现嵌套的 for 循环。一个列表包含消息,消息中的一个字段是作者的 id(遗憾的是,不是 obj 引用)。另一个列表是作者,作者中的一个字段是作者 ID。代码需要为消息列表中的每条消息在作者列表中找到匹配的作者。我拥有的工作 Java 代码(不使用流或 lambdas)如下,我的问题是如何使用流重写它?我已经尝试了几件事,我认为需要一个 flatMap 和一个过滤器,但我似乎无法做到。

public void showAuthors(List<Message> messages, List<Authors> authors) {
    String authorName=null;
    for (Message message : messages) {
        int authorId = message.getAuthorId();
        for (Authors author : authors) {
            if (author.getId() == authorId)
                authorName = author.getFullName();
                break;
        }
        System.out.println("Message id is " + message.getId() + 
                           " author is " + authorName);
    }
}

我在这里查看了一些类似的问题,但我似乎无法正确回答。

感谢任何帮助!

==============================================

更新:作者列表中有一些 Id 字段的空数据。这要求我使用带有流操作的过滤器来创建 AuthorId,Name 的映射。

【问题讨论】:

    标签: java java-stream


    【解决方案1】:

    由于您正在进行mxn 比较,因此最好在authorId -&gt; author 之间创建一个映射以避免O(n) 查找作者:

    Map<Integer, Author> authorMaps = authors.stream().collect(toMap(Author::getId, Function.identity()));
    

    现在您可以像这样获取每条消息的作者:

    messages.stream().filter(m -> authorMaps.containsKey(m.getAuthorId()))
            .forEach(m -> 
                System.out.println("Message id is " + m.getId() + 
                               " author is " + authorMaps.get(m.getAuthorId()). getFullName());
            );
    

    【讨论】:

    • 谢谢。我试过这个并意识到(在 NPE 之后,一些作者对象没有 ID。所以我想我可以在 authorMaps 的创建中添加一个.filter,是吗?
    • 检查前添加.filter
    【解决方案2】:

    首先对我来说,您当前的逻辑是错误的。如果第一次不满足条件并且对于所有消息你得到以前的作者或像这样的一些无效结果,它会中断内部循环。

    Message id is 1 author is AuthOne
    Message id is 2 author is AuthOne
    Message id is 4 author is AuthOne
    

    这是更正后的。

    public static void showAuthors(List<Message> messages, List<Author> authors) {
        String authorName = null;
        for (Message message : messages) {
            int authorId = message.getAuthorId();
            for (Author author : authors) {
                if (author.getId() == authorId) {
                    authorName = author.getFullName();
                    break;
                }
            }
            System.out.println("Message id is " + message.getId() + " author is " + authorName);
        }
    }
    

    只有在找到第一个 Author 后,您才需要中断循环。这是具有线性时间复杂度的等效 stream 对应项。

    Map<Integer, String> authorsById = authors.stream()
            .collect(Collectors.toMap(Author::getId, Author::getFullName));
    Map<Integer, String> authorNameAgainstId = messages.stream()
            .collect(Collectors.toMap(Message::getId, m -> authorsById.get(m.getAuthorId())));
    

    【讨论】:

    • 逻辑上完全正确,忘记了 if 语句上的大括号。粗心大意。
    • 感谢您的帮助。
    【解决方案3】:

    在尝试了上面的一些建议之后,我很快意识到作者数据中有一些没有 ID 的对象,只有名称(显然它们有双重目的......这不是我的设计!)。

    所以,我根据上述答案的帮助/提示写了以下内容:

    public void showAuthors(List<Message> messages, List<Reference> authors) {
    Map<Integer, String> authorsById = authors.stream()
        .filter(author -> !(author.getId() == null))
        .filter(name -> !(name.getFullName() == null))
        .collect(Collectors.toMap(Reference::getId, Reference::getFullName));
    
    messages.forEach(
        message -> System.out.println("Message id is " + message.getId() + 
                  " Author is " + authorsById.get(message.getSenderId())));
    

    这个解决方案有什么问题吗?

    另外,我是 stackoverflow 的新手(我相信您会说出来)...有没有办法可以为所有 3 位响应者提供帮助?这就是点赞的目的吗?

    梅格

    【讨论】:

    • 另外,一些作者对象有id但没有名字。
    • 一般来说这是可以的方法,问题是关于可能每一步的空检查。编写生产代码时应了解每类输入会发生什么,并且您在生产中遇到的每个此类情况都应包含在单元测试中。例如,原始代码会跳过没有正确作者的消息,而您的代码会打印 messageId+null author name。是内嵌的吗?至于给所有人一些东西 - 如果您满意并想要“关闭主题”,您需要选择 1 个答案,但请随意投票。
    • 谢谢亚历山大。当我尝试投票时,它告诉我,因为我太新了,它会记录投票,但不会增加总数......嘘!
    【解决方案4】:

    消息处理不需要流,这里 forEach 更合适。不过,流对于作者列表到地图的转换很有用。

    您可以使用带有 3 个参数的 toMap 来避免作者列表中可能的作者重复(原始代码避免了这种情况)。

    您可以使用 Optional 来避免可能缺少 authos、null id 等 - 每次 .map 返回 null 时,Optional 都会变为 Optional::Empty,因此对其的进一步处理会跳到终端操作,而 ifPresence 只是跳过 empty选项。

    Map<Integer, String> authorNames=authors.stream()
        .collect(Collectors.toMap(
            Author::getId,
            Author::getFullName,
            (oldValue, newValue) -> oldValue));
    
    messages.forEach(
        m -> Optional.of(m)
            .map(Message::getAuthorId)
            .map(authorNames::get)
            .map(name -> "Message id is " + m.getId() + " author is " + name)
            .ifPresent(System.out::println)
    );
    

    【讨论】:

    • 没有理由将Optional 用于不应该是可选的东西。这只是使整个代码复杂化。与 messages.forEach( m -&gt; Optional.of(authorNames.get(m.getAuthorId())) .ifPresent(name -&gt; System.out.println("Message id is " + m.getId() + " author is " + name)) ); 相比,如果 ID 不唯一,我们应该将其视为错误,因此,您不应该提供像 (oldValue, newValue) -&gt; oldValue 这样的合并函数,它会在出现此类错误时静默丢弃项目。
    • 如果没有作者,您的 Optional.of 将简单地抛出 NPE。在这种情况下旧代码跳过消息 - 新代码应该做同样的事情,除非你有直接的问题来修复它(并且 M Watson 没有表达它)。旧代码也仅适用于重复的 ID,这又是一个正确规范的问题。
    • 是的,它应该是Optional.ofNullable,否则我没有使用Optional,这就是我评论的实际意义,不要用Optional使代码复杂化不是可选的。说到“新代码应该做同样的事情”,旧代码将匹配null ID,而您使用Optional 导致将null ID 视为永远不匹配。关于重复,旧代码不处理它们,只是由于短路而忽略它们。这与显式使用重复删除合并函数不同。
    • 有什么理由不使用这样的东西:作者的stream 变成map(过滤掉空ID),然后是messages.forEach 打印出消息ID 和作者?
    • @Holger 旧代码将在“int authorId =”行中抛出 NPE,因此它不会在 null 作者的 id 中存活,因此会跳过消息中的 null id,但永远不会正确匹配它们。通常,单独行中的直接函数引用序列比包含在可选构造函数中的直接计算组合更具可读性。如果您使用函数引用,编译器优化器会在这种情况下生成良好的代码 - 与直接点符号序列一样有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-05
    相关资源
    最近更新 更多