CodeGym /Các khóa học /JAVA 25 SELF /Lọc các phần tử của bộ sưu tập

Lọc các phần tử của bộ sưu tập

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

1. Giới thiệu

Trong lập trình, chúng ta thường xuyên gặp tình huống cần chọn ra những phần tử cần thiết từ một tập dữ liệu lớn — đó chính là lọc. Muốn giữ lại chỉ các số chẵn, tìm các chuỗi chứa từ "Java", hoặc chọn người dùng trên 18 tuổi — tất cả đều là các bài toán lọc.

Trong Java, các thao tác như vậy xuất hiện ở mọi nơi, vì thế cần nắm vững các cách thực hiện khác nhau và hiểu đặc điểm của chúng.

2. Lọc bằng vòng lặp

Bắt đầu từ cách cơ bản nhất — vòng lặp for thông thường. Cách tiếp cận này được gọi là mệnh lệnh vì bạn chỉ rõ phải làm gì và làm như thế nào. Nó dễ đọc và dễ hiểu.

Ví dụ: giữ lại chỉ các số chẵn

import java.util.*;

public class FilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> evenNumbers = new ArrayList<>(); // Tạo danh sách mới để chứa kết quả

        for (Integer n : numbers) {
            if (n % 2 == 0) { // Kiểm tra điều kiện: số chẵn
                evenNumbers.add(n);
            }
        }

        System.out.println("Các số chẵn: " + evenNumbers);
    }
}

Kết quả:

Các số chẵn: [2, 4, 6, 8, 10]

Cần lưu ý: bộ sưu tập ban đầu (numbers) không thay đổi. Chúng ta tạo một danh sách kết quả mới.

Ví dụ: lọc chuỗi theo chuỗi con

List<String> words = Arrays.asList("java", "python", "javascript", "kotlin", "c++");
List<String> javaWords = new ArrayList<>();

for (String word : words) {
    if (word.contains("java")) {
        javaWords.add(word);
    }
}
System.out.println(javaWords); // [java, javascript]

Nguyên tắc tương tự: duyệt qua danh sách chuỗi và kiểm tra sự tồn tại của chuỗi con bằng phương thức contains.

3. Xóa phần tử khỏi bộ sưu tập: vì sao không đơn giản?

Cái bẫy nằm ở đâu?

Đôi khi bạn muốn xóa khỏi bộ sưu tập ban đầu tất cả các phần tử không cần thiết. Nhưng nếu cố làm điều đó trong khi duyệt bằng vòng lặp for-each, bạn sẽ nhận ngoại lệ ConcurrentModificationException.

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));

for (Integer n : numbers) {
    if (n < 0) {
        numbers.remove(n); // NGUY HIỂM! ConcurrentModificationException!
    }
}

Kết quả:

Exception in thread "main" java.util.ConcurrentModificationException

Các bộ sưu tập “không thích” bị sửa đổi trong khi duyệt kiểu này — quá trình lặp sẽ bị hỏng.

Xóa phần tử đúng cách như thế nào?

Cách 1: dùng Iterator.remove()

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));

Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
    Integer n = it.next();
    if (n < 0) {
        it.remove(); // Xóa an toàn!
    }
}
System.out.println(numbers); // [1, 3, 5]

Ở đây chúng ta tạo iterator bằng phương thức iterator(), di chuyển qua bộ sưu tập bằng hasNext() và next(), và xóa phần tử không cần bằng it.remove().

Cách 2: tạo danh sách mới chỉ chứa các phần tử cần thiết

List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5);
List<Integer> positive = new ArrayList<>();

for (Integer n : numbers) {
    if (n >= 0) {
        positive.add(n);
    }
}
System.out.println(positive); // [1, 3, 5]

Chúng ta không đụng đến bộ sưu tập gốc và thu thập vào một danh sách mới. Cách này an toàn, dễ hiểu và dễ mở rộng khi điều kiện phức tạp hơn.

Cách 3: dùng removeIf (Java 8+)

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [1, 3, 5]

Chỉ với một dòng, truyền vào điều kiện: “xóa mọi thứ nhỏ hơn 0”. Bên trong, bộ sưu tập tự xử lý các chi tiết để việc sửa đổi được an toàn.

Tóm lại: để xóa phần tử, đừng dùng for-each. Hãy chọn giữa Iterator.remove(), tạo danh sách mới, hoặc removeIf ngắn gọn.

4. Khi nào dùng cách nào để lọc?

Cách mệnh lệnh qua vòng lặp thông thường hữu ích khi bạn không chỉ cần chọn phần tử mà còn muốn làm gì đó với chúng ngay (ví dụ in ra hoặc biến đổi). Nó đơn giản và minh bạch.

Nếu mục tiêu là xóa khỏi bộ sưu tập gốc, hãy tránh xóa trong for-each. Dùng Iterator.remove() để kiểm soát từng bước hoặc removeIf() — cách ngắn gọn và biểu đạt nhất.

Ví dụ: có một danh sách từ, cần xóa tất cả từ ngắn hơn bốn ký tự:

List<String> words = new ArrayList<>(Arrays.asList("Java", "is", "fun", "awesome", "code"));
words.removeIf(word -> word.length() < 4);
System.out.println(words); // [Java, awesome, code]

Phương thức removeIf nhận một predicate — quy tắc lọc — và xóa mọi thứ thỏa điều kiện.

5. Các lỗi thường gặp khi lọc bộ sưu tập

Lỗi số 1: cố gắng xóa phần tử khỏi bộ sưu tập trong vòng lặp for-each.
Đoạn mã này sẽ dẫn đến ConcurrentModificationException:

for (Integer n : numbers) {
    if (n < 0) {
        numbers.remove(n); // BÙM! ConcurrentModificationException
    }
}

Cách đúng: dùng Iterator.remove() hoặc removeIf.

Lỗi số 2: điều kiện lọc viết sai.
Cần xóa chỉ các số âm:

List<Integer> numbers = new ArrayList<>(Arrays.asList(-3, -1, 0, 2, 4));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // [0, 2, 4]

Nhưng nếu nhầm thành “không dương” và dùng <=, thì số 0 cũng biến mất:

numbers.removeIf(n -> n <= 0); // Kết quả: [2, 4] — số 0 bị xóa nhầm

Hãy chú ý độ chính xác của điều kiện: chỉ một phép so sánh sai cũng có thể thay đổi hoàn toàn kết quả.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Làm sạch hồ sơ đặc vụ khỏi thông tin "tiêu cực" 🕵️‍♀️
Làm sạch hồ sơ đặc vụ khỏi thông tin "tiêu cực" 🕵️‍♀️
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Sắp xếp tên động vật cho bách khoa toàn thư mới 🦒
Sắp xếp tên động vật cho bách khoa toàn thư mới 🦒
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION