Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu toMap() method của Collectors class. Nó được sử dụng để chuyển các phần tử của Stream sang Map instance.
Convert List to Map
Chuyển một List sang Map là một thao tác khá phổ biến mà chúng ta thường sử dụng khi cần nhóm các đối tượng trong List theo một Key xác định.
Giả sử chúng ta có Book class:
class Book { private String name; private int releaseYear; private String phone; public Book(String name, int releaseYear, String phone) { this.name = name; this.releaseYear = releaseYear; this.phone= phone; } // getter, setter method }
Khởi tạo một List danh sách các Book instance để kiểm tra các ví dụ bên dưới:
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] agrs) throws InterruptedException { List<Book> bookList = new ArrayList<>(); bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318")); bookList.add(new Book("The Two Towers", 1954, "0345339711")); bookList.add(new Book("The Return of the King", 1955, "0618129111")); } }
Lưu ý toMap() method có các overload method khác, tuy nhiên chúng ta sẽ sử dụng toMap() method sau:
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
Như vậy, từ toMap() method chúng ta phải nêu rõ chiến lược để lấy Key và Value cho Map instance.
public static Map<String, String> listToMap(List<Book> books) { return books.stream().collect(Collectors.toMap(Book::getPhone, Book::getName)); }
Kiếm tra kết quả
Map<String, String> map = listToMap(bookList); map.forEach((k, v) -> System.out.println(k + " - " + v));
Output:
0395489318 – The Fellowship of the Ring
0618129111 – The Return of the King
0345339711 – The Two Towers
Duplicate key
Ví dụ ở trên chạy khá ổn, nhưng với trường hợp Key được chọn có nhiều hơn 1 thì sẽ gây ra IllegalStateException.
Ví dụ chuyển List sang Map với Key là releaseYear. Với các Book instance mà chúng ta có thì có 2 Book có cùng giá trị releaseYear = 1954.
import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; public class Main { public static void main(String[] agrs) throws InterruptedException { List<Book> bookList = new ArrayList<>(); bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318")); bookList.add(new Book("The Two Towers", 1954, "0345339711")); bookList.add(new Book("The Return of the King", 1955, "0618129111")); Map<Integer, Book> map = listToMapWithDupKeyError(bookList); map.forEach((k, v) -> System.out.println(k + " - " + v)); } public static Map<Integer, Book> listToMapWithDupKeyError(List<Book> books) { return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity())); } }
Output: Exception in thread “main” java.lang.IllegalStateException: Duplicate key Book@5d099f62
Để giải quyết vấn đề trên, chúng ta cần một toMap() method xử lý khi 1 Key xuất hiện nhiều hơn 1 lần.
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
toMap() method trên cung cấp thêm một BinaryOperator functional interface cho phép chúng ta triển khai mã xử lý khi có trường hợp Duplicate Key.
Sau đây là ví dụ, nếu có 2 Book instance có cùng Key, chúng ta sẽ lấy phần tử đã được thêm trước đó.
import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; class Main { public static void main(String[] agrs) throws InterruptedException { List<Book> bookList = new ArrayList<>(); bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318")); bookList.add(new Book("The Two Towers", 1954, "0345339711")); bookList.add(new Book("The Return of the King", 1955, "0618129111")); Map<Integer, Book> map = listToMapWithDupKey(bookList); map.forEach((k, v) -> System.out.println(k + " - " + v.getName())); } public static Map<Integer, Book> listToMapWithDupKey(List<Book> books) { return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(), (existing, replacement) -> existing)); } }
Output:
1954 – The Fellowship of the Ring
1955 – The Return of the King
Change Return Type Map
Mặc định toMap() method trả về HashMap, 1 implement của Map interface. Tuy nhiên chúng ta có thể thay đổi sang các implement khác của Map interface với
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
Trong đó mapSupplier sẽ là function chỉ định một implement của Map interface được trả về bởi toMap().
Ví dụ sử dụng toMap() trả về một ConcurrentHashMap.
public Map<Integer, Book> listToConcurrentMap(List<Book> books) { return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(), (o1, o2) -> o1, ConcurrentHashMap::new)); }
Tóm lược
Qua bài viết này, chúng ta đã biết cách chuyển một List sang Map với toMap() method. Nó sẽ rất hữu ích khi mà nhu cầu chuyển từ một List sang Map thường xuyên xảy ra.
Nguồn tham khảo