如何使用 Java Streams 依元素欄位對映射值清單進行排序
1. 概述
在本教程中,我們將探討 Streams API對儲存在Map中的List中的元素進行排序的好處。在此過程中,我們還將其與使用List#sort(Comparator)方法的更傳統方法進行比較,看看哪種方法更有效。
2. 問題陳述
在看解決方案之前,我們先討論一下問題。
假設有一個Employee類別:
public class Employee {
private String name;
private int salary;
private String department;
private String sex;
public Employee(String name, int salary, String department, String sex) {
this.name = name;
this.salary = salary;
this.department = department;
this.sex = sex;
}
//getter and setters ..
}
Employee類別包含欄位name 、 salary 、 department和sex 。此外,我們還有一個建構函式幫助我們建立Employee物件。
我們將透過從包含員工資料的 CSV 檔案emp_not_sorted.csv中讀取記錄來建立Employee物件清單:
Sales,John Doe,48000,M
HR,Jane Smith,60000,F
IT,Robert Brown,75000,M
Marketing,Alice Johnson,55000,F
Sales,Chris Green,48000,M
HR,Emily White,62000,F
IT,Michael Black,72000,M
Marketing,Linda Blue,60000,F
More records...
CSV 檔案包含department 、 name 、 salary和sex欄位。
我們將讀取此 CSV 檔案並將記錄儲存在Map中:
static void populateMap(String filePath) throws IOException {
String[] lines = readLinesFromFile(filePath);
Arrays.asList(lines)
.forEach(e -> {
String[] strArr = e.split(",");
Employee emp = new Employee(strArr[1], Integer.valueOf(strArr[2]), strArr[0], strArr[3]);
MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES.computeIfAbsent(emp.getDepartment(),
k -> new HashMap<>())
.computeIfAbsent(emp.getSex(), k -> new ArrayList<>())
.add(emp);
});
}
在這個方法中, MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES欄位的類型為Map<String, Map<String, List>> 。映射的外鍵是department字段,映射的內鍵是sex字段。
在下一節中,我們將存取內部Map中的Employee List ,並嘗試按salary排序,然後按員工name.
這是我們期望排序後的結果:
Sales,Chris Green,48000,M
Sales,John Doe,48000,M
Sales,Matthew Cyan,48000,M
Sales,David Grey,50000,M
Sales,James Purple,50000,M
Sales,Aiden White,55000,M
More records..
HR,Isabella Magenta,60000,F
HR,Jane Smith,60000,F
HR,Emily White,62000,F
HR,Sophia Red,62000,F
More records..
首先,記錄按salary排序,然後按員工name排序。我們對其他部門也遵循相同的模式。
3. 沒有Stream API的解決方案
傳統上,我們會使用List#sort(Comparator)方法:
void givenHashMapContainingEmployeeList_whenSortWithoutStreamAPI_thenSort() throws IOException {
final List<Employee> lstOfEmployees = new ArrayList<>();
MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES.forEach((dept, deptToSexToEmps) ->
deptToSexToEmps.forEach((sex, emps) ->
{
emps.sort(Comparator.comparingInt(Employee::getSalary).thenComparing(Employee::getName));
emps.forEach(this::processFurther);
lstOfEmployees.addAll(emps);
})
);
String[] expectedArray = readLinesFromFile(getFilePath("emp_sorted.csv"));
String[] actualArray = getCSVDelimitedLines(lstOfEmployees);
assertArrayEquals(expectedArray, actualArray);
}
我們使用forEach()方法來迭代Map ,而不是使用for或while循環遍歷鍵Set或Map類別的Entry 。此方法是 Java 8 中引入的泛型、函數式程式設計和 Stream API 等增強功能的一部分。
List#sort(Comparator) Comparator#comparingInt() thenComparing() name 8 中salary的Comparator功能Comparator 。方法鏈透過函數或 lambda 表達式提供靈活的自訂排序邏輯。這種風格的程式碼更具聲明性,因此更容易理解。
sort()方法對emps變數中的原始Employee List物件進行排序,違反了不變性原則。在修復編程缺陷時,這種突變會使故障排除和調試變得複雜。此外,它不會傳回List或Stream物件以供進一步處理。因此,我們需要再次循環遍歷List物件以進行進一步處理。它打破了流程,使其不太直觀地理解。
4. 使用 Stream API 的解決方案
考慮到上一節討論的缺點,讓我們藉助Stream API 來解決它們:
void givenHashMapContainingEmployeeList_whenSortWithStreamAPI_thenSort() throws IOException {
final List<Employee> lstOfEmployees = new ArrayList<>();
MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES.forEach((dept, deptToSexToEmps) ->
deptToSexToEmps.forEach((sex, emps) ->
{
List<Employee> employees = emps.stream()
.sorted(Comparator.comparingInt(Employee::getSalary).thenComparing(Employee::getName))
.map(this::processFurther)
.collect(Collectors.toList());
lstOfEmployees.addAll(employees);
})
);
String[] expectedArray = readLinesFromFile(getFilePath("emp_sorted.csv"));
String[] actualArray = getCSVDelimitedLines(lstOfEmployees);
assertArrayEquals(expectedArray, actualArray);
}
與先前的方法不同, Stream#sorted(Comparator)方法傳回一個Stream物件。它的工作原理與List#sort(Comparator)方法類似,但在這裡我們可以藉助Stream#map()方法進一步處理Employee List的每個元素。例如, map()方法中的processFurther()函數參數將每個employee元素作為參數來進一步處理它。
我們可以在管道中執行多個中間操作,最後以像collect()或**reduce()** .最後,我們收集排序後的Employee列表,然後透過將其與emp_sorted.csv檔案中排序後的員工資料進行比較來驗證它是否已排序。
5. 結論
在本文中,我們討論了Stream#sorted(Comparator)方法並將其與List#sort(Comparator).
我們可以得出結論, Stream#sorted(Comparator)比List#sort(Comparator).雖然 Stream API 具有許多強大的功能,但必須考慮 Stream API 中的函數式程式設計原則,例如不變性、無狀態和純函數。如果不遵循它們,我們最終可能會得到錯誤的結果。
與往常一樣,本文中使用的程式碼可以在 GitHub 上找到。