Class ArrayList<E>

Lớp ArrayList trong Java kế thừa từ lớp AbstractList và triển khai giao diện List. ArrayList là một lớp trong Java dùng để lưu trữ các phần tử trong một mảng động. Nó giống như một mảng truyền thống, nhưng không có giới hạn kích thước. Bạn có thể thêm hoặc xóa các phần tử bất kỳ lúc nào, do đó, nó linh hoạt hơn nhiều so với mảng truyền thống.

Mô tả các phần tử trong ArrayList - minh họa
Mô tả các phần tử trong ArrayList.

Cách hoạt động của Class ArrayList<E>

Thêm phần tử:

↳ Khi bạn thêm một phần tử vào ArrayList, nó sẽ kiểm tra xem mảng hiện tại có đủ dung lượng không.

↳ Nếu có đủ dung lượng, phần tử sẽ được thêm vào mảng tại chỉ số kế tiếp.

↳ Nếu không đủ dung lượng, ArrayList sẽ tạo ra một mảng mới với kích thước lớn hơn, sao chép các phần tử từ mảng cũ sang mảng mới, và sau đó thêm phần tử mới vào mảng mới.

Truy cập phần tử:

↳ Bạn có thể truy cập phần tử tại một chỉ số cụ thể bằng cách sử dụng phương thức get(int index).

↳ ArrayList cung cấp truy cập nhanh chóng (O(1)) đến các phần tử vì nó dựa trên mảng.

Xóa phần tử:

↳ Khi bạn xóa một phần tử tại một chỉ số cụ thể, ArrayList sẽ di chuyển các phần tử phía sau chỉ số đó về phía trước để lấp đầy khoảng trống.

↳ Việc di chuyển các phần tử có thể tốn thời gian (O(n)), vì tất cả các phần tử phía sau phải được dịch chuyển một vị trí về phía trước.

Cập nhật phần tử:

↳ Bạn có thể cập nhật phần tử tại một chỉ số cụ thể bằng cách sử dụng phương thức set(int index, E element).

↳ ArrayList sẽ thay thế phần tử cũ bằng phần tử mới tại chỉ số đã cho.

Tìm kích thước và kiểm tra tính rỗng:

↳ Phương thức size(): trả về số lượng phần tử hiện tại trong ArrayList.

↳ Phương thức isEmpty(): kiểm tra xem ArrayList có chứa phần tử nào không.

Một số đặc điểm nổi bật của ArrayList

↳ Cho phép phần tử trùng lặp: ArrayList cho phép bạn lưu nhiều phần tử giống nhau, bao gồm cả giá trị null. Ví dụ, bạn có thể có hai phần tử 5 trong danh sách hoặc một phần tử là null.

↳ Duy trì thứ tự chèn: Các phần tử trong ArrayList sẽ được giữ theo đúng thứ tự bạn đã chèn vào danh sách. Ví dụ, nếu bạn thêm số 1 rồi đến số 2, thì số 1 sẽ luôn ở trước số 2 trong danh sách.

↳ Không đồng bộ hóa: ArrayList không tự động bảo vệ dữ liệu khi nhiều luồng (threads) cùng truy cập vào danh sách. Nếu bạn có nhiều luồng làm việc với ArrayList mà không có biện pháp đồng bộ hóa, có thể dẫn đến lỗi. Để xử lý vấn đề này, bạn có thể sử dụng các lớp đồng bộ hóa khác hoặc thêm đồng bộ hóa bằng tay.

↳ Truy cập ngẫu nhiên: Bạn có thể truy cập bất kỳ phần tử nào trong ArrayList nhanh chóng bằng cách sử dụng chỉ số (index). Ví dụ, để lấy phần tử thứ ba trong danh sách, bạn chỉ cần gọi phương thức với chỉ số 2.

↳ Hiệu suất: Khi bạn thêm hoặc xóa phần tử từ ArrayList, có thể mất thời gian hơn so với một số cấu trúc dữ liệu khác như LinkedList vì ArrayList phải di chuyển các phần tử khác để giữ cho mảng liên tục. Điều này có thể làm cho các thao tác thêm hoặc xóa trở nên chậm hơn nếu bạn thường xuyên thực hiện những thao tác này.

