markdown.## 1. Set là gì?
Trong lập trình Java, một Set là một trong những kiểu collection quan trọng nhất. Từ “Set” bắt nguồn từ toán học, và giống như một tập hợp toán học, nó có đặc điểm then chốt là không thể chứa các phần tử trùng lặp.
Set được sử dụng khi bạn muốn quản lý chỉ các giá trị duy nhất, bất kể kiểu dữ liệu là số, chuỗi hay đối tượng.
Sự khác biệt giữa Set và List là gì?
Java Collections Framework cung cấp nhiều cấu trúc dữ liệu như List và Map. Trong số đó, Set và List thường được so sánh. Các điểm khác nhau chính như sau:
- List : Cho phép các giá trị trùng lặp và bảo toàn thứ tự phần tử (dựa trên chỉ mục).
- Set : Không cho phép trùng lặp, và thứ tự phần tử không được đảm bảo (trừ một số triển khai cụ thể).
Nói ngắn gọn, List là “collection có thứ tự”, trong khi Set là “collection của các phần tử duy nhất”.
Ví dụ, nếu bạn muốn quản lý các ID người dùng mà không có sự trùng lặp, Set là lựa chọn lý tưởng.
Lợi ích của việc sử dụng Set
- Tự động loại bỏ trùng lặp Ngay cả khi nhận một lượng lớn dữ liệu từ người dùng, chỉ cần thêm các phần tử vào Set thì các phần tử trùng lặp sẽ chỉ được lưu một lần. Điều này loại bỏ nhu cầu kiểm tra trùng lặp thủ công và đơn giản hoá việc triển khai.
- Tìm kiếm và xóa hiệu quả Set được thiết kế để thực hiện kiểm tra tồn tại và các thao tác xóa nhanh chóng, mặc dù hiệu năng có thể thay đổi tùy vào triển khai (như
HashSethayTreeSet).
Khi nào nên sử dụng Set?
- Khi quản lý thông tin phải không được trùng lặp, chẳng hạn như địa chỉ email hoặc ID người dùng
- Khi cần đảm bảo tính duy nhất của dữ liệu
- Khi muốn tạo một danh sách các giá trị duy nhất một cách hiệu quả từ một tập dữ liệu lớn
Như đã nêu ở trên, Set là cơ chế tiêu chuẩn trong Java để xử lý thông minh các collection không cho phép trùng lặp.
Trong các phần tiếp theo, chúng ta sẽ khám phá chi tiết các đặc tả của Set, các mẫu sử dụng, và các ví dụ mã cụ thể.
2. Đặc tả cơ bản và lợi ích của Set
Trong Java, Set được định nghĩa bởi giao diện java.util.Set. Bằng cách triển khai giao diện này, bạn có thể biểu diễn một collection các phần tử duy nhất, không có trùng lặp. Hãy xem xét kỹ hơn các đặc tả cốt lõi và lợi thế của Set.
Các đặc điểm cơ bản của giao diện Set
Set có các đặc điểm sau:
- Không có phần tử trùng lặp Nếu bạn cố gắng thêm một phần tử đã tồn tại, nó sẽ không được thêm. Ví dụ, ngay cả khi bạn thực thi
set.add("apple")hai lần, chỉ có một “apple” sẽ được lưu. - Thứ tự không được đảm bảo (phụ thuộc vào triển khai) Mặc định Set không bảo đảm thứ tự phần tử. Tuy nhiên, một số triển khai như
LinkedHashSetvàTreeSetquản lý các phần tử theo một thứ tự nhất định. - Xử lý phần tử null Việc cho phép null phụ thuộc vào triển khai. Ví dụ,
HashSetcho phép một phần tử null, trong khiTreeSetkhông cho phép.
Tầm quan trọng của equals và hashCode
Việc hai phần tử có được coi là trùng lặp trong một Set hay không được quyết định bởi các phương thức equals và hashCode.
Khi sử dụng các lớp tùy chỉnh làm phần tử của Set, nếu không ghi đè đúng các phương thức này có thể dẫn đến việc xuất hiện các phần tử trùng lặp không mong muốn hoặc hành vi lưu trữ không chính xác.
equals: Xác định hai đối tượng có bằng nhau về mặt logic hay khônghashCode: Trả về một giá trị số dùng để nhận dạng hiệu quả
Lợi ích khi sử dụng Set
Set mang lại một số ưu điểm thực tiễn:
- Dễ dàng loại bỏ trùng lặp Chỉ cần thêm các giá trị vào Set, các phần tử trùng lặp sẽ tự động bị loại bỏ, không cần kiểm tra thủ công.
- Tìm kiếm và xóa hiệu quả Các triển khai như
HashSetcung cấp các thao tác tra cứu và xóa nhanh, thường vượt trội hơn List. - API đơn giản và trực quan Các phương thức cơ bản như
add,removevàcontainskhiến Set dễ sử dụng.
Cài đặt nội bộ và hiệu năng
Một trong những triển khai Set phổ biến nhất, HashSet, bên trong sử dụng HashMap để quản lý các phần tử. Điều này cho phép thêm, xóa và tra cứu phần tử được thực hiện với độ phức tạp thời gian trung bình O(1).
Nếu cần thứ tự hoặc sắp xếp, bạn có thể chọn các triển khai như LinkedHashSet hoặc TreeSet tùy theo nhu cầu của bạn.
3. Các Lớp Triển Khai Chính và Đặc Điểm Của Chúng
Java cung cấp một số triển khai chính của giao diện Set. Mỗi cái có đặc điểm khác nhau, vì vậy việc chọn cái phù hợp cho trường hợp sử dụng của bạn là quan trọng.
Ở đây, chúng tôi sẽ giải thích ba triển khai được sử dụng phổ biến nhất: HashSet, LinkedHashSet, và TreeSet.
HashSet
HashSet là triển khai Set được sử dụng phổ biến nhất.
- Đặc điểm
- Không bảo toàn thứ tự phần tử (thứ tự chèn và thứ tự lặp có thể khác nhau).
- Bên trong sử dụng
HashMap, cung cấp các hoạt động thêm, tìm kiếm và xóa nhanh. - Cho phép một phần tử
null. - Các Trường Hợp Sử Dụng Điển Hình
- Lý tưởng khi bạn muốn loại bỏ các phần tử trùng lặp và thứ tự không quan trọng.
- Mã Mẫu
Set<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); set.add("apple"); // Duplicate is ignored for (String s : set) { System.out.println(s); // Only "apple" and "banana" are printed }
LinkedHashSet
LinkedHashSet mở rộng chức năng của HashSet bằng cách bảo toàn thứ tự chèn.
- Đặc điểm
- Các phần tử được lặp theo thứ tự chúng được chèn.
- Bên trong được quản lý bằng cách kết hợp bảng băm và danh sách liên kết.
- Chậm hơn một chút so với
HashSet, nhưng hữu ích khi thứ tự quan trọng. - Các Trường Hợp Sử Dụng Điển Hình
- Tốt nhất khi bạn muốn loại bỏ trùng lặp trong khi duy trì thứ tự chèn.
- Mã Mẫu
Set<String> set = new LinkedHashSet<>(); set.add("apple"); set.add("banana"); set.add("orange"); for (String s : set) { System.out.println(s); // Printed in order: apple, banana, orange }
TreeSet
TreeSet là triển khai Set tự động sắp xếp các phần tử.
- Đặc điểm
- Bên trong sử dụng Cây Đỏ-Đen (một cấu trúc cây cân bằng).
- Các phần tử được sắp xếp tự động theo thứ tự tăng dần.
- Thứ tự tùy chỉnh có thể thực hiện bằng
ComparablehoặcComparator. - Không cho phép giá trị
null. - Các Trường Hợp Sử Dụng Điển Hình
- Hữu ích khi bạn cần cả tính duy nhất và sắp xếp tự động.
- Mã Mẫu
Set<Integer> set = new TreeSet<>(); set.add(30); set.add(10); set.add(20); for (Integer n : set) { System.out.println(n); // Printed in order: 10, 20, 30 }
Tóm tắt
- HashSet : Tốt nhất cho hiệu suất cao khi không cần thứ tự
- LinkedHashSet : Sử dụng khi thứ tự chèn quan trọng
- TreeSet : Sử dụng khi cần sắp xếp tự động
Việc chọn triển khai Set phù hợp phụ thuộc vào yêu cầu cụ thể của bạn. Chọn cái phù hợp nhất và sử dụng nó hiệu quả.
4. Các Phương Thức Phổ Biến và Cách Sử Dụng Chúng
Giao diện Set cung cấp các phương thức khác nhau cho các hoạt động bộ sưu tập. Dưới đây là các phương thức được sử dụng phổ biến nhất, được giải thích với ví dụ.
Các Phương Thức Chính
add(E e)Thêm một phần tử vào Set. Nếu phần tử đã tồn tại, nó không được thêm.remove(Object o)Xóa phần tử được chỉ định khỏi Set. Trả về true nếu thành công.contains(Object o)Kiểm tra xem Set có chứa phần tử được chỉ định không.size()Trả về số lượng phần tử trong Set.clear()Xóa tất cả các phần tử khỏi Set.isEmpty()Kiểm tra xem Set có rỗng không.iterator()Trả về một Iterator để duyệt qua các phần tử.toArray()Chuyển đổi Set thành mảng.
Ví Dụ Sử Dụng Cơ Bản
Set<String> set = new HashSet<>();
// Add elements
set.add("apple");
set.add("banana");
set.add("apple"); // Duplicate ignored
// Get size
System.out.println(set.size()); // 2
// Check existence
System.out.println(set.contains("banana")); // true
// Xóa phần tử
set.remove("banana");
System.out.println(set.contains("banana")); // false
// Xóa tất cả phần tử
set.clear();
System.out.println(set.isEmpty()); // true
Iterating Over a Set
Since Set does not support index-based access (e.g., set.get(0)), use an Iterator or enhanced for-loop.
// Vòng lặp for nâng cao
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");
for (String s : set) {
System.out.println(s);
}
// Sử dụng Iterator
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
Important Notes
- Adding an existing element using
adddoes not change the Set. - Element order depends on the implementation (HashSet: unordered, LinkedHashSet: insertion order, TreeSet: sorted).
5. Common Use Cases and Typical Scenarios
Java Sets are widely used in many situations where duplicate values must be avoided. Below are some of the most common and practical use cases encountered in real-world development.
Creating a Unique List (Duplicate Removal)
When you want to extract only unique values from a large dataset, Set is extremely useful.
For example, it can automatically remove duplicates from user input or existing collections.
Example: Creating a Set from a List to Remove Duplicates
List<String> list = Arrays.asList("apple", "banana", "apple", "orange");
Set<String> set = new HashSet<>(list);
System.out.println(set); // [apple, banana, orange]

Ensuring Input Uniqueness
Sets are ideal for scenarios where duplicate values must not be registered, such as user IDs or email addresses.
You can immediately determine whether a value already exists by checking the return value of add.
Set<String> emailSet = new HashSet<>();
boolean added = emailSet.add("user@example.com");
if (!added) {
System.out.println("Giá trị này đã được đăng ký");
}
Storing Custom Classes and Implementing equals/hashCode
When storing custom objects in a Set, proper implementation of equals and hashCode is essential.
Without them, objects with the same logical content may be treated as different elements.
Example: Ensuring Uniqueness in a Person Class
class Person {
String name;
Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
// Ví dụ sử dụng
Set<Person> people = new HashSet<>();
people.add(new Person("Taro"));
people.add(new Person("Taro")); // Nếu không triển khai đúng, có thể xảy ra trùng lặp
System.out.println(people.size()); // 1
Fast Lookup and Data Filtering
Because Set provides fast lookups via contains, it is often used for filtering and comparison tasks.
Converting a List to a Set can significantly improve performance when repeatedly checking for existence.
Example: Fast Keyword Lookup
Set<String> keywordSet = new HashSet<>(Arrays.asList("java", "python", "c"));
boolean found = keywordSet.contains("python"); // true
6. Performance Considerations and Pitfalls
While Set is a powerful collection for managing unique elements, improper usage can lead to unexpected behavior or performance issues. This section explains key performance characteristics and common pitfalls.
Performance Differences by Implementation
answer. HashSet Sử dụng bảng băm nội bộ, cung cấp hiệu năng trung bình O(1) cho các thao tác thêm, xóa và tra cứu. Hiệu năng có thể giảm nếu số lượng phần tử trở nên cực lớn hoặc nếu va chạm băm xảy ra thường xuyên.
LinkedHashSet Hiệu năng tương tự HashSet, nhưng có chi phí bổ sung do duy trì thứ tự chèn. Trong hầu hết các trường hợp, sự khác biệt là không đáng kể trừ khi xử lý tập dữ liệu rất lớn.
TreeSet Sử dụng Cây Đỏ-Đen nội bộ, mang lại hiệu năng O(log n)* cho các thao tác thêm, xóa và tra cứu. Chậm hơn HashSet, nhưng cung cấp sắp xếp tự động.
Sử dụng các đối tượng có thể thay đổi làm phần tử của Set
Cần thận trọng đặc biệt khi lưu các đối tượng có thể thay đổi trong một Set.
HashSet và TreeSet dựa vào giá trị hashCode hoặc compareTo để quản lý các phần tử.
Nếu những giá trị này thay đổi sau khi chèn, việc tra cứu và xóa có thể thất bại.
Ví dụ: Cạm bẫy với các đối tượng có thể thay đổi
Set<Person> people = new HashSet<>();
Person p = new Person("Taro");
people.add(p);
p.name = "Jiro"; // Modifying after insertion
people.contains(p); // May return false unexpectedly
Để tránh các vấn đề này, nên sử dụng các đối tượng bất biến làm phần tử của Set bất cứ khi nào có thể.
Xử lý giá trị null
- HashSet / LinkedHashSet : Cho phép một phần tử null
- TreeSet : Không cho phép null (ném NullPointerException)
Các lưu ý quan trọng khác
- Sửa đổi trong khi lặp Việc sửa đổi một Set trong khi đang lặp có thể gây ra
ConcurrentModificationException. Hãy sử dụngIterator.remove()thay vì sửa đổi Set trực tiếp. - Chọn triển khai phù hợp Sử dụng
LinkedHashSethoặcTreeSetkhi thứ tự quan trọng.HashSetkhông đảm bảo thứ tự.
7. Bảng so sánh (Tổng quan)
Bảng dưới đây tóm tắt các khác biệt giữa các triển khai Set chính để dễ so sánh.
| Implementation | No Duplicates | Order Preserved | Sorted | Performance | null Allowed | Typical Use Case |
|---|---|---|---|---|---|---|
| HashSet | Yes | No | No | Fast (O(1)) | One allowed | Duplicate removal, order not required |
| LinkedHashSet | Yes | Yes (Insertion order) | No | Slightly slower than HashSet | One allowed | Duplicate removal with order preservation |
| TreeSet | Yes | No | Yes (Automatic) | O(log n) | Not allowed | Duplicate removal with sorting |
Những điểm chính cần nhớ
- HashSet : Lựa chọn mặc định khi thứ tự không quan trọng và hiệu năng là yếu tố quan trọng.
- LinkedHashSet : Tốt nhất khi cần bảo toàn thứ tự chèn.
- TreeSet : Lý tưởng khi cần sắp xếp tự động.
8. Câu hỏi thường gặp (FAQ)
Q1. Kiểu nguyên thủy (int, char, v.v.) có thể được sử dụng trong Set không?
A1. Không. Hãy sử dụng các lớp wrapper như Integer hoặc Character thay thế.
Q2. Điều gì xảy ra nếu cùng một giá trị được thêm nhiều lần?
A2. Chỉ lần chèn đầu tiên được lưu. Phương thức add sẽ trả về false nếu phần tử đã tồn tại.
Q3. Khi nào nên dùng List so với Set?
A3. Dùng List khi thứ tự hoặc trùng lặp quan trọng, và Set khi cần tính duy nhất.
Q4. Cần gì để lưu các đối tượng tùy chỉnh trong Set?
A4. Ghi đè đúng equals và hashCode.
Q5. Làm sao để bảo toàn thứ tự chèn?
A5. Sử dụng LinkedHashSet.
Q6. Làm sao để tự động sắp xếp các phần tử?
A6. Sử dụng TreeSet.
Q7. Set có thể chứa giá trị null không?
A7. HashSet và LinkedHashSet cho phép một null; TreeSet không cho phép.
Q8. Làm sao để lấy kích thước của Set?
A8. Dùng size().
Q9. Làm sao để chuyển Set thành List hoặc mảng?
A9.
- Để thành mảng:
toArray() - Để thành List:
new ArrayList<>(set)
Q10. Tôi có thể xóa phần tử khi đang lặp không?
A10. Có, nhưng chỉ bằng cách sử dụng Iterator.remove().
9. Kết luận
Bài viết này đã đề cập đến các bộ sưu tập Set trong Java từ cơ bản đến nâng cao. Các điểm chính bao gồm:
- Set được thiết kế để quản lý các tập hợp các phần tử duy nhất, làm cho nó lý tưởng cho việc loại bỏ trùng lặp.
- Các triển khai chính bao gồm HashSet (nhanh, không có thứ tự), LinkedHashSet (giữ thứ tự chèn), và TreeSet (có thứ tự).
- Các trường hợp sử dụng phổ biến bao gồm loại bỏ trùng lặp, kiểm tra tính duy nhất, quản lý các đối tượng tùy chỉnh, và tra cứu nhanh.
- Hiểu các đặc điểm hiệu năng và các cạm bẫy như đối tượng có thể thay đổi và quy tắc lặp là cần thiết.
- Bảng so sánh và phần FAQ cung cấp hướng dẫn thực tế cho phát triển thực tế.
Việc làm chủ các bộ sưu tập Set giúp lập trình Java sạch hơn, an toàn hơn và hiệu quả hơn.
Tiếp theo, hãy xem xét việc kết hợp Sets với Lists hoặc Maps để xây dựng các cấu trúc dữ liệu và giải pháp nâng cao hơn.


