Map lưu các cặp khóa và giá trị
HashMap (trong java.util) lưu các liên kết: mỗi khóa (key) ánh xạ tới một giá trị (value), và bạn tra giá trị theo khóa trong thời gian gần như hằng số. Hãy nghĩ tới một cuốn từ điển: từ là khóa, định nghĩa là giá trị.
Hai tham số kiểu là <KeyType, ValueType>. Ở đây khóa là String và giá trị là Integer. Cũng như với ArrayList, bạn thường khai báo biến theo interface Map và tạo đối tượng HashMap.
put, get và ghi đè
Hai điều cần ghi nhớ:
- Khóa là duy nhất.
putvới một khóa đã có sẽ thay giá trị của nó và trả về giá trị cũ. getvới khóa không tồn tại trả vềnull, không báo lỗi. Tự động unboxing giá trịnullđó thànhintsẽ némNullPointerException, một nguồn bug rất phổ biến.
getOrDefault tránh bẫy null
Thay vì lần nào cũng kiểm tra null, hãy yêu cầu một giá trị dự phòng:
Đây là cách gọn nhất để xử lý những lần tra cứu "có thể có, có thể không", và nó dẫn thẳng tới mẫu code nổi tiếng nhất của HashMap.
Đếm số lần xuất hiện
Đếm mỗi phần tử xuất hiện bao nhiêu lần là bài toán kinh điển của HashMap:
Mẫu map.put(key, map.getOrDefault(key, 0) + 1) đọc là "lấy số đếm hiện tại (hoặc 0), cộng một, rồi lưu lại". Một cách viết gọn hơn tương đương là counts.merge(word, 1, Integer::sum).
Kiểm tra và xóa
putIfAbsent(key, value) chỉ ghi khi khóa chưa tồn tại, hữu ích cho việc khởi tạo trễ (lazy initialization).
Duyệt một HashMap
Vòng lặp phổ biến nhất duyệt entrySet(), cho bạn cùng lúc khóa và giá trị:
Nếu bạn chỉ cần khóa hoặc chỉ cần giá trị:
Bạn cũng có thể dùng forEach với lambda: ages.forEach((name, age) -> System.out.println(name + ": " + age));.
HashMap không giữ thứ tự
HashMap không cam kết gì về thứ tự duyệt: thứ tự phụ thuộc vào kết quả băm và có thể thay đổi giữa các lần chạy. Nếu bạn cần thứ tự dự đoán được:
LinkedHashMapgiữ thứ tự thêm vào.TreeMapgiữ khóa được sắp xếp theo thứ tự tự nhiên (hoặc theoComparatorbạn cung cấp).
Cả ba đều cài đặt interface Map, nên muốn đổi chỉ cần sửa một dòng ở constructor.
Khóa phải băm được
HashMap tìm phần tử bằng cách băm khóa, nên hashCode() và equals() của khóa phải nhất quán với nhau. Các kiểu có sẵn như String và Integer đã làm đúng điều này. Nếu dùng lớp của riêng bạn làm khóa, hãy override cả equals lẫn hashCode, nếu không hai đối tượng "bằng nhau" về ý nghĩa sẽ rơi vào hai bucket khác nhau và việc tra cứu sẽ thất bại một cách khó hiểu.
Tiếp theo: HashSet
HashMap trả lời câu hỏi "giá trị nào được lưu dưới khóa này?". Khi bạn chỉ quan tâm một thứ có tồn tại hay không, tức một tập giá trị duy nhất không kèm dữ liệu liên kết, công cụ cần dùng là HashSet, nội dung của trang tiếp theo.
Câu hỏi thường gặp
Làm sao tạo HashMap trong Java?
Khai báo với hai tham số kiểu, kiểu của khóa và kiểu của giá trị, rồi gọi constructor: Map<String, Integer> ages = new HashMap<>();. Sau đó thêm phần tử bằng ages.put("Ada", 36); và đọc ra bằng ages.get("Ada");. Nhớ import java.util.HashMap và java.util.Map.
Làm sao duyệt HashMap trong Java?
Duyệt map.entrySet() bằng vòng lặp for-each để lấy cùng lúc khóa và giá trị: for (Map.Entry<String, Integer> e : map.entrySet()) { ... }, rồi đọc e.getKey() và e.getValue(). Bạn cũng có thể duyệt map.keySet() nếu chỉ cần khóa, hoặc map.values() nếu chỉ cần giá trị. Lưu ý HashMap không giữ thứ tự thêm vào.
get và getOrDefault khác nhau thế nào?
get(key) trả về giá trị của khóa, hoặc null nếu khóa không tồn tại, và điều này có thể gây NullPointerException nếu bạn dùng kết quả trực tiếp. getOrDefault(key, fallback) trả về giá trị nếu có, nếu không thì trả về giá trị dự phòng bạn truyền vào, nhờ vậy bạn khỏi phải kiểm tra null. Nó đặc biệt tiện khi đếm: counts.put(c, counts.getOrDefault(c, 0) + 1).