↳ Không hỗ trợ kiểu nguyên thủy: Bạn không thể tạo ArrayList với các kiểu dữ liệu nguyên thủy như int, float, hoặc char. Thay vào đó, bạn phải sử dụng các lớp bao bọc như Integer, Float, hoặc Character. Ví dụ:

Ví dụ

ArrayList<int> al = new ArrayList<int>(); // không hợp lệ
ArrayList<Integer> al = new ArrayList<Integer>(); // hợp lệ

Khai báo Class ArrayList<E> trong Java

Để sử dụng Class ArrayList<E> bạn cần import gói java.util vào đầu file Java của mình. Gói này cung cấp các lớp và giao diện để làm việc với các collection trong Java.

Cú pháp câu lệnh import:

Cú Pháp

import java.util.ArrayList;

Cú pháp khai báo Interface List<E>:

Cú Pháp

public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

Dưới đây là giải thích chi tiết về cú pháp khai báo này:

↳ public: Lớp ArrayList có thể được truy cập từ bất kỳ đâu.

↳ class: Từ khóa để khai báo một lớp trong Java.

↳ ArrayList<E>: Tên lớp là ArrayList, dùng generic E đại diện cho kiểu phần tử mà danh sách chứa.

↳ extends AbstractList<E>: Kế thừa từ AbstractList<E> - ArrayList kế thừa từ lớp AbstractList, lớp này cung cấp một số phương thức cơ bản để làm việc với danh sách nhưng không cung cấp cài đặt đầy đủ cho tất cả các phương thức của giao diện List. ArrayList kế thừa và cụ thể hóa các phương thức này.

↳ implements List<E>: Triển khai giao diện List<E> - ArrayList cài đặt giao diện List, đảm bảo rằng tất cả các phương thức của giao diện List đều có sẵn trong ArrayList. Giao diện List định nghĩa các phương thức cho việc thêm, xóa và truy cập các phần tử theo chỉ số.

↳ implements RandomAccess: Triển khai giao diện RandomAccess - ArrayList triển khai giao diện RandomAccess, cho biết rằng danh sách hỗ trợ truy cập ngẫu nhiên (random access), nghĩa là bạn có thể truy cập bất kỳ phần tử nào trong danh sách ngay lập tức bằng cách sử dụng chỉ số.

↳ implements Cloneable: Triển khai giao diện Cloneable - ArrayList triển khai giao diện Cloneable, cho phép bạn sao chép đối tượng ArrayList bằng phương thức clone(). Tuy nhiên, bạn cần chú ý rằng sao chép này là nông (shallow copy), nghĩa là các phần tử trong danh sách không được sao chép mà chỉ tham chiếu đến các đối tượng gốc.

↳ implements Serializable: Triển khai giao diện Serializable: ArrayList triển khai giao diện Serializable, cho phép đối tượng ArrayList được tuần tự hóa (serialized) và ghi ra luồng dữ liệu hoặc lưu trữ. Điều này hữu ích khi bạn muốn lưu trữ hoặc truyền tải đối tượng ArrayList qua mạng.

Các constructor của lớp ArrayList

Để bắt đầu sử dụng một ArrayList, bạn cần "tạo" nó. Có ba cách chính để bạn có thể tạo một đối tượng ArrayList trong Java, mỗi cách sẽ giúp bạn thiết lập danh sách theo một kiểu khác nhau ngay từ đầu.

↳ ArrayList(): Tạo một danh sách rỗng với dung lượng ban đầu là 10.

↳ ArrayList(Collection<? extends E> c): Tạo một danh sách chứa các phần tử từ một bộ sưu tập khác.

↳ ArrayList(int initialCapacity): Tạo một danh sách rỗng với dung lượng ban đầu được chỉ định.

Ví dụ

// Tạo một danh sách rỗng để lưu trữ các chuỗi
        list1 = new ArrayList<>();

        // Tạo một danh sách chứa các số nguyên từ 1 đến 5
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        list2 = new ArrayList<>(numbers);

        // Tạo một danh sách rỗng với dung lượng ban đầu là 100 để lưu trữ các đối tượng Person
        list3 = new ArrayList<>(100);

Lưu ý:

↳ Dung lượng ban đầu của ArrayList là kích thước của mảng được sử dụng để lưu trữ các phần tử. Nếu bạn dự đoán sẽ thêm nhiều phần tử, bạn có thể chỉ định dung lượng ban đầu lớn hơn để tránh việc phải tăng kích thước mảng nhiều lần.

↳ Khi tạo một ArrayList từ một bộ sưu tập khác, các phần tử sẽ được sao chép vào ArrayList mới. Thay đổi bộ sưu tập gốc sẽ không ảnh hưởng đến ArrayList đã được tạo.

So sánh Collection Non-generic và Generic trong Java

Trước JDK 1.5, Java không hỗ trợ các collection kiểu generic. Từ JDK 1.5 trở đi, Java đã giới thiệu generic collection để cải thiện sự an toàn kiểu và giảm thiểu lỗi khi thực thi.

1. Collection Non-generic

Trước khi có generics, các collection trong Java không có sự kiểm soát kiểu dữ liệu. Điều này dẫn đến việc phải sử dụng kiểu đối tượng chung như Object, và việc kiểm tra kiểu phải thực hiện ở thời điểm chạy (runtime).

Ví dụ

ArrayList list = new ArrayList(); // Tạo ArrayList không kiểu generic
list.add("Hello");
list.add(123); // Có thể thêm bất kỳ loại đối tượng nào
String str = (String) list.get(0); // Phải ép kiểu khi lấy dữ liệu

↳ Không kiểm soát kiểu: Bạn có thể thêm bất kỳ loại đối tượng nào vào danh sách.

↳ Cần ép kiểu: Khi lấy phần tử ra, bạn cần ép kiểu, có thể gây lỗi nếu không chính xác.

↳ Lỗi tại runtime: Các lỗi liên quan đến kiểu dữ liệu chỉ được phát hiện khi chương trình chạy.

2. Collection Generic

Từ JDK 1.5 trở đi, Java đã hỗ trợ generics cho các collection. Generics cho phép bạn chỉ định kiểu dữ liệu cho collection, giúp kiểm soát kiểu dữ liệu tại thời điểm biên dịch (compile-time).

Ví dụ

ArrayList<String> list = new ArrayList<String>(); // Tạo ArrayList với kiểu generic
list.add("Hello");
// list.add(123); // Lỗi biên dịch, không thể thêm số vào danh sách kiểu String
String str = list.get(0); // Không cần ép kiểu

↳ Kiểm soát kiểu: Bạn chỉ định kiểu dữ liệu cho collection, ví dụ ArrayList<String> chỉ cho phép thêm đối tượng kiểu String.

↳ An toàn kiểu: Không cần ép kiểu khi lấy dữ liệu từ collection.

↳ Lỗi tại biên dịch: Các lỗi liên quan đến kiểu dữ liệu được phát hiện khi biên dịch, giúp tránh lỗi tại runtime.

Các phương thức chính trong ArrayList

Lớp ArrayList cung cấp các phương thức linh hoạt để thao tác với danh sách, bao gồm thêm, xóa, tìm kiếm, duyệt qua các phần tử theo thứ tự, và thay đổi kích thước danh sách. Dưới đây là danh sách tất cả các phương thức của ArrayList<E> trong Java:

↳ boolean add(E e): Thêm phần tử e vào cuối danh sách.

↳ void add(int index, E element): Chèn phần tử element vào vị trí index trong danh sách.

↳ boolean addAll(Collection<? extends E> c): Thêm tất cả các phần tử trong collection c vào cuối danh sách.

↳ boolean addAll(int index, Collection<? extends E> c): Chèn tất cả các phần tử trong collection c vào vị trí index trong danh sách.

↳ void clear(): Xóa tất cả các phần tử khỏi danh sách.

↳ Object clone(): Trả về một bản sao nông của instance ArrayList này.

↳ boolean contains(Object o): Trả về true nếu danh sách chứa phần tử o.

↳ void ensureCapacity(int minCapacity): Tăng dung lượng của instance ArrayList này, nếu cần thiết, để đảm bảo rằng nó có thể chứa ít nhất số lượng phần tử được chỉ định bởi đối số dung lượng tối thiểu.

↳ void forEach(Consumer<? super E> action): Thực hiện hành động đã cho cho mỗi phần tử của Iterable cho đến khi tất cả các phần tử đã được xử lý hoặc hành động ném ra một ngoại lệ.

↳ get(int index): Lấy phần tử tại vị trí index trong danh sách.

↳ int indexOf(Object o): Trả về chỉ số lần xuất hiện đầu tiên của phần tử o trong danh sách, hoặc -1 nếu danh sách không chứa phần tử o.

↳ boolean isEmpty(): Trả về true nếu danh sách không chứa phần tử nào.

↳ Iterator<E>iterator(): Trả về một iterator duyệt qua các phần tử trong danh sách theo thứ tự đúng.

↳ int lastIndexOf(Object o): Trả về chỉ số lần xuất hiện cuối cùng của phần tử o trong danh sách, hoặc -1 nếu danh sách không chứa phần tử o.

↳ ListIterator<E> listIterator(): Trả về một list iterator duyệt qua các phần tử trong danh sách theo thứ tự đúng.

↳ ListIterator<E> listIterator(int index): Trả về một list iterator duyệt qua các phần tử trong danh sách theo thứ tự đúng, bắt đầu từ vị trí index đã cho.

↳ remove(int index): Xóa phần tử tại vị trí index trong danh sách.

↳ boolean remove(Object o): Xóa phần tử đầu tiên xuất hiện của phần tử o trong danh sách.

↳ boolean removeAll(Collection<?> c): Xóa tất cả các phần tử của danh sách này cũng có trong collection c.

↳ boolean removeIf(Predicate<? super E> filter): Xóa tất cả các phần tử thỏa mãn điều kiện filter.

↳ protected void removeRange(int fromIndex, int toIndex): Xóa tất cả các phần tử có chỉ số nằm trong khoảng từ fromIndex (bao gồm) đến toIndex (không bao gồm).

↳ void replaceAll(UnaryOperator<E> operator): Thay thế mỗi phần tử của danh sách bằng kết quả của việc áp dụng toán tử lên phần tử đó.

↳ boolean retainAll(Collection<?> c): Giữ lại chỉ những phần tử trong danh sách này cũng có trong collection c.s

↳ set(int index, E element): Thay thế phần tử tại vị trí index trong danh sách bằng phần tử element.

↳ int size(): Trả về số lượng phần tử trong danh sách.

↳ void sort(Comparator<? super E> c): Sắp xếp danh sách theo thứ tự được quy định bởi bộ so sánh đã cho.

↳ Spliterator<E> spliterator(): Tạo một Spliterator duyệt qua các phần tử trong danh sách.

↳ List<E> subList(int fromIndex, int toIndex): Trả về một view của phần của danh sách này nằm giữa fromIndex (bao gồm) và toIndex (không bao gồm).

↳ Object[] toArray(): Trả về một mảng chứa tất cả các phần tử trong danh sách này theo thứ tự đúng (từ phần tử đầu tiên đến phần tử cuối cùng).

↳ <T> T[] toArray(T[] a): Trả về một mảng chứa tất cả các phần tử trong danh sách này theo thứ tự đúng (từ phần tử đầu tiên đến phần tử cuối cùng); kiểu thời gian chạy của mảng được trả về là kiểu của mảng đã cho.

↳ void trimToSize(): Giảm dung lượng của danh sách xuống bằng với kích thước hiện tại

Ví dụ về cách sửa đổi, xóa và truy cập trong ArrayList

Dưới đây là một ví dụ về cách sửa đổi, xóa và truy cập các phần tử trong ArrayList một cách đơn giản:

