Map trong Java là một trong những interface quan trọng thuộc Java Collections Framework mà mỗi bạn học về lập trình cần nắm vững. Hãy cùng Rikkei Academy dành 5 phút để tìm hiểu chi tiết về giao diện này nhé!
Map trong Java là gì?
Map trong Java là một interface thuộc Java Collections Framework, đại diện cho một cấu trúc dữ liệu dạng map, cho phép lưu trữ và truy cập dữ liệu dưới dạng các cặp key-value (khóa – giá trị).
Trong Java, java.util.Map là interface cơ bản mà các lớp triển khai map phải tuân theo. Các cặp key-value trong map giúp tổ chức và quản lý dữ liệu theo cách hiệu quả và dễ dàng.
Cách thức hoạt động của Map trong Java
Trong Java, các phần tử của Map được lưu trữ dưới dạng các cặp key/value. Key là giá trị duy nhất được gán với từng value. Một Map không thể chứa các key trùng lặp. Mỗi key được gán với một giá trị duy nhất.
Các giá trị trong Map có thể được truy cập và sửa đổi bằng cách sử dụng key tương ứng với chúng.
Ví dụ: Ta có các giá trị: United States, Brazil và Spain và ác key tương ứng: us, br và es. Ta có thể truy cập các giá trị này bằng cách sử dụng các key tương ứng với chúng.
Lưu ý: Giao diện Map duy trì 3 tập hợp khác nhau:
- Tập hợp các key
- Tập hợp các value
- Mapping: tập hợp các key/value (liên kết key/value).
Do đó, chúng ta có thể truy cập các key, value và mapping một cách độc lập bằng cách sử dụng các phương thức khác nhau ví dụ keySet() trả tập hợp tất cả các keys, phương thức values() trả về tất cả các values và phương thức entrySet() trả về tập hợp tất cả các mappings,
Đặc điểm của Map trong Java
- Không chứa các key trùng lặp: Tức là mỗi key chỉ có thể ánh xạ đến một value. Nếu bạn thêm một cặp key-value mới mà key đã tồn tại, giá trị mới sẽ ghi đè lên giá trị cũ. Tuy nhiên, hai hoặc nhiều key khác nhau có thể được ánh xạ đến cùng một giá trị trong Map.
- Sử dụng key và value là null: Một số cài đặt Map cho phép sử dụng key và value là null. Trong đó, HashMap và LinkedHashMap cho phép bạn sử dụng cả key và value là null. TreeMap không cho phép sử dụng key là null.
- Thứ tự của các phần tử trong Map phụ thuộc vào cài đặt cụ thể. TreeMap và LinkedHashMap có thứ tự dự đoán được, trong khi HashMap thì không.
- Map trong Java sử dụng hashCode và equals của Key để tìm kiếm các entry trong Map. Nếu hashCode hoặc equals của Key thay đổi sau khi đặt vào Map, việc tìm kiếm các entry bằng Key có thể không thành công.
Cấu trúc phân tầng Map trong Java
Dưới đây là cấu trúc phân tầng map trong Java:
Giao diện (Interface) Map trong Java
Ngoài giao diện Map chính, Java còn cung cấp các giao diện phụ để mở rộng và tùy chỉnh hơn các tính năng của Map:
Map
Giao diện java.util.Map là giao diện cơ bản định nghĩa cấu trúc dữ liệu Map trong Java. Nó bao gồm các phương thức để thêm, truy xuất, cập nhật và xóa các cặp key-value, cũng như kiểm tra kích thước và tính chất của Map.
SortedMap
SortedMap là một interface trong Java mở rộng từ Map, và nó cung cấp một cách sắp xếp các key trong Map. Các phần tử trong SortedMap được sắp xếp theo thứ tự tăng dần của key. TreeMap là một lớp triển khai chính của giao diện SortedMap.
import java.util.SortedMap;
import java.util.TreeMap; public class SortedMapExample { public static void main(String[] args) { // Khởi tạo đối tượng SortedMap SortedMap<String, Integer> sortedMap = new TreeMap<>(); // Thêm các cặp key-value vào SortedMap sortedMap.put(“apple”, 3); sortedMap.put(“banana”, 5); sortedMap.put(“orange”, 2); // In ra các phần tử trong SortedMap for (String key : sortedMap.keySet()) { System.out.println(key + ” : ” + sortedMap.get(key)); } } } |
NavigableMap
Giao diện NavigableMap kế thừa giao diện SortedMap và cung cấp các phương thức để điều hướng và truy vấn các cặp key-value trong Map theo thứ tự được sắp xếp. TreeMap cũng là một lớp triển khai chính của giao diện NavigableMap.
import java.util.NavigableMap;
import java.util.TreeMap; public class NavigableMapExample { public static void main(String[] args) { // Khởi tạo đối tượng NavigableMap NavigableMap<String, Integer> navigableMap = new TreeMap<>(); // Thêm các cặp key-value vào NavigableMap navigableMap.put(“apple”, 3); navigableMap.put(“banana”, 5); navigableMap.put(“orange”, 2); // Lấy một NavigableMap con từ NavigableMap ban đầu NavigableMap<String, Integer> subMap = navigableMap.subMap(“banana”, true, “grape”, false); System.out.println(“SubMap from ‘banana’ to ‘grape’: ” + subMap); } } |
Lưu ý: Ngoài các phương thức kế thừa từ interface cha – map, Sortedmap và Navigablemap còn sở hữu những đặc điểm, ứng dụng và phương thức triển khai riêng. Tuy nhiên, trong bài này chúng ta sẽ không đi sâu vào vấn đề này. Ngoài ra, các interface không thể tự tạo đối tượng mà cần thông qua các lớp triển khai sau.
Lớp (class) triển khai Map
Java cung cấp nhiều lớp triển khai Map khác nhau để đáp ứng các nhu cầu lưu trữ dữ liệu khác nhau. Trong đó, 3 lớp triển khai phổ biến gồm: HashMap, LinkedHashMap và TreeMap.
HashMap
HashMap là một lớp triển khai của giao diện Map trong Java. Nó sử dụng bảng băm để lưu trữ dữ liệu, cho phép thao tác nhanh chóng với các phần tử của nó. Lưu ý rằng, khi sử dụng Hashmap thì thứ tự các phần thử sẽ không được đảm bảo. Ngoài ra, nó cho phép sử dụng cả key và value là null.
import java.util.HashMap;
import java.util.Map; public class HashMapExample { public static void main(String[] args) { // Bước 1: Khởi tạo đối tượng HashMap Map<String, Integer> hashMap = new HashMap<>(); // Bước 2: Thêm các cặp key-value bằng phương thức put() hashMap.put(“apple”, 3); hashMap.put(“banana”, 5); hashMap.put(“orange”, 2); // Bước 3: In ra đối tượng HashMap System.out.println(“HashMap: ” + hashMap); } } |
LinkedHashMap
LinkedHashMap kế thừa HashMap và triển khai giao diện Map. Nó duy trì thứ tự của các phần tử được thêm vào dựa trên thời điểm chúng được thêm. Nói cách khác, các phần tử được truy cập theo thứ tự chúng được thêm vào. Ngoài ra, LinkedHashmap cho phép sử dụng key và value là null.
import java.util.LinkedHashMap;
import java.util.Map; public class LinkedHashMapExample { public static void main(String[] args) { // Bước 1: Khởi tạo đối tượng LinkedHashMap Map<String, Integer> linkedHashMap = new LinkedHashMap<>(); // Bước 2: Thêm các cặp key-value bằng phương thức put() linkedHashMap.put(“apple”, 3); linkedHashMap.put(“banana”, 5); linkedHashMap.put(“orange”, 2); // Bước 3: In ra đối tượng LinkedHashMap System.out.println(“LinkedHashMap: ” + linkedHashMap); } } |
TreeMap
TreeMap là một lớp triển khai của giao diện Map và được xây dựng dựa trên cây đỏ đen (Red-Black Tree). Nó duy trì thứ tự của các phần tử dựa trên khóa (key) của chúng, nghĩa là các phần tử được sắp xếp theo khóa. Khác với hashmap và linkedhashmap, Treemap chỉ cho phép sử dụng value là null.
import java.util.Map;
import java.util.TreeMap; public class TreeMapExample { public static void main(String[] args) { // Bước 1: Khởi tạo đối tượng TreeMap Map<String, Integer> treeMap = new TreeMap<>(); // Bước 2: Thêm các cặp key-value bằng phương thức put() treeMap.put(“apple”, 3); treeMap.put(“banana”, 5); treeMap.put(“orange”, 2); // Bước 3: In ra đối tượng TreeMap System.out.println(“TreeMap: ” + treeMap); } } |
Lưu ý: Map Interface cung cấp nhiều phương thức cho các lớp triển khai. Tuy nhiên, các lớp trên cũng có những phương thức triển khai riêng, phù hợp với các trường hợp khác nhau. Tuy nhiên, trong bài này chúng ta sẽ không đi quá sâu. Bạn có thể xem các phương thức được cung cấp bởi Map Interface ở phần sau.
Tạo đối tượng trong Map Java
Map là một Interface trong Java, do đó, đối tượng (object) không thể được tạo trực tiếp từ map. Ta cần sử dụng một lớp triển khai cụ thể của map để có thể tạo đối tượng. Ngoài ra từ Java 1.5, ta có thể sử dụng kiểu Generics để giới thiệu kiểu đối tượng được lưu trữ trong Map. Ví dụ dễ hiểu, đối tượng ở đây là 1 cửa hàng gia dụng, nếu ta muốn giới hạn cửa hàng chỉ chứa các loại đèn, ta sử dụng kiểu generics.
Cú pháp khai báo đối tượng trong map sử dụng lớp Hashmap:
Map<KeyType, ValueType> mapName = new HashMap<KeyType, ValueType>(); |
Ví dụ:
Map<String, Integer> studentMarks = new HashMap<String, Integer>(); |
studentMarks là một đối tượng HashMap có thể lưu trữ các cặp key-value trong đó key là một chuỗi (strings) và value là một số nguyên (interger)
Map.Entry Interface
Trong Java, Map.Entry là một giao diện lồng nhúng (nested interface) được định nghĩa bên trong giao diện Map. Nó được sử dụng để biểu diễn một cặp key-value trong Map. Các đối tượng của giao diện Map.Entry chỉ có thể tồn tại trong một đối tượng Map.
Hay để dễ hiểu, bạn có thể tưởng tượng Map như một danh sách chứa các cặp thông tin, và Map.Entry là một cách để truy cập và xử lý từng cặp thông tin đó.
Trong Map.Entry, chúng ta sẽ làm quen với một thuật ngữ “entry” – dùng để chỉ đối tượng của interface Map.Entry trong Java. Nó đại diện cho một cặp key-value trong một đối tượng map.
Phương thức của Map.Entry Interface
Giao diện Map.Entry cung cấp các phương thức để truy cập, thay đổi và so sánh thông tin của từng cặp key-value trong một Map:
Phương thức | Mô tả |
getKey() | Trả về key (tên) của cặp key-value. |
getValue() | Trả về value (giá trị) của cặp key-value. |
setValue(V value) | Thay đổi giá trị của cặp key-value đang tồn tại. |
boolean equals(Object o) | So sánh đối tượng Map.Entry này với đối tượng được chỉ định, kiểm tra xem chúng có bằng nhau hay không. |
int hashCode() | Trả về giá trị băm (hash code) của đối tượng Map.Entry. |
Ví dụ:
import java.util.HashMap;
import java.util.Map; public class MapEntryExample { public static void main(String[] args) { // Tạo HashMap Map<String, Integer> hashMap = new HashMap<>(); hashMap.put(“one”, 1); hashMap.put(“two”, 2); hashMap.put(“three”, 3); // Duyệt qua các phần tử trong HashMap bằng cách sử dụng Map.Entry for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(“Key: ” + key + “, Value: ” + value); } } } |
Các phương thức của Map trong Java
Dưới đây là bảng tổng hợp các phương thức được tích hợp trong map:
Phương thức | Mô tả |
clear() | Xóa tất cả các entry (phần tử) từ Map. |
containsKey(Object key) | Kiểm tra xem Map có chứa key đã cho hay không. |
containsValue(Object value) | Kiểm tra xem Map có chứa value đã cho hay không. |
entrySet() | Trả về tập hợp các entry (phần tử) của Map, mỗi entry bao gồm một key và một value. |
get(Object key) | Trả về value được liên kết với key đã cho, hoặc null nếu không có entry nào có key đã cho. |
isEmpty() | Kiểm tra xem Map có bị trống (không có entry nào) hay không. |
keySet() | Trả về tập hợp các key của Map. |
put(K key, V value) | Thêm một entry vào Map với key và value đã cho, nếu key đã tồn tại, value mới sẽ thay thế value cũ. |
putAll(Map<? extends K, ? extends V> m) | Thêm tất cả các entry từ Map m đã cho vào Map hiện tại. |
remove(Object key) | Xóa entry có key đã cho khỏi Map và trả về value được liên kết với key đó, hoặc null nếu không có. |
size() | Trả về số lượng entry có trong Map. |
getOrDefault(Object key, V defaultValue) | Trả về giá trị được liên kết với key đã cho, hoặc trả về giá trị mặc định nếu không có entry nào có key đã cho. |
forEach(BiConsumer<? super K, ? super V> action) | Thực hiện một hành động cho mỗi entry trong Map cho đến khi tất cả các entry đã được xử lý hoặc hành động gặp ngoại lệ. |
replace(K key, V value) | Thay thế giá trị được liên kết với key đã cho (chỉ khi key đã tồn tại) bằng giá trị mới và trả về giá trị cũ, hoặc trả về null nếu không có entry nào có key đã cho. |
replace(K key, V oldValue, V newValue) | Thay thế giá trị được liên kết với key đã cho chỉ khi giá trị hiện tại của entry là giá trị đã cho (oldValue). Trả về true nếu giá trị đã được thay thế, ngược lại trả về false. |
replaceAll(BiFunction<? super K, ? super V, ? extends V> function) | Thay thế giá trị của mỗi entry với kết quả của việc áp dụng hàm cho cặp key-value của entry đó. |
compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | Tính toán giá trị mới cho key đã cho bằng cách sử dụng hàm remappingFunction với cặp key-value hiện tại (nếu có) và cập nhật entry với giá trị mới. Nếu hàm trả về null, entry sẽ bị xóa (nếu có). |
computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) | Nếu key đã cho không có trong Map, tính toán giá trị mới cho key bằng cách sử dụng hàm mappingFunction và thêm entry mới với key và giá trị mới vào Map (nếu giá trị mới không phải là null). |
computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | Nếu key đã cho có trong Map, tính toán giá trị mới cho key bằng cách sử dụng hàm remappingFunction với cặp key-value hiện tại và cập nhật entry với giá trị mới. Nếu hàm trả về null, entry sẽ bị xóa. |
merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) | Nếu key đã cho chưa có trong Map, thêm entry mới với key và giá trị đã cho. Nếu key đã tồn tại, tính toán giá trị mới cho key bằng cách sử dụng hàm remappingFunction với giá trị hiện tại và giá trị đã cho. Nếu hàm trả về null, entry sẽ bị xóa. |
equals(Object o) | So sánh đối tượng đã cho với Map này để xem chúng có bằng nhau hay không. |
hashCode() | Trả về giá trị hashCode của Map này. |
Lưu ý: Các phương thức của Map trong Java là các phương thức trừu tượng và không có triển khai cụ thể bên trong interface. Để sử dụng các phương thức này, chúng ta phải tạo một lớp triển khai của Map và cung cấp một triển khai cho các phương thức đó.
Một số thao tác cần lưu ý về Map trong Java
Khi làm việc với Map trong Java, bạn cần lưu ý một số vấn đề sau:
Kiểm tra giá trị Null
Ta sử dụng phương thức isEmpty() để kiểm tra xem map trong java có rỗng không trước khi sử dụng.
import java.util.*;
public class MapExample { public static void main(String[] args) { // Khởi tạo một Map mới Map<String, Integer> map = null; // Kiểm tra xem Map có rỗng hay không if (map == null || map.isEmpty()) { System.out.println(“Map là rỗng”); } else { System.out.println(“Map không rỗng”); } } } //kết quả: map là rỗng |
Xử lý ngoại lệ
Ta sử dụng phương thức getOrDefault(). Phương thức này được sử dụng để lấy giá trị của một entry trong Map bằng Key, nhưng nếu Key không tồn tại thì trả về một giá trị mặc định.
import java.util.*;
public class MapExample { public static void main(String[] args) { // Khởi tạo một Map mới Map<String, Integer> map = new HashMap<>(); // Chèn các entry vào Map map.put(“apple”, 50); map.put(“banana”, 30); map.put(“orange”, 70); // Lấy giá trị của một entry trong Map bằng Key, nếu không tồn tại thì trả về giá trị mặc định int pearCount = map.getOrDefault(“pear”, 0); System.out.println(“Số lượng lê: ” + pearCount); } } // số lượng lê: 0 |
Lấy tập hợp Key
Ta sử dụng phương thức keySet() để lấy tập hợp các Key trong Map
import java.util.*;
public class MapExample { public static void main(String[] args) { // Khởi tạo một Map mới với kiểu dữ liệu String cho Key và Integer cho Value Map<String, Integer> map = new HashMap<>(); // Chèn các entry vào Map map.put(“apple”, 50); map.put(“banana”, 30); map.put(“orange”, 70); // Lấy tập hợp các Key trong Map Set<String> keySet = map.keySet(); // In tập hợp các Key trong Map System.out.println(keySet); } } //kết quả: [apple, banana, orange] |
Tính giá trị mới cho Entry trong Map
Ở đây, ta sử dụng phương thức computeIfAbsent() để tính toán giá tị mới cho một entry trong map nếu key chưa tồn tại.
import java.util.*;
public class MapExample { public static void main(String[] args) { // Khởi tạo một Map mới với kiểu dữ liệu String cho Key và List<String> cho Value Map<String, List<String>> map = new HashMap<>(); // Chèn các entry vào Map map.put(“fruits”, Arrays.asList(“apple”, “banana”, “orange”)); // Tính toán giá trị mới cho một entry trong Map nếu Key chưa tồn tại trong Map map.computeIfAbsent(“vegetables”, k -> new ArrayList<>()).add(“carrot”); // In các entry của Map System.out.println(map); } } //kết quả: {fruits=[apple, banana, orange], vegetables=[carrot]} |
Chèn entry mới vào Map
Ta sử dụng phương thức putIfAbsent() để chèn một entry mới vào Map nếu Key chưa tồn tại trong Map.
import java.util.*;
public class MapExample { public static void main(String[] args) { // Khởi tạo một Map mới với kiểu dữ liệu String cho Key và Integer cho Value Map<String, Integer> map = new HashMap<>(); // Chèn các entry vào Map map.put(“apple”, 50); map.put(“banana”, 30); map.put(“orange”, 70); // Chèn một entry mới vào Map nếu Key chưa tồn tại trong Map map.putIfAbsent(“pear”, 40); // In các entry của Map System.out.println(map); } } //kết quả: {orange=70, pear=40, apple=50, banana=30} |
Kết luận
Trong bài viết này, chúng ta đã tìm hiểu chi tiết về Map trong Java, bao gồm khái niệm, cấu trúc phân tầng cũng như một số lưu ý và một số thao tác với map. Hy vọng qua đây, đã giúp bạn hiểu hơn về Map cũng như cách sử dụng giao diện này trong Java.
Nếu bạn đang tìm hiểu về các khóa học lập trình Java, tham khảo ngay Rikkei Academy! Với chương trình tinh gọn, bám sát thực tế cùng đội ngũ giảng viên tận tâm, hỗ trợ 24/7 sẽ giúp bạn có thể trở thành lập trình viên trong 6 tháng. Để biết thêm thông tin về khóa học, đăng ký để nhận tư vấn miễn phí ngay tại đây!