高级java 8流使用问题

时间:2015-03-02 12:53:40

标签: java java-8 java-stream

我正在尝试使用java 8流来对MessageMember列表执行操作。

Message是我的域模型中的实体。 Message有一个sender字段和一个receiver类型Member字段。

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 :以下是请求的示例输入/输出:

输入(message表的内容):

enter image description here

#2 成员的输出应为#5和#4

的消息

说明:会员#1 与会员#1 的最新发送或接收消息是消息#4 以及涉及会员的最新消息会员#6 #2 是消息#5

会员#2 和会员#1 之间的其他消息较旧,因此不予考虑。

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

2 个答案:

答案 0 :(得分:4)

如果我理解得很好,这可能是一种可能的实施方式:

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中
  • 如果两封邮件的发送日期相同,则只保留一条

<小时/> 自编辑以来,映射实际上更复杂,因为您必须实际获取消息的发送日期以及发送方和接收方之间的映射。因此,您必须创建一个返回唯一映射的函数(警告:您需要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

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

希望它有所帮助! :)

答案 1 :(得分:2)

您可以按收件人对发送的邮件进行分组,然后找到每个收件人的最新邮件(未经过测试,可能有一些拼写错误):

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

这为您提供了与您想要的相反的关键和价值,但它应该让您开始。例如,您可以通过创建此地图条目的流,然后使用toMap收集它来反转该过程中的键和值来创建反转地图。

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