【问题标题】:java8 grouping by custom value custom map valuejava8按自定义值分组自定义地图值
【发布时间】:2018-05-17 18:44:54
【问题描述】:

java8

public class CustomDate{    
    LocalDateTime datetime;   
}

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

我想按天分组日期和值,因为下面给我 CustomDate 作为值?

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

感谢任何帮助。

【问题讨论】:

  • 什么?这还不清楚

标签: java java-8 java-stream


【解决方案1】:

您似乎想按日期分组并将地图值设为LocalTime 而不是CustomDate

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

【讨论】:

  • 谢谢,尤金,我认为我的问题很简单,不知道为什么需要读心术:D
  • @d-man 你可能很清楚,但我已经读了 5 遍了,现在仍然不明白。这是我拥有的列表怎么样,我希望输出为 Map&lt;LocalDate, List&lt;LocalTime&gt;&gt;... 这会更有意义
  • @Eugene 我想你现在会问这个“读心”技能背后的秘密吗? ;)
  • 为什么不在collect前加一个getDatetime()?→list.stream() .map(CustomDate::getDatetime).collect(Collectors.groupingBy(LocalDateTime ::toLocalDate, Collectors.mapping(LocalDateTime::toLocalTime, Collectors.toList())));
  • @Holger 好电话那更好,虽然对 getDatetime() 的电话非常轻量级,不应该太担心。
【解决方案2】:
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;
                } );
            }); 
        }
    );

不像调用 3 次收集器静态方法那样以声明方式执行此操作,可以让您更多地控制来指示行为,例如为数组使用初始大小、使用特定列表等,而不会丢失来自声明性的糖风格。

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();
    }
}

【讨论】:

    【解决方案3】:

    正如 Aominè 的回答,您应该使用 Collectors.groupingBy()。但我建议你做一些额外的步骤。

    1 使用一些通用代码团队板创建GroupUtils 类:

    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())));
        }
    }
    

    这是我的一个项目中的示例。这里我有更多类似的方法。

    2 在本地文件中,创建具体的方法,使客户端的代码更具可读性。

    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);        
        }
    }
    

    我相信这有点多一些代码,但比:

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

    【讨论】:

    • Collectors.groupingBy 不是Collectors.groupBy
    • 而且在你看来这更易读,我发现你的解决方案可读性差很多,但可能只是我
    • 好吧,我也不喜欢Optional.ofNullable;如果您谈论可读性,可以使用public static &lt;K, V, S&gt; Map&lt;K, List&lt;S&gt;&gt; groupMultipleBy(Collection&lt;V&gt; data, Function&lt;V, K&gt; classifier, Function&lt;V, S&gt; mapper) { if (data == null) { return Collections.emptyMap(); } return data.stream() .collect(Collectors.groupingBy( classifier, Collectors.mapping(mapper, Collectors.toList()))); }
    • 这就是重点,这是您(和您的同事可能)经常使用的东西 - 所以您知道它是否;我不 - 因此并不真正信任它,因此必须了解实现才能真正“得到它”,而其他解决方案 直截了当。这完全取决于你如何看待它
    • 等等 - 你是不是只是想在这里让互联网安静片刻,哈哈哈哈哈你让我开心了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 2021-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多