简体   繁体   中英

Issue with advanced java 8 stream usage

I am trying to use java 8 streams in order to perform a manipulation on a list of Message and Member .

A Message is a entity from my domain model. A Message has a sender field and a receiver field of type Member .

A Member is the second entity from my domain model. A Member has a collection of sent messages and a collection of received messages.

Member JPA entity :

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

Message JPA entity :

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

From a Collection<Message> (the combined list of sent and received messages from a given member) I want to obtain a SortedMap<Message, Member> that contains the latest sent or received message from another member as the key and that other member as the value.

Can anyone please provice pointers or ideas on how to use java 8 streams in order to achieve this?

edit 1 : Here is a sample input/output as requested:

Input (content of message table):

在此处输入图片说明

Output for member with #2 should be messages with #5 and #4

Explanation: the latest sent or received messages involving member #2 with member #1 is message #4 and the latest message involving member #2 with member #6 is message #5 .

Other messages between member #2 and member #1 are older so not taken into account.

The ultimate goal is to achieve a message box such as whatsapp/hangout or fb whereby I display the last sent or received message between the currently logged user and each of the other users.

If I understood well, this (not a one-liner), is a possible implementation:

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

If you really want a one-liner, this should do it:

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

The toMap collector can be decompose as the following:

  • for each Message in the stream
  • construct a new entry with the message as the key and the recipient member as the value
  • which you would put in a TreeMap that is comparing the message as their send date
  • if two messages have the same send date, keep only one


Since your edit, the mapping is actually more complex because you have to take in fact the sending date of the message and the mapping between the sender and the receiver. So you have to create a function that returns a unique mapping (warning: you need that 1->2 has to be the same mapping has 2->1 ).

This the mapping method I used (you may want to implement your own; it's just for the example):

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

Once done, put the messages in the map, and use a downstream collector to get the latest message:

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]

which results in:

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

The key is not important here since you are only interested in the messages. What you can do is get a List<Message> from this map, filtering the values that are empty first:

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

Final output:

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

I created this small gist if you want to see a small implementation.


I don't know how those collections are filled; but maybe it's worth to implement a caching map for each member so that when a new message is sent/received, you update the mapping. This would be more efficient than getting all the combined messages.

Hope it helps! :)

You can group the sent messages by recipient and then find the latest message for each recipient (not tested, might have some typos) :

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

This gives you the key and value reversed from what you wanted, but it should get you started. For example, you can create the reversed map by creating a Stream of this map's entries and then collecting it with toMap , reversing the key and value in the process.

I'm not sure if the entire process can be done in a single Stream pipeline.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM