CodeGym /Các khóa học /JAVA 25 SELF /Map.copyOf, Set.copyOf và các tiện ích khác

Map.copyOf, Set.copyOf và các tiện ích khác

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Các phương thức copyOf: List.copyOf, Set.copyOf, Map.copyOf

Hãy hình dung tình huống: có một phương thức cần trả về danh sách, tập hợp hoặc bản đồ, nhưng điều quan trọng là mã gọi không thể thay đổi nội dung của collection này theo bất kỳ cách nào. Ví dụ, “danh sách các đơn vị tiền tệ được hỗ trợ” hoặc “tập quyền của người dùng”. Bất kỳ thao tác chèn/xóa ngẫu nhiên nào cũng có thể phá vỡ logic nghiệp vụ.

Trước Java 10, chúng ta hoặc phải tự trả về một bản sao của collection, hoặc dùng lớp bọc như Collections.unmodifiableList(list). Lớp bọc chỉ bảo vệ khỏi thay đổi thông qua chính nó, chứ không khỏi việc sửa đổi collection gốc.

Giờ đây chúng ta có cách đáng tin cậy hơn — các phương thức copyOf.

Chúng là gì?

Các phương thức List.copyOf(Collection), Set.copyOf(Collection), Map.copyOf(Map) (Java 10) tạo ra một bản sao bất biến thực sự của collection hoặc map được truyền vào.

  • Bản sao không liên quan tới collection gốc: thay đổi ở bản gốc không ảnh hưởng tới kết quả của copyOf.
  • Không thể sửa bản sao: mọi thao tác sửa đổi sẽ dẫn đến UnsupportedOperationException.

Ví dụ tạo bản sao bất biến

import java.util.*;

public class CopyOfDemo {
    public static void main(String[] args) {
        List<String> modifiable = new ArrayList<>();
        modifiable.add("Java");
        modifiable.add("Python");

        // Tạo bản sao bất biến
        List<String> immutable = List.copyOf(modifiable);

        // Thử thay đổi bản sao
        try {
            immutable.add("C++"); // Sẽ ném UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.out.println("Không thể thêm phần tử vào bộ sưu tập bất biến!");
        }

        // Sửa danh sách gốc
        modifiable.add("Kotlin");

        // Bản sao vẫn không thay đổi!
        System.out.println("Danh sách gốc: " + modifiable);
        System.out.println("Bản sao bất biến: " + immutable);
    }
}

Kết quả chạy chương trình:

Không thể thêm phần tử vào bộ sưu tập bất biến!
Danh sách gốc: [Java, Python, Kotlin]
Bản sao bất biến: [Java, Python]

Tóm tắt từng phương thức

  • List.copyOf(Collection) — trả về danh sách bất biến chứa các phần tử từ collection gốc.
  • Set.copyOf(Collection) — trả về tập hợp bất biến với các phần tử từ collection; các phần tử trùng lặp sẽ bị loại bỏ.
  • Map.copyOf(Map) — trả về map bất biến với các cặp từ map gốc.

Ví dụ với Set và Map

import java.util.*;

public class CopyOfSetMapDemo {
    public static void main(String[] args) {
        Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
        Set<String> immutableSet = Set.copyOf(modifiableSet);

        // Thử thêm phần tử
        try {
            immutableSet.add("D");
        } catch (UnsupportedOperationException e) {
            System.out.println("Không thể thay đổi Set!");
        }

        Map<String, Integer> modifiableMap = new HashMap<>();
        modifiableMap.put("Alice", 30);
        modifiableMap.put("Bob", 25);

        Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);

        try {
            immutableMap.put("Charlie", 28);
        } catch (UnsupportedOperationException e) {
            System.out.println("Không thể thay đổi Map!");
        }
    }
}

2. Đặc điểm và hạn chế của các phương thức copyOf

Không cho phép phần tử null

Giống như các phương thức factory List.of, Set.of, Map.of, các phương thức copyOf cấm null làm phần tử, khóa hoặc giá trị. Nếu trong collection/map gốc có null, khi sao chép sẽ ném NullPointerException.

List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // Sẽ ném NullPointerException!

Collection thực sự bất biến

Mọi nỗ lực add/remove/put/replace đều dẫn đến UnsupportedOperationException.

Bản gốc và bản sao không liên kết

Thay đổi ở collection gốc không ảnh hưởng tới bản sao, và ngược lại.

Nếu collection đã bất biến — chính nó sẽ được trả về

Truyền vào một collection vốn đã bất biến (ví dụ kết quả của List.of(...)) — copyOf sẽ trả về cùng một đối tượng (tiết kiệm bộ nhớ).

List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);

System.out.println(immutable == copy); // true

Không đảm bảo hiện thực cụ thể

Kiểu của collection trả về chỉ là List, Set hoặc Map. Không nhất thiết là ArrayList, HashSet hay HashMap. Đừng dựa vào chi tiết hiện thực (ví dụ kiểu nội bộ và tối ưu hóa); hợp đồng là tính bất biến và giao diện tương ứng.

3. Khác biệt giữa copyOf và wrapper (unmodifiable wrappers)

Collections.unmodifiableList(list) tạo một lớp bọc quanh collection hiện có. Nếu collection gốc thay đổi, những thay đổi này cũng hiển thị trong lớp bọc.

List.copyOf(list) tạo một collection bất biến mới, không liên quan tới bản gốc.

Minh họa sự khác nhau

import java.util.*;

public class WrapperVsCopyOf {
    public static void main(String[] args) {
        List<String> original = new ArrayList<>(List.of("A", "B"));

        // Lớp bọc
        List<String> wrapper = Collections.unmodifiableList(original);

        // Bản sao
        List<String> copy = List.copyOf(original);

        // Sửa bản gốc
        original.add("C");

        System.out.println("Lớp bọc: " + wrapper); // [A, B, C]
        System.out.println("Bản sao: " + copy);     // [A, B]
    }
}

Kết quả:

Lớp bọc: [A, B, C]
Bản sao: [A, B]

Lớp bọc phản ánh thay đổi của bản gốc, còn bản sao vẫn giữ nguyên.

4. Các kịch bản thực tế khi dùng copyOf

Bảo vệ dữ liệu khi trả về từ phương thức

public class CurrencyService {
    private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));

    public List<String> getSupportedCurrencies() {
        // Không ai có thể thay đổi kết quả
        return List.copyOf(currencies);
    }
}

Giờ đây mã gọi sẽ không thể thêm hoặc xóa loại tiền tệ khỏi danh sách được trả về.

Truyền dữ liệu giữa các tầng của ứng dụng

Khi dữ liệu được truyền giữa các tầng (DAO → service → controller), hãy dùng copyOf để đảm bảo rằng không ai thay đổi collection “trên đường đi”.

Công bố an toàn trong chương trình đa luồng

Các collection bất biến là cách tiện lợi để “chia sẻ” dữ liệu giữa các luồng mà không cần đồng bộ hóa bổ sung.

5. Những lỗi thường gặp khi sử dụng copyOf và các collection bất biến

Lỗi số 1: cố chèn null. Các phương thức copyOf (cũng như of) không chấp nhận null — không phải là phần tử của danh sách/tập hợp, cũng không phải là khóa/giá trị của map. Khi sao chép, bạn sẽ nhận NullPointerException.

Lỗi số 2: nhầm lẫn giữa lớp bọc và bản sao. copyOf tạo bản sao độc lập: thay đổi ở bản gốc không hiển thị trong bản sao. Còn Collections.unmodifiableList(list) chỉ bọc collection gốc: mọi thay đổi của bản gốc sẽ phản ánh trong lớp bọc.

Lỗi số 3: cố thay đổi collection bất biến. Gọi add/remove/put trên các collection từ copyOf/of sẽ dẫn đến UnsupportedOperationException.

Lỗi số 4: quên các giới hạn của factory Map. Ví dụ, Map.of() hỗ trợ tối đa 10 cặp key–value. Nếu cần nhiều hơn — hãy dùng Map.ofEntries(...) hoặc tạo một map có thể thay đổi rồi áp dụng Map.copyOf.

Lỗi số 5: dựa vào hiện thực cụ thể. Đừng kỳ vọng copyOf sẽ trả về chính xác ArrayList hay HashSet. Chỉ có giao diện (List/Set/Map) và tính bất biến là được đảm bảo.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION