Hướng dẫn và ví dụ Java Set

1- Set

Set là một interface nằm trong nền tảng tập hợp của Java (Java Collection Framework), và là một interface con của Collection vì vậy nó có đầy đủ các tính năng của một Collection.

Set là một Collection không tuần tự (unordered Collection), không cho phép các phần tử trùng lặp, và có thể chứa nhiều nhất 1 phần tử null. Nếu bạn cố tình thêm một phần tử trùng lặp vào Set hành động này sẽ bị bỏ qua, Set sẽ không thay đổi.


public interface Set extends Collection

SortedSet là interface con của Set, nó có khả năng tự động xắp xếp các phần tử theo thứ tự tự nhiên của chúng hoặc dựa trên một Comparator được cung cấp.

Hệ thống phân cấp các lớp thi hành (implement) interface Set.

Các phương thức của interface Set:

Set interface


boolean add​(E e)  
boolean addAll​(Collection c)  

void clear()  

boolean contains​(Object o)  
boolean containsAll​(Collection c)  

boolean equals​(Object o)  
int hashCode()  
boolean isEmpty()  
int size()  

boolean remove​(Object o)  
boolean removeAll​(Collection c)  
boolean retainAll​(Collection c)  

Object[] toArray()  
 T[] toArray​(T[] a)

Iterator iterator()  
default Spliterator spliterator()  

static  Set copyOf​(Collection coll)  

static  Set of()  
static  Set of​(E e1)  
static  Set of​(E... elements)  
static  Set of​(E e1, E e2)  
static  Set of​(E e1, E e2, E e3)  
static  Set of​(E e1, E e2, E e3, E e4)  
static  Set of​(E e1, E e2, E e3, E e4, E e5)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)  

2- Simple Examples

Set là một interface, vì vậy để tạo một đối tượng Set bạn cần tạo thông qua một lớp thi hành nó, chẳng hạn HashSet, LinkedHashSet, TreeSet,..

Ví dụ dưới đây chúng ta tạo một đối tượng HashSet với sức chứa (capacity) ban đầu là 10 phần tử, và sức chứa sẽ tăng lên 80% nếu số phần tử của nó vượt quá sức chứa hiện tại.

SetEx1. java


package org.o7planning.set.ex;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetEx1 {

    public static void main(String[] args) {
        Set set = new HashSet(10, (float) 0.8);

        set.add("One");
        set.add("Two");
        set.add("Three");
        
        Iterator it1 = set.iterator();
        while (it1.hasNext()) {
            System.out.println(it1.next());
        }
        
        System.out.println(" ----- ");
        
        // When duplication occurs.
        // It will add new element and remove old element.
        set.add("Two");
        set.add("Four");

        Iterator it2 = set.iterator();
        while (it2.hasNext()) {
            System.out.println(it2.next());
        }
    }
}

Chú ý rằng: Set là một Collection không có thứ tự (unordered Collection) nên bạn có thể nhận được một kết quả hơi khác khi in các phần tử ra màn hình Console.

Output :


One
Two
Three
 -----
One
Four
Two
Three

Array — > Set ?

Ví dụ chuyển đổi một mảng thành một đối tượng Set.

ArrayToSetEx. java


package org.o7planning.set.ex;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class ArrayToSetEx {
    
    public static void main(String[] args) {
        Integer[] sourceArray = { 0, 3, 5, 3, 4, 5 };
        
        Set targetSet = new HashSet(Arrays.asList(sourceArray));
    
        for(Integer i : targetSet)  {
            System.out.println(i);
        }
    }  
}

Output :


0
3
4
5

Set là một interface con của Collection, vì vậy nó được thừa kế phương thức stream(). Truy cập vào các phần tử của một Collection thông qua Stream sẽ giúp code của bạn ngắn gọn và dễ hiểu hơn:

Set_stream. java


package org.o7planning.set.ex;

import java.util.HashSet;
import java.util.Set;

public class Set_stream {

    public static void main(String[] args) {  
        Set set = new HashSet();
        
        set.add("a1");
        set.add("b1");
        set.add("a2");  
        set.add("c1");  
        set.add("d1");  
        set.add("e1");  
        
        set.stream() //
              .map(String::toUpperCase) // to upsercase
              .filter(s -> !s.startsWith("A")) // Not starts with "A".
              .forEach(System.out::println);  
    }  
}

Output :


E1
D1
C1
B1

iterator() là phương thức thừa kế từ Collection, nó trả về một đối tượng Iterator để lặp (iterate) trên các phần tử của Collection.


Iterator iterator()  


Set_iterator. java


package org.o7planning.set.ex;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Set_iterator {

    public static void main(String[] args) {  
        Set set = new HashSet();
        
        set.add("a1");
        set.add("b1");
        set.add("a2");  
        
        Iterator ite = set.iterator();
        
        while(ite.hasNext()) {
            System.out.println(ite.next());
        }
    }  
}

