Collectors toMap trong Stream API

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 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

https://www.baeldung.com/java-collectors-tomap

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x