Ví dụ: Example.java

import java.util.ArrayList;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList và thêm phần tử vào danh sách
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Mango");
        list.add("Orange");

        // Truy cập phần tử bằng chỉ số
        System.out.println("Phần tử tại vị trí 2: " + list.get(2)); // Output: Mango

        // Sửa đổi phần tử tại vị trí cụ thể
        list.set(1, "Grapes");
        System.out.println("Danh sách sau khi thay thế phần tử tại vị trí 1:" + list);

        // Xóa phần tử bằng chỉ số
        list.remove(3); // Xóa phần tử tại vị trí 3 (Orange)
        System.out.println("Danh sách sau khi xóa phần tử tại vị trí 3:" + list);

        // Xóa phần tử bằng đối tượng
        list.remove("Mango"); // Xóa phần tử Mango
        System.out.println("Danh sách sau khi xóa phần tử Mango:" + list);

        // Thêm phần tử tại vị trí cụ thể
        list.add(1, "Strawberry");
        System.out.println("Danh sách sau khi thêm Strawberry tại vị trí 1:"+ list);
    }
}

Kết quả của chương trình là:

Phần tử tại vị trí 2: Mango
Danh sách sau khi thay thế phần tử tại vị trí 1:[Apple, Grapes, Mango, Orange]
Danh sách sau khi xóa phần tử tại vị trí 3:[Apple, Grapes, Mango]
Danh sách sau khi xóa phần tử Mango:[Apple, Grapes]
Danh sách sau khi thêm Strawberry tại vị trí 1:[Apple, Strawberry, Grapes]

Ví dụ về cách sử dụng các phương thức contains, containsAll, indexOf, lastIndexOf, isEmpty và size

Dưới đây là một ví dụ về cách sử dụng các phương thức contains, containsAll, indexOf, lastIndexOf và isEmpty trong ArrayList:

Ví dụ: Example.java

import java.util.ArrayList;
import java.util.Arrays;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList và thêm phần tử vào danh sách
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Mango");
        list.add("Banana");

        // Kiểm tra xem danh sách có chứa một phần tử cụ thể hay không
        System.out.println("Danh sách có chứa 'Banana' không? " + list.contains("Banana")); // Output: true

        // Kiểm tra xem danh sách có chứa tất cả các phần tử trong một Collection hay không
        ArrayList<String> otherList = new ArrayList<>(Arrays.asList("Banana", "Mango"));
        System.out.println("Danh sách có chứa tất cả phần tử của otherList không? " + list.containsAll(otherList)); // Output: true

        // Tìm chỉ số của lần xuất hiện đầu tiên của một phần tử
        System.out.println("Chỉ số lần đầu tiên xuất hiện của 'Banana': " + list.indexOf("Banana")); // Output: 1

        // Tìm chỉ số của lần xuất hiện cuối cùng của một phần tử
        System.out.println("Chỉ số lần cuối cùng xuất hiện của 'Banana': " + list.lastIndexOf("Banana")); // Output: 3

        // Kiểm tra xem danh sách có rỗng hay không
        System.out.println("Danh sách có rỗng không? " + list.isEmpty()); // Output: false

    }
}

Kết quả của chương trình là:

Danh sách có chứa 'Banana' không? true
Danh sách có chứa tất cả phần tử của otherList không? true
Chỉ số lần đầu tiên xuất hiện của 'Banana': 1
Chỉ số lần cuối cùng xuất hiện của 'Banana': 3
Danh sách có rỗng không? false

Ví dụ về kích thước và dung lượng của ArrayList

↳ Trong Java, kích thước (size) và dung lượng (capacity) thường được nhắc đến trong ngữ cảnh của các lớp collection như ArrayList và StringBuilder. Dưới đây là cách hiểu và sử dụng kích thước và dung lượng cho cả hai loại collection này:

↳ Kích thước (Size): Kích thước của ArrayList là số lượng phần tử hiện có trong danh sách. Ví dụ, nếu không có phần tử nào được thêm vào, kích thước của danh sách sẽ là 0, mặc dù dung lượng có thể khác.

