如何使用 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 上找到。