项目场景:

前段时间遇到了一个业务场景,要对List<Object>对象列表进行较复杂的排序操作:首先要对列表根据对象属性A进行分组,然后要对分组后的每组内的对象属性B(每组的属性B值相同,可能为空)对组进行排序,然后每组组内要对属性C进行排序

这里的对象为People类属性为:

@Data
public class People {
    /**国家*/
    private String country;
    /**国庆*/
    private Date nationalDay;
    /**年龄*/
    private Integer age;
    /**姓名*/
    private String name;

}

原始数据如下:

[People{country='Egypt', nationalDay=1952-07-23, age=27, name='工具人0'}
, People{country='China', nationalDay=1949-10-01, age=28, name='工具人1'}
, People{country='Japan', nationalDay=null, age=25, name='工具人2'}
, People{country='Korea', nationalDay=1948-08-15, age=23, name='工具人3'}
, People{country='Egypt', nationalDay=1952-07-23, age=27, name='工具人4'}
, People{country='Japan', nationalDay=null, age=24, name='工具人5'}
, People{country='Japan', nationalDay=null, age=25, name='工具人6'}
, People{country='U.S', nationalDay=1776-07-04, age=22, name='工具人7'}
, People{country='Egypt', nationalDay=1952-07-23, age=23, name='工具人8'}
, People{country='China', nationalDay=1949-10-01, age=20, name='工具人9'}
, People{country='Egypt', nationalDay=1952-07-23, age=27, name='工具人10'}
, People{country='Korea', nationalDay=1948-08-15, age=20, name='工具人11'}
, People{country='Korea', nationalDay=1948-08-15, age=21, name='工具人12'}
, People{country='Egypt', nationalDay=1952-07-23, age=22, name='工具人13'}
, People{country='Korea', nationalDay=1948-08-15, age=25, name='工具人14'}
]

目标数据为:即先对对象的country进行分组,然后用nationalDay对每组进行倒序(nationalDay属性为空放最后),每组组内对age进行升序

[People{country='Egypt', nationalDay=1952-07-23, age=22, name='工具人13'}
, People{country='Egypt', nationalDay=1952-07-23, age=23, name='工具人8'}
, People{country='Egypt', nationalDay=1952-07-23, age=27, name='工具人0'}
, People{country='Egypt', nationalDay=1952-07-23, age=27, name='工具人4'}
, People{country='Egypt', nationalDay=1952-07-23, age=27, name='工具人10'}
, People{country='China', nationalDay=1949-10-01, age=20, name='工具人9'}
, People{country='China', nationalDay=1949-10-01, age=28, name='工具人1'}
, People{country='Korea', nationalDay=1948-08-15, age=20, name='工具人11'}
, People{country='Korea', nationalDay=1948-08-15, age=21, name='工具人12'}
, People{country='Korea', nationalDay=1948-08-15, age=23, name='工具人3'}
, People{country='Korea', nationalDay=1948-08-15, age=25, name='工具人14'}
, People{country='U.S', nationalDay=1776-07-04, age=22, name='工具人7'}
, People{country='Japan', nationalDay=null, age=24, name='工具人5'}
, People{country='Japan', nationalDay=null, age=25, name='工具人2'}
, People{country='Japan', nationalDay=null, age=25, name='工具人6'}
]


解决方案:

我的方案是通过Stream对列表进行分组排序等操作达到预期结果:

1、 country分组

分组为Map集合key-value值为country属性-同country属性的列表List<People>

// 1、country分组
Map<String, List<People>> countryGroupMap = peopleList.stream().collect(Collectors.groupingBy(People::getCountry));
System.err.println(countryGroupMap);

2、 country分组每组取一条

        // country分组每组取一条
        Map<String, People> countryGroupGetOneMap = peopleList.stream().collect(
                Collectors.groupingBy(
                        People::getCountry,Collectors.collectingAndThen(
                                Collectors.reducing((c1, c2) -> c1), Optional::get)
                )
        );
        System.err.println(countryGroupGetOneMap);

 3、 对分组后的map根据nationalDay倒序(birthday可能为空null放最后)

