Menu

HashSet trong Java: giá trị duy nhất và phép toán tập hợp

Cách dùng HashSet của Java cho tập giá trị không trùng lặp: add, contains, remove, loại bỏ phần tử trùng trong list, và hợp, giao, hiệu các tập hợp.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Set lưu các giá trị duy nhất

HashSet (trong java.util) là một collection chứa mỗi giá trị tối đa một lần. Không có cặp khóa và giá trị như trong HashMap, chỉ có một túi các phần tử khác nhau. Toàn bộ nhiệm vụ của nó là trả lời thật nhanh một câu hỏi: "thứ này có ở trong đây không?"

Chỉ có một tham số kiểu, <ElementType>. Cũng như với ArrayList và HashMap, bạn thường khai báo biến theo interface Set và tạo đối tượng HashSet.

add cho biết giá trị có mới hay không

add không chỉ lưu giá trị: nó trả về một boolean cho biết set có thực sự thay đổi hay không. Thêm một giá trị đã có sẵn sẽ trả về false và set giữ nguyên.

Giá trị trả về đó thực sự hữu ích: if (!seen.add(x)) { /* x is a repeat */ } giúp bạn phát hiện phần tử trùng chỉ trong một dòng khi đang duyệt.

Xóa phần tử trùng trong list

Vì set từ chối giá trị lặp lại, cách nhanh nhất để loại bỏ trùng lặp trong một collection là đổ nó vào một set. Constructor của HashSet nhận bất kỳ collection nào khác:

Đây là lý do phổ biến nhất khiến người mới học dùng set. Chỉ cần nhớ rằng bạn mất thứ tự ban đầu sau khi chuyển qua lại: hãy dùng LinkedHashSet nếu thứ tự quan trọng (xem bên dưới).

contains, remove và size

Các thao tác thường ngày giống như ở các collection khác:

Lợi thế lớn so với ArrayList nằm ở contains. List phải duyệt qua từng phần tử để trả lời (O(n)); HashSet gần như nhảy thẳng tới câu trả lời (khoảng O(1)). Khi thấy mình gọi list.contains(...) bên trong một vòng lặp, đó thường là dấu hiệu nên chuyển sang set.

Phép toán tập hợp: hợp, giao, hiệu

Set phát huy sức mạnh khi bạn kết hợp chúng. Tên các phương thức dễ hiểu ngay khi bạn biết cái nào làm gì:

Điểm cần lưu ý: addAll, retainAll và removeAll thay đổi chính set được gọi. Đó là lý do mỗi ví dụ đều sao chép a sang một HashSet mới trước: nếu không, bạn sẽ phá hỏng set ban đầu. Hãy tạo một set mới cho mỗi kết quả.

HashSet không giữ thứ tự

Giống HashMap, HashSet không cam kết gì về thứ tự duyệt, và thứ tự có thể khác nhau giữa các lần chạy. Nếu bạn cần thứ tự dự đoán được:

  • LinkedHashSet giữ thứ tự thêm vào, tức thứ tự bạn thêm các phần tử.
  • TreeSet giữ các phần tử được sắp xếp theo thứ tự tự nhiên (hoặc theo Comparator bạn cung cấp).

Cả ba đều cài đặt interface Set, nên muốn đổi qua lại chỉ cần sửa một dòng ở constructor.

Phần tử phải băm được

Bên dưới, HashSet được xây dựng trên một HashMap, nên cùng một quy tắc được áp dụng: nó định vị phần tử bằng cách băm, nghĩa là hashCode() và equals() của phần tử phải nhất quán. Các kiểu có sẵn như String và Integer đã làm đúng điều này, đó là lý do các chuỗi "java" trùng nhau ở trên được gộp lại chính xác. Nếu bạn lưu instance của lớp do mình viết, hãy override cả equals lẫn hashCode: nếu không, hai đối tượng "bằng nhau" về ý nghĩa sẽ bị coi là khác nhau, và contains cũng như việc loại bỏ trùng lặp sẽ lặng lẽ thất bại.

Tiếp theo: duyệt collection

Giờ bạn đã làm quen với ba collection chủ lực: ArrayList, HashMap và HashSet. Mỗi loại được duyệt theo cách hơi khác nhau, và có những cái bẫy tinh vi (như thay đổi collection trong lúc đang lặp qua nó). Tiếp theo, chúng ta sẽ tổng hợp lại và học cách duyệt collection gọn gàng bằng vòng lặp for-each, iterator và forEach.

Câu hỏi thường gặp

Làm sao tạo HashSet trong Java?

Khai báo với một tham số kiểu, là kiểu của phần tử, rồi gọi constructor: Set<String> tags = new HashSet<>();. Thêm giá trị bằng tags.add("java"); và kiểm tra phần tử có trong tập hay không bằng tags.contains("java");. Nhớ import java.util.HashSet và java.util.Set.

HashSet và ArrayList trong Java khác nhau thế nào?

ArrayList giữ mọi phần tử bạn thêm vào (kể cả trùng lặp) theo thứ tự thêm và được đánh chỉ số theo vị trí. HashSet chỉ lưu giá trị không trùng lặp, không cam kết thứ tự, không có chỉ số, và phép kiểm tra contains của nó chạy trong thời gian gần như hằng số thay vì phải quét cả danh sách. Hãy dùng HashSet khi bạn quan tâm tới tính duy nhất hoặc kiểm tra nhanh, không phải vị trí.

Làm sao xóa phần tử trùng lặp khỏi list trong Java?

Truyền list vào constructor của HashSet: Set<String> unique = new HashSet<>(list);. Set tự động loại bỏ các giá trị lặp lại. Nếu cần lấy lại một list (và không ngại mất thứ tự), hãy bọc nó thêm lần nữa: new ArrayList<>(unique). Dùng LinkedHashSet nếu bạn muốn giữ nguyên thứ tự ban đầu.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu