Java Stream 复杂场景排序与分组技术解析与示例代码
文章目录
- 前言
- 技术积累
- 数据源准备
- 复杂场景排序
- 多级排序
- 嵌套对象排序
- 自定义排序逻辑
- 复杂场景分组
- 单字段分组
- 多级分组
- 多级分组与排序结合
- 多级分组并按组内元素排序
- 多级分组并按组内元素多级排序
- 总结
前言
Java Stream API 自从 Java 8 引入以来,已经成为处理集合数据的强大工具。它提供了丰富的操作,包括过滤、映射、归约、排序和分组等。本文将深入探讨如何在复杂场景下使用 Java Stream 进行排序和分组,并通过示例代码展示各种排序和分组技巧。
技术积累
Java Stream API 提供了 sorted 方法,可以方便地对流中的元素进行排序。同时,Collectors.groupingBy 方法可以用于对流中的元素进行分组。在复杂场景下,如多级排序、自定义排序逻辑、处理嵌套对象、多级分组与排序结合等,使用这些方法时需要一些技巧和注意事项。本文将详细介绍这些复杂场景下的排序和分组方法。
数据源准备
首先新增一个person对象来作为演示数据源,内部提供一个初始化的方法。
/*** Person* @author senfel* @version 1.0* @date 2025/4/18 15:37*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {private String name;private String department;private int age;private double salary;private Address address;/*** init* @author senfel* @date 2025/4/18 15:39* @return java.util.List<com.example.ccedemo.stream.Person>*/public List<Person> init(){return new ArrayList<Person>(Arrays.asList(new Person("ayan", "HR", 30, 10000, new Address(10088, "四川南充")),new Person("kiki", "Engineering", 25, 20000, new Address(10086, "四川成都")),new Person("tim", "HR", 28, 15000, new Address(10012, "重庆")),new Person( "senfel", "Engineering", 35, 15000, new Address(10086, "四川成都")),new Person("roy", "HR", 32, 12000, new Address(10010, "上海"))));}/*** getStreetNumber* @author senfel* @date 2025/4/18 18:51* @return java.lang.Integer*/public Integer getStreetNumber() {return this.getAddress().getStreetNumber();}@Data@AllArgsConstructor@NoArgsConstructorpublic static class Address {private int streetNumber;private String streetName;}
}
复杂场景排序
多级排序
是指根据多个字段对元素进行排序。可以使用 Comparator.comparing 和 thenComparing 方法实现。
/*** multilevelSortTest* @author senfel* @date 2025/4/18 15:58* @return void*/
@Test
public void multilevelSortTest() {Person person = new Person();List<Person> personList = person.init();//先部门排序,再age排序personList = personList.stream().sorted(Comparator.comparing(Person::getDepartment).thenComparing(Person::getAge)).collect(Collectors.toList());/*Person(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))Person(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))*/personList.forEach(System.out::println);
}
嵌套对象排序
当需要根据嵌套对象的字段进行排序时,可以使用 Comparator.comparing 方法并传递一个函数来提取嵌套对象的字段。
/*** nestedObjectSortTest* @author senfel* @date 2025/4/18 16:14* @return void*/
@Test
public void nestedObjectSortTest() {Person person = new Person();List<Person> personList = person.init();//先根据部门排序,再根据嵌套街道号排序personList = personList.stream().sorted(Comparator.comparing(Person::getDepartment).thenComparing(Person::getAddress, Comparator.comparing(Person.Address::getStreetNumber))).collect(Collectors.toList());/*Person(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))Person(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))*/personList.forEach(System.out::println);
}
自定义排序逻辑
有时需要根据复杂的逻辑进行排序,可以使用 Comparator.comparing 和自定义的比较器。
/*** customSortTest* @author senfel* @date 2025/4/18 17:55 * @return void*/
@Test
public void customSortTest() {Person person = new Person();List<Person> personList = person.init();//工资一样比较部门,工资不一样比较街道号personList = personList.stream().sorted((e1,e2)->{if(e1.getSalary()==e2.getSalary()){return e1.getDepartment().compareTo(e2.getDepartment());}else{return Integer.compare(e1.getAddress().getStreetNumber(),e2.getAddress().getStreetNumber());}}).collect(Collectors.toList());/*Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))Person(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))Person(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))*/personList.forEach(System.out::println);
}
复杂场景分组
单字段分组
/*** singleFieldGroupTest* @author senfel* @date 2025/4/18 17:59 * @return void*/
@Test
public void singleFieldGroupTest() {Person person = new Person();List<Person> personList = person.init();//通过部门分组Map<String, List<Person>> collect =personList.stream().collect(Collectors.groupingBy(Person::getDepartment));/*department:EngineeringPerson(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))department:HRPerson(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))*/collect.forEach((k,v)->{System.out.println("department:"+k);v.forEach(System.out::println);});
}
多级分组
多级分组是指根据多个字段对元素进行分组。可以使用嵌套的 Collectors.groupingBy 方法实现。
/*** multiFieldGroupTest* @author senfel* @date 2025/4/18 18:41 * @return void*/
@Test
public void multiFieldGroupTest() {Person person = new Person();List<Person> personList = person.init();//通过部门分组,内部再通过街道号分组Map<String, Map<Integer, List<Person>>> collect = personList.stream().collect(Collectors.groupingBy(Person::getDepartment, Collectors.groupingBy(Person::getAge)));/*department:Engineeringage:35Person(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))age:25Person(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))department:HRage:32Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))age:28Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))age:30Person(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))*/collect.forEach((k,v)->{System.out.println("department:"+k);v.forEach((k1,v1)-> {System.out.println("age:"+k1);v1.forEach(System.out::println);});});
}
多级分组与排序结合
在实际应用中,我们经常需要对分组后的数据进行排序。可以通过在分组后使用 Collectors.collectingAndThen 和 Collectors.toList 结合 sorted 方法来实现。
多级分组并按组内元素排序
/*** groupingAndSortTest* @author senfel* @date 2025/4/18 18:48 * @return void*/
@Test
public void groupingAndSortTest() {Person person = new Person();List<Person> personList = person.init();//通过部门分组,内部再通过工资排序Map<String, List<Person>> collect = personList.stream().collect(Collectors.groupingBy(Person::getDepartment,Collectors.collectingAndThen(Collectors.toList(),list -> list.stream().sorted(Comparator.comparing(Person::getSalary)).collect(Collectors.toList()))));/*department:EngineeringPerson(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))department:HRPerson(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))*/collect.forEach((k,v)->{System.out.println("department:"+k);v.forEach(System.out::println);});
}
多级分组并按组内元素多级排序
/*** multiLevelGroupingAndSortTest* @author senfel* @date 2025/4/18 18:56* @return void*/
@Test
public void multiLevelGroupingAndSortTest() {Person person = new Person();List<Person> personList = person.init();personList.add(new Person("senfel2", "Engineering", 18, 15000, new Person.Address(10086, "四川成都")));//先根据部门分组,再根据街道号分组,再根据工资排序,最后根据年龄排序Map<String, Map<Integer, List<Person>>> collect = personList.stream().collect(Collectors.groupingBy(Person::getDepartment,Collectors.groupingBy(Person::getStreetNumber,Collectors.collectingAndThen(Collectors.toList(),list -> list.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).collect(Collectors.toList())))));/*department:EngineeringsheetNumber:10086Person(name=senfel2, department=Engineering, age=18, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=senfel, department=Engineering, age=35, salary=15000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))Person(name=kiki, department=Engineering, age=25, salary=20000.0, address=Person.Address(streetNumber=10086, streetName=四川成都))department:HRsheetNumber:10088Person(name=ayan, department=HR, age=30, salary=10000.0, address=Person.Address(streetNumber=10088, streetName=四川南充))sheetNumber:10010Person(name=roy, department=HR, age=32, salary=12000.0, address=Person.Address(streetNumber=10010, streetName=上海))sheetNumber:10012Person(name=tim, department=HR, age=28, salary=15000.0, address=Person.Address(streetNumber=10012, streetName=重庆))*/collect.forEach((k,v)->{System.out.println("department:"+k);v.forEach((k1,v1)-> {System.out.println("sheetNumber:"+k1);v1.forEach(System.out::println);});});
}
总结
Java Stream API 提供了强大的排序和分组功能,通过 sorted 方法可以方便地对流中的元素进行排序,通过 Collectors.groupingBy 方法可以对流中的元素进行分组。在复杂场景下,如多级排序、自定义排序逻辑、处理嵌套对象、多级分组与排序等,可以通过 Comparator.comparing 和 thenComparing 方法来实现多级排序,并通过Collectors.groupingBy来实现多级分组并按组内元素多级排序。