↳ Dung lượng (Capacity): Dung lượng của ArrayList là số lượng phần tử mà danh sách có thể chứa mà không cần mở rộng. Dung lượng khởi tạo của ArrayList mặc định là 10. Điều này có nghĩa là ArrayList có thể chứa 10 phần tử trước khi cần mở rộng. Khi số lượng phần tử bằng dung lượng, ArrayList sẽ tự động mở rộng dung lượng để thêm các phần tử mới.

Ví dụ: Example.java

import java.util.ArrayList;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList
        ArrayList<String> list = new ArrayList<>();

        // Thêm các phần tử vào ArrayList
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // Lấy kích thước hiện tại
        int size = list.size();
        System.out.println("Kích thước: " + size); // Output: Size: 3

        // Dung lượng của ArrayList không thể lấy trực tiếp,
        // nhưng bạn có thể thấy sự thay đổi khi thêm phần tử
        // hoặc sử dụng phương thức ensureCapacity để đảm bảo dung lượng
        list.ensureCapacity(10);
        System.out.println("Đảm bảo sức chứa ít nhất là 10");
    }
}

Kết quả của chương trình là:

Kích thước: 3
Đảm bảo sức chứa ít nhất là 10

Lưu ý:

↳ Khi bạn thêm phần tử vào ArrayList và kích thước đạt đến dung lượng hiện tại, dung lượng sẽ tăng lên.

↳ Dung lượng luôn lớn hơn hoặc bằng kích thước.

↳ ArrayList không cung cấp phương thức để lấy dung lượng hiện tại.

Ví dụ ArrayList với đối tượng do người dùng định nghĩa

Khi làm việc với một ArrayList chứa các đối tượng do người dùng định nghĩa, bạn có thể sử dụng các phương thức get và set giống như với các kiểu dữ liệu nguyên thủy. Dưới đây là một ví dụ minh họa:

Giả sử bạn có một lớp Student do người dùng định nghĩa:

Ví dụ: Example.java

import java.util.ArrayList;

class Student {
    private String name;
    private int age;

    // Constructor
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter và Setter cho name
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // Getter và Setter cho age
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    } 
}
class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList chứa các đối tượng Student
        ArrayList<Student> students = new ArrayList<>();

        // Thêm các đối tượng Student vào danh sách
        students.add(new Student("Vuong", 20));
        students.add(new Student("Truong", 22));
        students.add(new Student("Duong", 19));

        // Truy cập đối tượng tại vị trí chỉ định bằng phương thức get
        Student studentAtIndex1 = students.get(1); // Lấy đối tượng Student tại chỉ số 1
        System.out.println("Sinh viên tại vị trí 1: " + studentAtIndex1);

        // Thay đổi thông tin đối tượng tại vị trí chỉ định bằng phương thức set
        students.set(1, new Student("Truong", 23)); // Cập nhật thông tin của sinh viên Alice
        System.out.println("Danh sách sinh viên sau khi thay đổi: " + students);

        // Truy cập và thay đổi thuộc tính của đối tượng thông qua getter và setter
        Student studentAtIndex2 = students.get(2); // Lấy đối tượng Student tại chỉ số 2
        studentAtIndex2.setName("Trung"); // Thay đổi tên của sinh viên tại chỉ số 2 thành "Charlie"
        System.out.println("Danh sách sinh viên sau khi đổi tên: " + students);
    }
}

Kết quả của chương trình là:

Kích thước: 3
Sinh viên tại vị trí 1: Student{name='Truong', age=22}
Danh sách sinh viên sau khi thay đổi: [Student{name='Vuong', age=20}, Student{name='Truong', age=23}, Student{name='Duong', age=19}]
Danh sách sinh viên sau khi đổi tên: [Student{name='Vuong', age=20}, Student{name='Truong', age=23}, Student{name='Trung', age=19}]

Ví dụ về tuần tự hóa ArrayList

