高级 java 8 流使用问题

Issue with advanced java 8 stream usage

我正在尝试使用 java 8 个流来对 MessageMember.

的列表执行操作

A Message 是我的域模型中的一个实体。 Message 有一个 sender 字段和一个类型为 Memberreceiver 字段。

A Member 是我的域模型中的第二个实体。 Member 有一个已发送消息的集合和一个已接收消息的集合。

成员 JPA 实体:

@Entity
public class Member implements UserDetails {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    ...    

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "sender")
    private Collection<Message> sentMessages;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "recipient")
    private Collection<Message> receivedMessages;

    @Version
    private Integer version;

消息 JPA 实体:

@Entity
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    private Member sender;

    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    private Member recipient;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(pattern = "dd/MM/yyyy HH:mm:ss")
    private Date sendDate;

    private boolean messageRead;

    @NotNull
    @Size(min = 5, max = 500)
    @Column(length = 500)
    private String text;

    @Version
    private Integer version;

Collection<Message>(来自给定成员的已发送和已接收消息的组合列表)我想获得一个SortedMap<Message, Member>,其中包含来自另一个成员的最新已发送或已接收消息作为键另一个成员作为值。

任何人都可以提供有关如何使用 java 8 个流来实现此目的的指示或想法吗?

编辑 1:这里是一个示例 input/output 所要求的:

输入(messagetable的内容):

#2 成员的输出应该是 #5 和 #4

的消息

说明:成员#2与成员#1最新发送或接收的消息为消息#4 成员 #2 和成员 #6 的最新消息是消息 #5.

成员 #2 和成员 #1 之间的其他消息较旧,因此未考虑在内。

最终目标是实现一个消息框,例如 whatsapp/hangout 或 fb,借此显示当前登录用户与其他每个用户之间最后发送或接收的消息。

您可以按收件人对发送的邮件进行分组,然后为每个收件人查找最新的邮件(未测试,可能有一些错别字):

Map<Member,Message>
  memberMessages = 
      sentMessages.stream()
                  .collect(Collectors.groupingBy(Message::getRecipient, 
                                                 Collectors.maxBy(Comparator.comparingLong(m -> m.getSendDate().getTime())))));

这为您提供了与您想要的相反的键和值,但它应该可以帮助您入门。例如,您可以通过创建此映射条目的 Stream 然后使用 toMap 收集它来创建反向映射,并在该过程中反转键和值。

我不确定是否可以在单个 Stream 管道中完成整个过程。

如果我理解得很好,这(不是单行)是一种可能的实现方式:

SortedMap<Message, Member> sortedMap = new TreeMap<>(Comparator.comparing(Message::getSendDate).reversed());
myCombinedCollection.forEach(m -> sortedMap.put(m, m.getRecipient()));

如果你真的想要单线,应该这样做:

SortedMap<Message, Member> sort = myCombinedCollection.stream()
                                                      .collect(Collectors.toMap(m -> m,
                                                                                Message::getRecipient, 
                                                                                (m1, m2) -> m2,
                                                                                () -> new TreeMap<>(Comparator.comparing(Message::getSendDate).reversed())));

toMap收集器可以分解如下:

  • 对于流中的每个 Message
  • 构造一个新条目,以消息为键,收件人成员为值
  • 您可以将其放入 TreeMap 中,将消息与发送日期进行比较
  • 如果两条消息的发送日期相同,则只保留一条


由于您的编辑,映射实际上更复杂,因为您实际上必须考虑消息的发送日期以及发送者和接收者之间的映射。因此,您必须创建一个 returns 唯一映射的函数(警告:您需要 1->2 必须与具有 2->1 的映射相同)。

这是我使用的映射方法(您可能想实现自己的;这只是为了示例):

public String mapping() {
    long minId = Math.min(sender.id, recipient.id);
    long maxId = Math.max(sender.id, recipient.id);
    return (maxId + "->" + minId);
}

完成后,将消息放入映射中,并使用下游收集器获取最新消息:

  Map<String, Optional<Message>> map = myCombinedCollection.stream()
                         .collect(groupingBy(Message::mapping, maxBy(Comparator.comparing(Message::getSendDate))));

这导致:

6->2 => Optional[5-Thu Apr 01 00:00:00 CEST 3915:Hi there dude]
2->1 => Optional[4-Fri Apr 02 09:45:00 CEST 3915:Je suis disponible]

密钥在这里并不重要,因为您只对消息感兴趣。你可以做的是从这个映射中得到一个List<Message>,首先过滤空值:

List<Message> messages = map.entrySet().stream()
                                       .filter(e -> e.getValue().isPresent())
                                       .map(e -> e.getValue().get())
                                       .collect(toList());

最终输出:

[5-Thu Apr 01 00:00:00 CEST 3915:Hi there dude, 4-Fri Apr 02 09:45:00 CEST 3915:Je suis disponible]

如果你想看一个小实现,我创建了这个小 gist


我不知道那些藏品是如何填满的;但也许值得为每个成员实现一个缓存映射,以便在新消息 sent/received 时更新映射。这比获取所有合并的消息更有效。

希望对您有所帮助! :)