balteo
balteo

Reputation: 24659

Issue with java 8 stream's Collector interface

I have the following 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;

From a collection of messages (Collection<Message> messages) I am trying to obtain a Map<Member, List<Message>> messageMap by grouping by either on the recipient OR sender fields.

To further define my use case, the connected member (currentMember) has a number of sent and received messages attached to its instance. I want to retrieve and add the messages to a collection that will contain all of the connected member messages (sent or received) as follows:

Collection<Message> messages = new ArrayList<Message>();
messages.addAll(currentMember.getSentMessages());
messages.addAll(currentMember.getReceivedMessages());

And for each other/opposite member with which the current member has exchanged at least one message, I would like to obtain a list of all exchanged messages.

I will then be able to construct the above Map<Member, List<Message>> messageMap.

Is this possible out of the box with the stream api or do I need to implement my own collector?

Upvotes: 0

Views: 67

Answers (1)

Holger
Holger

Reputation: 298123

If I understand your question correctly, you want to treat the messages uniformly by using the receiver as key for sent messages and using the sender as key for received messages. You could do this by using a conditional in the collect operation:

Map<Member, List<Message>> map = Stream.concat(
  currentMember.getSentMessages().stream(), currentMember.getReceivedMessages().stream())
.collect(Collectors.groupingBy(msg ->
                   msg.getSender()==currentMember? msg.getRecipient(): msg.getSender()));

This uses either, sender or receiver, as the key depending on the result of a comparison with the currentMember, so that the key will always be “the other side”. It may not be the most efficient solution as it evaluates an information which is actually known beforehand, but it’s simple.

If you want to avoid regeneration of the already known information, you have to carry it through the stream. In the absence of a standard Pair type you may use Map.Entry instances to encapsulate pairs of “desired key” and Message instance:

Map<Member, List<Message>> map =
  Stream.concat(
      currentMember.getSentMessages().stream().map(msg ->
          new AbstractMap.SimpleImmutableEntry<>(msg.getRecipient(), msg)),
      currentMember.getReceivedMessages().stream().map(msg ->
          new AbstractMap.SimpleImmutableEntry<>(msg.getSender(), msg)))
  .collect(Collectors.groupingBy(e -> e.getKey(),
               Collectors.mapping(e->e.getValue(), Collectors.toList())));

Here, not only the wrapping into the pairs complicates the code, the collection operation’s code also becomes more complicated due to the required unwrapping.

The decision which direction to go is up to you…

Upvotes: 2

Related Questions