Tuần tự hóa (serialization) là quá trình chuyển đổi trạng thái của một đối tượng thành một chuỗi byte để có thể lưu trữ hoặc truyền tải qua mạng.

Dưới đây là ví dụ về cách tuần tự hóa một đối tượng ArrayList trong Java:

Ví dụ: Example.java

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList chứa các chuỗi
        ArrayList<String> cities = new ArrayList<>();
        cities.add("Hanoi");
        cities.add("Ho Chi Minh City");
        cities.add("Da Nang");

        try {
            // Tạo luồng đầu ra tệp để ghi đối tượng
            FileOutputStream fileOut = new FileOutputStream("cities.ser");
            // Tạo ObjectOutputStream để tuần tự hóa đối tượng
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            // Ghi đối tượng ArrayList vào tệp
            out.writeObject(cities);
            out.close();
            fileOut.close();
            System.out.println("Đối tượng ArrayList đã được tuần tự hóa và lưu vào cities.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Trong ví dụ này, chúng ta sử dụng ObjectOutputStream để tuần tự hóa đối tượng ArrayList và lưu nó vào tệp cities.ser. Dữ liệu được tuần tự hóa từ cities.ser:

���sr�java.util.ArrayListx����a� �I� sizexp��� w ��� t�Hanoit�Ho Chi Minh Cityt�Da Nangx

Ví dụ về hủy tuần tự hóa ArrayList

Hủy tuần tự hóa (deserialization) là quá trình ngược lại, tức là chuyển đổi chuỗi byte đó trở lại thành đối tượng ban đầu.

Dưới đây là ví dụ về cách hủy tuần tự hóa một đối tượng ArrayList trong Java:

Ví dụ: Example.java

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class Example {
    public static void main(String[] args) {
        ArrayList<String> cities = null;

        try {
            // Tạo luồng đầu vào tệp để đọc đối tượng
            FileInputStream fileIn = new FileInputStream("cities.ser");
            // Tạo ObjectInputStream để hủy tuần tự hóa đối tượng
            ObjectInputStream in = new ObjectInputStream(fileIn);
            // Đọc đối tượng ArrayList từ tệp
            cities = (ArrayList<String>) in.readObject();
            in.close();
            fileIn.close();
        } catch (IOException i) {
            i.printStackTrace();
        } catch (ClassNotFoundException c) {
            System.out.println("Lớp ArrayList không tìm thấy");
            c.printStackTrace();
        }

        // Hiển thị dữ liệu đã được hủy tuần tự hóa
        System.out.println("Dữ liệu hủy tuần tự hóa từ cities.ser:");
        for (String city : cities) {
            System.out.println(city);
        }
    }
}

Trong ví dụ này, chúng ta sử dụng ObjectInputStream để đọc đối tượng ArrayList đã tuần tự hóa từ tệp cities.ser. Sau khi hủy tuần tự hóa, chúng ta in các thành phố trong danh sách để kiểm tra xem dữ liệu có được khôi phục đúng cách hay không. Kết quả của chương trình là:

Dữ liệu hủy tuần tự hóa từ cities.ser:
Hanoi
Ho Chi Minh City
Da Nang

Các cách duyệt các phần tử của Collection trong Java

Có nhiều cách để duyệt các phần tử của collection trong Java:

↳ Sử dụng Interface Iterator: Iterator cho phép bạn duyệt qua các phần tử một cách tuần tự.

↳ Sử dụng vòng lặp for-each: Vòng lặp for-each giúp duyệt qua các phần tử của collection một cách đơn giản và dễ đọc.

↳ Sử dụng Interface ListIterator: ListIterator là một phần mở rộng của Iterator, cung cấp khả năng duyệt qua collection từ cả hai hướng (tới và lui).

↳ Sử dụng vòng lặp for: Vòng lặp for truyền thống cho phép bạn duyệt qua các phần tử bằng cách sử dụng chỉ số (index).

↳ Sử dụng phương thức forEach(): Phương thức forEach() cung cấp một cách tiếp cận hàm (functional) để duyệt qua các phần tử, thường kết hợp với các biểu thức lambda.

↳ Sử dụng phương thức forEachRemaining(): Phương thức forEachRemaining() của Iterator cho phép bạn áp dụng một hành động cho tất cả các phần tử còn lại trong iterator.

Ví dụ cách sử dụng Iterator với ArrayList

Ví dụ: Example.java

import java.util.ArrayList;
import java.util.Iterator;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList và thêm các phần tử vào
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // Lấy Iterator từ ArrayList
        Iterator<String> iterator = list.iterator();

        // Duyệt qua các phần tử của ArrayList
        while (iterator.hasNext()) {
            // Lấy phần tử tiếp theo
            String fruit = iterator.next();
            System.out.println(fruit);
        }
    }
}

