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.

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à:
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 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à:
Đả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à:
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:
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à:
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à:
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à:
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à:
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à:
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.