package java8.streams.collectors; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toCollection; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collector; import org.junit.Test; /** * I want to take a set of objects and group them by one property, and have the resulting lists be sorted by another. * * @author Sebastian Millies * @see http://stackoverflow.com/questions/32171283/how-to-group-a-set-of-objects-into-sorted-lists-using-java-8 */ public class CollectToSortedList { static class Something { private final String theOne; private final String theOther; public Something(String theOne, String theOther) { Objects.requireNonNull(theOne); Objects.requireNonNull(theOther); this.theOne = theOne; this.theOther = theOther; } public String getTheOne() { return theOne; } public String getTheOther() { return theOther; } @Override public String toString() { return "(" + theOne + "," + theOther + ")"; } } final static class SomeComparableThing extends Something implements Comparable { public SomeComparableThing(String theOne, String theOther) { super(theOne, theOther); } @Override public int compareTo(SomeComparableThing obj) { return getTheOther().compareTo(obj.getTheOther()); } @Override public int hashCode() { return getTheOne().hashCode() ^ getTheOther().hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SomeComparableThing o = (SomeComparableThing) obj; return getTheOne().equals(o.getTheOne()) && getTheOther().equals(o.getTheOther()); } } // sort the entire stream before collecting static Map> relyOnEncounterOrder(Set theSet ) { Map> result = theSet .stream() .sorted(comparing(Something::getTheOther)) .collect(groupingBy(Something::getTheOne)); return result; } // sort the list in-situ after collecting static Map> withToSortedListCollector(Set theSet ) { Map> result = theSet .stream() .collect(groupingBy(Something::getTheOne, toSortedList(comparing(Something::getTheOther)))); return result; } // sort the list in-situ after collecting, using the natural order of the (Comparable) list elements static Map> withToNaturallySortedListCollector(Set theSet ) { Map> result = theSet .stream() .collect(groupingBy(Something::getTheOne, toNaturallySortedList())); // !! return result; } // Here we explicitly collect to ArrayList (toList() does the same, but it's not guaranteed), then sort the resulting list in-place // without additional copying (using stream().sorted().collect(toList()) would copy the whole list content at least twice). static Collector> toSortedList(Comparator order) { return collectingAndThen(toCollection(ArrayList::new), (List l) -> { l.sort(order); return l; }); } // Here we declare the parameter as extends Comparable and make use of the natural order. Otherwise you can mistakenly use this // collector for non-comparable type, which would compile fine, but result in runtime error. static > Collector> toNaturallySortedList() { return collectingAndThen(toCollection(ArrayList::new), (List l) -> { l.sort(Comparator.naturalOrder()); return l; }); } // test data static Set theSet() { return new HashSet<>(Arrays.asList(new SomeComparableThing("a","z"), new SomeComparableThing("b","x"), new SomeComparableThing("a","y"))); } static final Map> expected = new HashMap<>(); { expected.put("a", Arrays.asList(new SomeComparableThing("a","y"),new SomeComparableThing("a","z"))); expected.put("b", Arrays.asList(new SomeComparableThing("b","x"))); } @Test public void test() { assertEquals(expected, CollectToSortedList.relyOnEncounterOrder(theSet())); assertEquals(expected, CollectToSortedList.withToSortedListCollector(theSet())); assertEquals(expected, CollectToSortedList.withToNaturallySortedListCollector(theSet())); } }