Kết quả của chương trình là:

Apple
Banana
Cherry

Trong ví dụ trên, iterator() trả về một đối tượng Iterator cho ArrayList. Sau đó, bạn có thể sử dụng hasNext() để kiểm tra có còn phần tử nào nữa không, và next() để lấy phần tử tiếp theo trong danh sách.

Ví dụ cách sử dụng vòng lặp for-each với ArrayList

Ví dụ: Example.java

import java.util.ArrayList;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList và thêm các phần tử vào
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // Sử dụng vòng lặp for-each để duyệt qua các phần tử của ArrayList
        for (String fruit : list) {
            System.out.println(fruit);
        }
    }
}

Kết quả của chương trình là:

Apple
Banana
Cherry

Vòng lặp for-each giúp mã nguồn ngắn gọn và dễ đọc hơn, đặc biệt khi bạn chỉ cần duyệt qua các phần tử mà không cần thay đổi danh sách hoặc biết chỉ số của các phần tử.

Ví dụ về cách sử dụng ListIterator với ArrayList

Ví dụ: Example.java

import java.util.ArrayList;
import java.util.ListIterator;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList và thêm các phần tử vào
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // Lấy ListIterator từ ArrayList
        ListIterator<String> listIterator = list.listIterator();

        // Duyệt qua các phần tử theo chiều tiến
        System.out.println("Từ đầu đến cuối:");
        while (listIterator.hasNext()) {
            String fruit = listIterator.next();
            System.out.println(fruit);
        }

        // Duyệt qua các phần tử theo chiều lùi
        System.out.println("Ngược lại:");
        while (listIterator.hasPrevious()) {
            String fruit = listIterator.previous();
            System.out.println(fruit);
        }
    }
}

Kết quả của chương trình là:

Từ đầu đến cuối:
Apple
Banana
Cherry
Ngược lại:
Cherry
Banana
Apple

Dưới đây là ví dụ về cách sử dụng forEachRemaining() với Iterator

Ví dụ: Example.java

import java.util.ArrayList;
import java.util.Iterator;

public class Example {
    public static void main(String[] args) {
        // Tạo một ArrayList và thêm các phần tử vào
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // Lấy Iterator từ ArrayList
        Iterator<String> iterator = list.iterator();

        // Sử dụng forEachRemaining để in tất cả các phần tử còn lại
        iterator.forEachRemaining(fruit -> System.out.println(fruit));
    }
}

Kết quả của chương trình là:

Apple
Banana
Cherry

Như vậy, chúng ta đã đi qua các phương thức chính của ArrayList. Với khả năng thêm, xóa, tìm kiếm, và truy cập phần tử linh hoạt cùng với việc tự động thay đổi kích thước, ArrayList là một trong những lựa chọn phổ biến và mạnh mẽ nhất khi bạn cần làm việc với các danh sách có thứ tự trong Java. Nắm vững ArrayList là điều cần thiết để bạn có thể quản lý dữ liệu hiệu quả trong hầu hết các ứng dụng.

Câu Nói Truyền Cảm Hứng

“Bắt đầu ở đâu không quan trọng, quan trọng là bạn sẵn sàng bắt đầu.” – W. Clement Stone

Không Gian Tích Cực

“Chúc bạn luôn giữ vững niềm tin và sức mạnh để vượt qua mọi thử thách trong cuộc sống.”