Output :


a1
a2
b1

5- Set kiểm tra trùng lặp thế nào?

Set là một Collection không cho phép chứa các phần tử trùng lặp, nếu bạn cố tình thêm một phần tử trùng lặp vào Set, hành động này sẽ bị bỏ qua. Vậy câu hỏi đặt ra là làm thế nào để “Set” kiểm tra sự trùng lặp?

SortedSet là một interface con của Set. Trong phần này chúng ta chỉ đề cập tới các kiểu Set thuần thuý (không phải là SortedSet). Các lớp đại diện cho một Set thuần thuý là HashSet, LinkedHashSet, CopyOnWriteArraySet, EnumSet.

Cách SortedSet kiểm tra sự trùng lặp khác biệt hoàn toàn với Set thuần thuý, và được đề cập trong bài viết dưới đây:

  • Java SortedSet

HashSet / LinkedHashSet

HashSet (và LinkedHashSet) sẽ so sánh hashcode (mã băm) của 2 phần tử, sau đó so sánh trực tiếp 2 phần tử thông qua phương thức equals của chúng.

Hãy xem 2 ví dụ HashSetDupTest1 & HashSetDupTest2 dưới đây để hiểu hơn về nhận xét trên.

HashSetDupTest1. java


package org.o7planning.set.test;

import java.util.HashSet;
import java.util.Set;

public class HashSetDupTest1 {

    public static class Student {
        private int studentId;
        private String studentName;
        
        public Student(int studentId, String studentName) {
            this.studentId = studentId;
            this.studentName = studentName;
        }
        public int getStudentId() {
            return studentId;
        }
        public String getStudentName() {
            return studentName;
        }
        @Override
        public boolean equals(Object other)  {
            if(other == null || !(other instanceof Student)) {
                return false;
            }
            Student o = (Student) other;
            return this.studentId == o.studentId;
        }
    }
    
    public static void main(String[] args) {
        Student s1 = new Student(1, "Tom");
        Student s2 = new Student(2, "Jerry");
        Student s3 = new Student(1, "Tom Cat"); // Same Id with s1.
        
        Set set = new HashSet();
        
        set.add(s1);
        set.add(s2);
        set.add(s3);

        for(Student s: set)  {
            System.out.printf("Student Id: %d / Name: %s (Hashcode: %s)\n", //
                    s.getStudentId(),s.getStudentName(),s.hashCode());
        }
    }
}

Output :


Student Id: 2 / Name: Jerry (Hashcode: 474675244)
Student Id: 1 / Name: Tom Cat (Hashcode: 932583850)
Student Id: 1 / Name: Tom (Hashcode: 1586600255)

Sửa lại ví dụ trên bằng cách ghi đè (override) phương thức hashCode() trong lớp Student:

HashSetDupTest2. java


package org.o7planning.set.test;

import java.util.HashSet;
import java.util.Set;

public class HashSetDupTest2 {

    public static class Student {
        private int studentId;
        private String studentName;
        
        public Student(int studentId, String studentName) {
            this.studentId = studentId;
            this.studentName = studentName;
        }
        public int getStudentId() {
            return studentId;
        }
        public String getStudentName() {
            return studentName;
        }
        @Override
        public boolean equals(Object other)  {
            if(other == null || !(other instanceof Student)) {
                return false;
            }
            Student o = (Student) other;
            return this.studentId == o.studentId;
        }
        @Override
        public int hashCode() { // -----> Override hashCode() method.
            return this.studentName.charAt(0);
        }
    }
    
    public static void main(String[] args) {
        Student s1 = new Student(1, "Tom");   // Hashcode: 84 ('T')
        Student s2 = new Student(1, "Tom Cat"); // Hashcode: 84 ('T')
        
        Student s3 = new Student(2, "Jerry"); // Hashcode: 74 ('J')
        
        Student s4 = new Student(4, "Daffy"); // Hashcode: 68 ('D')
        Student s5 = new Student(5, "Donald"); // Hashcode: 68 ('D')
        
        
        Set set = new HashSet();
        
        set.add(s1);
        set.add(s2); // a Duplication (Ignored!)
        set.add(s3);
        set.add(s4);  
        set.add(s5);  

        for(Student s: set)  {
            System.out.printf("Student Id: %d / Name: %s (Hashcode: %s)\n", //
                    s.getStudentId(),s.getStudentName(),s.hashCode());
        }
    }
}

Output :


Student Id: 1 / Name: Tom (Hashcode: 84)
Student Id: 4 / Name: Daffy (Hashcode: 68)
Student Id: 5 / Name: Donald (Hashcode: 68)
Student Id: 2 / Name: Jerry (Hashcode: 74)

CopyOnWriteArraySet

CopyOnWriteArraySet so sánh trực tiếp 2 phần tử thông qua phương thức equals của chúng.

