java8 grouping by custom value custom map value

java 8 groupingby(map values)
java 8 group by multiple fields
java 8 group by multiple fields and sum
java 8 multi level grouping
java 8 group by and reduce
java collections group by example
java 8 group by multiple fields and count
java 7 collection group by

java8

public class CustomDate{    
    LocalDateTime datetime;   
}

List<CustomDate>  list = // 2 dates with multiple times

I want to group dates by day and values as times following gives me CustomDate as values?

Map<LocalDate, List<CustomDate>> map = 
         list.stream().groupingBy(d-> d.datetime.toLocalDate())

any help is appreciated.

public class Dates {

    private static List< CustomDate > list = new ArrayList<>(  );

    public static void main( String[] args ) {

        Map< LocalDate, List< LocalTime > > result2 = list.stream()
            .map( CustomDate::getDatetime )
            .collect(
                HashMap::new,
                Dates::accept,
                Dates::merge
            );

    }

    private static void accept( HashMap< LocalDate, List< LocalTime > > map, LocalDateTime date ) {

        map.putIfAbsent( date.toLocalDate( ), new ArrayList<>())
           .add( date.toLocalTime( ) );
    }

    private static void merge( Map< LocalDate, List< LocalTime > > map1, Map< LocalDate, List< LocalTime > > map2 ) {

        map2.forEach( (date,timeList) -> {
            map1.merge( date, timeList, ( l1, l2 ) -> {
                l1.addAll( l2 );
                return l1;
            } );
        });

    }
}

Or simply:

Map< LocalDate, List< LocalTime > > result2 = list.stream()
    .map( CustomDate::getDatetime )
    .collect(
        HashMap::new,
        (map,date) -> {
             map.putIfAbsent( date.toLocalDate( ), new ArrayList<>(   )).add( date.toLocalTime( ) );
        },
        (map1, map2) -> {
            map2.forEach( (date,timeList) -> {
                map1.merge( date, timeList, ( l1, l2 ) -> {
                    l1.addAll( l2 );
                    return l1;
                } );
            }); 
        }
    );

Doing it not so declaratively as calling 3 times the collectors static methods, gives you much more control to dictate behavior, like using a initial size for your arrays, using specific lists and so on, without losing the sugar coming from the declarative style.

Map< LocalDate, List< LocalTime > > result2 = list.stream()
    .map( CustomDate::getDatetime )
    .collect(
        TreeMap::new,
        (map,date) -> {
             map.putIfAbsent( date.toLocalDate( ), new ArrayList<>( 800  )).add( date.toLocalTime( ) );
        },
        (map1, map2) -> {
            map2.forEach( (date,timeList) -> {
                map1.merge( date, timeList, ( l1, l2 ) -> {
                    l1.addAll( l2 );
                    return l1;
                } );
            }); 
        }
    );

Of course if the default behavior is acceptable, this is way simpler:

    Map<LocalDate, List<LocalTime>> result = list.stream()
            .map( CustomDate::getDatetime )
            .collect(groupingBy(LocalDateTime::toLocalDate,
                     mapping(LocalDateTime::toLocalTime,toList())));

Another way to go is to define your own collector, which is what I usually prefer. By doing that you gain both, control and the ability to code your logic declaratively, and reuse when needed.

Which would reduce you code to:

Map< LocalDate, List< LocalTime > > result2 = list.stream()
            .collect(
                    customDateCollector()
            );

If you had something like:

class CustomDateCollector implements Collector<CustomDate, Map< LocalDate, List< LocalTime > >, Map< LocalDate, List< LocalTime > >> {

    @Override
    public Supplier< Map< LocalDate, List< LocalTime > > > supplier( ) {

        return HashMap::new;
    }

    @Override
    public BiConsumer< Map< LocalDate, List< LocalTime > >, CustomDate > accumulator( ) {
        int goodStartUsingProblemKnowledge = 30;
        return (map, customDate) -> map.putIfAbsent( customDate.getDatetime().toLocalDate(), new ArrayList<>( goodStartUsingProblemKnowledge) )
                                       .add(  customDate.getDatetime().toLocalTime() );
    }

    @Override
    public BinaryOperator< Map< LocalDate, List< LocalTime > > > combiner( ) {

        return (map1, map2) -> {
                map2.forEach( (date,timeList) -> {
                    map1.merge( date, timeList, ( list1, list2 ) -> {
                        list1.addAll( list2 );
                        return list1;
                    } );
                });
                return map1;
        };
    }

    @Override
    public Function< Map< LocalDate, List< LocalTime > >, Map< LocalDate, List< LocalTime > > > finisher( ) {

        return Function.identity();
    }

    @Override
    public Set< Characteristics > characteristics( ) {

        return Collections.unmodifiableSet( EnumSet.of( IDENTITY_FINISH, UNORDERED,CONCURRENT) );
    }

    public static CustomDateCollector customDateCollector(){
        return new CustomDateCollector();
    }
}

Guide to Java 8 groupingBy Collector, The value that is returned by the function is used as a key to the map that we get from the groupingBy collector. To group the blog posts in the  In this tutorial, we demonstrate how to sort a Map by value in Java, using methods like the LinkedHashMap, Comparator, and Sorted to provide example code. How to Sort a Map by Value in Java 8+

It seems like you want to group by the date and have the map values as LocalTime instead of CustomDate.

Map<LocalDate, List<LocalTime>> result = list.stream()
                .collect(Collectors.groupingBy(e -> e.getDatetime().toLocalDate(),
                        Collectors.mapping(e -> e.getDatetime().toLocalTime(),
                                Collectors.toList())));

