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;
} );
});
}
}
Или просто:
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;
} );
});
}
);
Делая это не так декларативно, как трехкратный вызов статических методов сборщиков, вы получаете гораздо больший контроль над поведением, как, например, использование начального размера для ваших массивов,использование определенных списков и т. д. без потери сахара из декларативного стиля.
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;
} );
});
}
);
Конечно, если поведение по умолчанию приемлемо, это намного проще:
Map<LocalDate, List<LocalTime>> result = list.stream()
.map( CustomDate::getDatetime )
.collect(groupingBy(LocalDateTime::toLocalDate,
mapping(LocalDateTime::toLocalTime,toList())));
Другойпуть - определить собственного коллекционера, что я обычно и предпочитаю.Делая это, вы получаете и контроль, и способность декларативно кодировать свою логику и повторно использовать ее при необходимости.
Что уменьшит ваш код до:
Map< LocalDate, List< LocalTime > > result2 = list.stream()
.collect(
customDateCollector()
);
Если у вас будет что-то вроде:
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();
}
}