注意:用LinkedHashMap保存,因为HashMap是根据key计算hash值设置元素存储位置的是无序的,故应用有序map保存排序后数据

        // 对分组后的map根据nationalDay倒序(birthday可能为空null放最后)
        Map<String, People> linkedHashMap = new LinkedHashMap<>();
        countryGroupGetOneMap.entrySet().stream().sorted(
                Map.Entry.<String, People>comparingByValue(
                        Comparator.comparing(
                                People::getNationalDay, Comparator.nullsFirst(Date::compareTo)
                        )
                ).reversed()
        ).forEachOrdered(e -> linkedHashMap.put(e.getKey(), e.getValue()));
        System.err.println(linkedHashMap);

 4、获取根据 nationalDay 排序后的 country列表

        // 获取根据 nationalDay 排序后的 country列表
        List<String> birthdaySortCountryGroupCountryList = new ArrayList<String>(linkedHashMap.keySet());

5、根据country列表 获取完整分组、排序、排序类表(每组再根据年龄升序)得到最终结果

        // 根据country列表 获取完整分组、排序、排序类表(每组再根据年龄升序)
        List<People> finalPeopleGroupSortList = new ArrayList<>();
        for (String country : nationalDaySortCountryGroupCountryList) {
            // 每组再根据年龄升序
            List<People> ageSortGroup = countryGroupMap.get(country).stream().sorted(Comparator.comparing(People::getAge)).collect(Collectors.toList());
            finalPeopleGroupSortList.addAll(ageSortGroup);
        }
        System.err.println(finalPeopleGroupSortList);

附上完整代码:

/**
 * @author zg
 * @create 2021/8/24 19:19
 */
public class GroupTanSortExample {

    public static void main(String[] args) {
        // 生成乱序的People对象列表
        List<People> peopleList = GroupTanSortExample.randomPeopleList(15);
        System.err.println(peopleList);

        // 1、country分组
        Map<String, List<People>> countryGroupMap = peopleList.stream().collect(Collectors.groupingBy(People::getCountry));
        System.err.println(countryGroupMap);

        // 2、country分组每组取一条
        Map<String, People> countryGroupGetOneMap = peopleList.stream().collect(
                Collectors.groupingBy(
                        People::getCountry,Collectors.collectingAndThen(
                                Collectors.reducing((c1, c2) -> c1), Optional::get)
                )
        );
        System.err.println(countryGroupGetOneMap);

        // 3、对分组后的map根据nationalDay倒序(nationalDay可能为空null放最后)
        Map<String, People> linkedHashMap = new LinkedHashMap<>();
        countryGroupGetOneMap.entrySet().stream().sorted(
                Map.Entry.<String, People>comparingByValue(
                        Comparator.comparing(
                                People::getNationalDay, Comparator.nullsFirst(Date::compareTo)
                        )
                ).reversed()
        ).forEachOrdered(e -> linkedHashMap.put(e.getKey(), e.getValue()));
        System.err.println(linkedHashMap);

        // 4、获取根据 nationalDay 排序后的 country列表
        List<String> nationalDaySortCountryGroupCountryList = new ArrayList<String>(linkedHashMap.keySet());

        // 5、根据country列表 获取完整分组、排序、排序类表(每组再根据年龄升序)
        List<People> finalPeopleGroupSortList = new ArrayList<>();
        for (String country : nationalDaySortCountryGroupCountryList) {
            // 每组再根据年龄升序
            List<People> ageSortGroup = countryGroupMap.get(country).stream().sorted(Comparator.comparing(People::getAge)).collect(Collectors.toList());
            finalPeopleGroupSortList.addAll(ageSortGroup);
        }
        System.err.println(finalPeopleGroupSortList);
    }



    // 生成乱序的People对象
    public static List<People> randomPeopleList(int size) {
        List<People> list = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            People people = new People();
            people.setName("工具人" + i);
            int num = (int) (Math.random() * 10 % 5);
            people.setAge(20 + (int) (Math.random() * 10 % 9));
            switch (num) {
                case 0:people.setCountry("China");people.setNationalDay(parserDate("1949-10-01 00:00:00"));break;
                case 1:people.setCountry("Japan");people.setNationalDay(null);break;
                case 2:people.setCountry("U.S");people.setNationalDay(parserDate("1776-07-04 00:00:00"));break;
                case 3:people.setCountry("Korea");people.setNationalDay(parserDate("1948-08-15 00:00:00"));break;
                case 4:people.setCountry("Egypt");people.setNationalDay(parserDate("1952-07-23 00:00:00"));break;
                default:;
            }
            list.add(people);
        }
        return list;
    }
    
    
    // 字符串解析为日期
    public static Date parserDate(String dateStr) {
        Date date = null;
        try {
            date = new SimpleDateFormat("yyy-MM-dd HH:mm:mm").parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

感觉目标是达到了,但是代码还是太冗长,Stream肯定还有更方便的方法,期待更简洁 更优雅的实现方式

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