Java 8 Grouping with Collectors, Tutorial on Java 8 groupingBy Collector with examples explains 3 groupingBy() I.e. for every value of R there is a collection of objects all of which return that The grouped elements are stored in a Map<R,Collection<T>> , with the 4 to Read Tutorial on Factory Design Pattern supplying Maps of type M . The Java 8 Stream API lets us process collections of data in a declarative way. The static factory methods Collectors.groupingBy() and Collectors.groupingByConcurrent() provide us with functionality similar to the ‘GROUP BY' clause in the SQL language. They are used for grouping objects by some property and storing results in a Map instance.

As in answer from Aominè you should use Collectors.groupingBy(). But I reccomend you to do some additional steps.

1 Create GroupUtils class with some general code teamplates:

public final class GroupUtils {

    public static <K, V> Map<K, List<V>> groupMultipleBy(Collection<V> data, Function<V, K> classifier) {
        return groupMultipleBy(data, classifier, Function.identity());
    }

    public static <K, V, S> Map<K, List<S>> groupMultipleBy(Collection<V> data, Function<V, K> classifier, Function<V, S> mapper) {
        return Optional.ofNullable(data).orElse(Collections.emptyList()).stream()
                       .collect(Collectors.groupingBy(classifier, Collectors.mapping(mapper, Collectors.toList())));
    }
}

This is example from one of my project. Here I have more similar methods.

2 In local file, do create concrete method, to make client's code much more readable.

class Client {

    public static void main(String... args) {
        List<CustomDate> list = Collections.emptyList();
        Map<LocalDateTime, List<CustomDate>> map = groupByDatetime(list);
    }

    private static Map<LocalDateTime, List<CustomDate>> groupByDatetime(List<CustomDate> list) {
        return GroupUtils.groupMultipleBy(list, CustomDate::getDatetime);        
    }
}

I believe this is a little bit more code, but much more readable than:

Map<LocalDate, List<LocalTime>> result = list.stream()
                .collect(Collectors.groupingBy(e -> e.getDatetime().toLocalDate(),
                        Collectors.mapping(e -> e.getDatetime().toLocalTime(),
                                Collectors.toList())));

The Ultimate Guide to the Java Stream API groupingBy , But, the collector itself is capable of doing much more than simple groupings, as shown above. Grouping Into a Custom Map Implementation. If  Introduction – Java 8 Grouping with Collectors tutorial explains how to use the predefined Collector returned by groupingBy() method of java.util.stream.Collectors class with examples. The tutorial begins with explaining how grouping of stream elements works using a Grouping Collector. The concept of grouping is visually illustrated with a

The Ultimate Guide to the Java Stream API , But the collector itself is capable of doing much more than simple groupings like above. Grouping Into a Custom Map Implementation. If you need  Java 8 Stream interface defines a collect method which performs a mutable reduction operation on the elements of the stream. It accepts a Collector as a parameter which encapsulates the strategy that is used to compute the final result. In this post we’ll have a look at how to create a custom collector in Java 8 from scratch. What is a collector?

Creating a custom collector in Java 8, In this post you'll see how to create a custom collector in Java 8. Reducing and summarizing stream elements to a single value; Grouping stream elements Map<String, List<Book>> booksByAuthor = books.stream(). Converting or transforming a List and Array Objects in Java is a common task when programming. In the tutorial, We show how to do the task with lots of Java examples code by 2 approaches: Using Traditional Solution with basic Looping Using a powerful API – Java 8 Stream Map Now let’s do details with … Continue reading "How to use Java 8 Stream Map Examples with a List or Array"

Java 8 – Stream Collectors groupingBy examples – Mkyong.com, 1.1 Group by a List and display the total count of it. Item("apple", 20, new BigDecimal("9.99")) ); //group by price Map<BigDecimal, List<Item>>  This is very important and trending topic.In this post i will be explaining HashMap custom implementation in lots of detail with diagrams which will help you in visualizing the HashMap implementation.

Comments
  • what? this is unclear
  • thanks, Eugene i thought my question was simple not sure why mind reading skil need :D
  • @d-man it's probably clear to you, but I've read it 5 times now still did not understand it. How about this is the List I have and I want output to be Map<LocalDate, List<LocalTime>>... this would have made a lot more sense
  • @Eugene I thought you'd ask the secret behind this "mind reading" skill by now? ;)
  • Why not do a single getDatetime() before the collect?→list.stream() .map(CustomDate::getDatetime).collect(Collectors.groupingBy(LocalDateTime ::toLocalDate, Collectors.mapping(LocalDateTime::toLocalTime, Collectors.toList())));
  • @Holger good call that's better, although the call to getDatetime() is very lightweight and one shouldn't be too worried about.
  • it's Collectors.groupingBy not Collectors.groupBy
  • and also in your opinion is this more readable, I find your solution a lot less readable, but it might be just me
  • well I also don't like the Optional.ofNullable either; if you talk about being readable if could be done as public static <K, V, S> Map<K, List<S>> groupMultipleBy(Collection<V> data, Function<V, K> classifier, Function<V, S> mapper) { if (data == null) { return Collections.emptyMap(); } return data.stream() .collect(Collectors.groupingBy( classifier, Collectors.mapping(mapper, Collectors.toList()))); }
  • that is the point, it's something that you (and your colleagues probably) use a lot - so you are aware if it; I don't - thus don't really trust it, thus would have to understand the implementation to actually "get it", while the other solution is straightforward. It all depends how you look at it
  • wait - did you just try to silence the internet here for a moment, hahahahaha u made my day!