Hãy xem 2 ví dụ CopyOnWriteArraySetDupTest1 & CopyOnWriteArraySetDupTest2 dưới đây để hiểu hơn về nhận xét trên.

CopyOnWriteArraySetDupTest1. java


package org.o7planning.set.test;

import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class CopyOnWriteArraySetDupTest1 {

    public static class Employee {
        private int empId;
        private String empName;
        
        public Employee(int empId, String empName) {
            this.empId = empId;
            this.empName = empName;
        }
        public int getEmpId() {
            return empId;
        }
        public String getEmpName() {
            return empName;
        }
        // Employee class uses equals(Object) method inherited from Object class.
    }
    
    public static void main(String[] args) {
        Employee s1 = new Employee(1, "Tom");    
        Employee s2 = new Employee(2, "Jerry");  
        Employee s3 = new Employee(1, "Tom Cat");  
        
        Set set = new CopyOnWriteArraySet();
        
        set.add(s1);
        set.add(s2);
        set.add(s3);  

        for(Employee s: set)  {
            System.out.printf("Emp Id: %d / Name: %s\n", //
                    s.getEmpId(),s.getEmpName());
        }
    }
}

Output :


Emp Id: 1 / Name: Tom
Emp Id: 2 / Name: Jerry
Emp Id: 1 / Name: Tom Cat

Sửa lại ví dụ trên bằng cách ghi đè (override) phương thức equals(Object) trong lớp Employee:

CopyOnWriteArraySetDupTest2. java


package org.o7planning.set.test;

import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class CopyOnWriteArraySetDupTest2 {

    public static class Employee {
        private int empId;
        private String empName;
        
        public Employee(int studentId, String studentName) {
            this.empId = studentId;
            this.empName = studentName;
        }
        public int getEmpId() {
            return empId;
        }
        public String getEmpName() {
            return empName;
        }
        @Override
        public boolean equals(Object other)  { // -----> Override equals() method.
            if(other == null || !(other instanceof Employee)) {
                return false;
            }
            Employee o = (Employee) other;
            return this.empId == o.empId;
        }
    }
    
    public static void main(String[] args) {
        Employee s1 = new Employee(1, "Tom");   
        Employee s2 = new Employee(2, "Jerry");  
        Employee s3 = new Employee(1, "Tom Cat");
        
        Set set = new CopyOnWriteArraySet();
        
        set.add(s1);
        set.add(s2);
        set.add(s3); // a Duplication.

        for(Employee s: set)  {
            System.out.printf("Emp Id: %d / Name: %s\n", //
                    s.getEmpId(),s.getEmpName());
        }
    }
}

Output :


Emp Id: 1 / Name: Tom
Emp Id: 2 / Name: Jerry

Phương thức tĩnh Set.of(..) trả về một đối tượng Set có kích thước cố định, ngoại lệ sẽ được ném ra nếu các tham số đầu vào trùng lặp. Đối tượng Set này không hỗ trợ các phương thức tuỳ chọn add, remove, setclear.


static  Set of()  
static  Set of​(E e1)  
static  Set of​(E... elements)  
static  Set of​(E e1, E e2)  
static  Set of​(E e1, E e2, E e3)  
static  Set of​(E e1, E e2, E e3, E e4)  
static  Set of​(E e1, E e2, E e3, E e4, E e5)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)  
static  Set of​(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)  

Set_of. java


package org.o7planning.set.ex;

import java.util.Iterator;
import java.util.Set;

public class Set_of {

    public static void main(String[] args) {
        Set set = Set.of(1, 2, 5, 3, 7, 9, 0);

        Iterator it2 = set.iterator();
        while (it2.hasNext()) {
            System.out.println(it2.next());
        }
    }
}

Chú ý rằng: Set là một Collection không có thứ tự (unordered Collection) nên bạn có thể nhận được một kết quả hơi khác khi in các phần tử ra màn hình Console.

Output :


2
3
5
7
9
0
1

Tạo một đối tượng Spliterator cho việc duyệt và phân vùng (traversing and partitioning) các phần tử của Set.


default Spliterator spliterator() 

Spliterator được sử dụng rộng rãi cho việc duyệt và phân vùng (traversing and partitioning) nhiều nguồn dữ liệu khác nhau như Collection (List, Set, Queue), BaseStream, array.

Phương thức toArray(..) trả về một mảng chứa tất cả các phần tử của Set.


Object[] toArray()  

 T[] toArray​(T[] a)

// Java 11, The default method, inherited from Collection.
default  T[] toArray​(IntFunction generator)

Set_toArray. java


package org.o7planning.set.ex;

import java.util.HashSet;
import java.util.Set;

public class Set_toArray {

    public static void main(String[] args) {  
        Set set = new HashSet();
        
        set.add("a1");
        set.add("b1");
        set.add("a2");  
        
        String[] array = new String[set.size()];
        
        set.toArray(array);
        
        for(String s: array) {
            System.out.println(s);
        }
    }  
}

Output :


a1
a2
b1