Java流拼图的嵌套循环

时间:2018-07-19 00:38:27

标签: java java-stream

我对Java 8中的流是陌生的。我正在努力寻找如何使用流在列表上实现嵌套的for循环的方法。一个列表包含消息,消息中的字段之一是作者的ID(可悲的是,不是obj引用)。另一个列表是作者,作者中的一个字段是作者ID。该代码需要在作者列表中为消息列表中的每条消息找到匹配的作者。我拥有的有效Java代码(不使用流或lambda)如下所示,我的问题是如何使用流重写此代码?我尝试了几件事,我认为将需要一个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的映射。

4 个答案:

答案 0 :(得分:1)

由于您要进行mxn比较,因此最好在authorId -> author之间创建一个Map,以避免对作者进行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());
        );

答案 1 :(得分:1)

首先,对我来说,您当前的逻辑是错误的。如果第一次不满足该条件,并且对于所有消息,您将获得先前的作者或类似的无效结果,则它将破坏内部循环。

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

答案 2 :(得分:1)

尝试了上面的一些建议之后,我很快意识到作者数据中的某些对象没有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位响应者有所帮助?那是为了什么?

梅格

答案 3 :(得分:0)

您不需要流来处理消息,这里forEach更合适。尽管如此,流对于作者列表进行映射转换还是有用的。

您可以将toMap与3个参数一起使用,以避免作者列表中的作者重复(原始代码避免了这一点)。

您可以使用Optional来避免可能缺少身份验证,空ID等。-每次.map返回null时,Optional变为Optional :: Empty,因此对它的进一步处理将跳至终端操作,并且ifPresence只是跳过为空

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