1. Lớp ArrayList
Trong Java, giao diện List là một hợp đồng: “Tôi là một bộ sưu tập phần tử có thứ tự, và bạn có thể lấy phần tử theo số (chỉ mục)”.
Các đặc tính chính của danh sách (List):
- Các phần tử được lưu theo một thứ tự xác định (khác với Set).
- Cho phép lưu phần tử trùng lặp (ví dụ: hai lần "Vasya").
- Có thể truy cập phần tử theo chỉ mục: phần tử đầu tiên — chỉ mục 0, phần thứ hai — 1 và cứ thế.
- Có thể thêm, xóa phần tử ở bất kỳ vị trí nào trong danh sách.
Hiện thực phổ biến nhất của giao diện List là ArrayList. Bên dưới, nó dùng mảng thông thường và tự động mở rộng khi dữ liệu tăng.
Tạo ArrayList như thế nào?
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
// Tạo danh sách chuỗi
List<String> students = new ArrayList<>();
}
}
Giải thích
- List<String> — đây là một biến kiểu “danh sách các chuỗi”. Khai báo biến qua giao diện, còn tạo đối tượng bằng lớp cụ thể (ArrayList).
- Dấu ngoặc nhọn <String> xác định kiểu phần tử của danh sách (generics).
Các phương thức cơ bản của ArrayList (và nói chung của mọi List):
- add(element) — thêm phần tử vào cuối danh sách.
- add(index, element) — chèn phần tử tại chỉ mục cụ thể.
- get(index) — lấy phần tử theo chỉ mục.
- set(index, element) — thay thế phần tử theo chỉ mục.
- remove(index) — xóa phần tử theo chỉ mục.
- remove(Object) — xóa phần tử đầu tiên tìm thấy bằng với đối tượng truyền vào.
- size() — lấy số lượng phần tử trong danh sách.
Ví dụ: Làm việc với danh sách sinh viên
import java.util.ArrayList;
import java.util.List;
public class StudentListDemo {
public static void main(String[] args) {
List<String> students = new ArrayList<>();
// Thêm sinh viên
students.add("Vasya");
students.add("Petya");
students.add("Masha");
// In ra tất cả sinh viên
System.out.println("Danh sách sinh viên: " + students);
// Lấy sinh viên đầu tiên
String first = students.get(0);
System.out.println("Sinh viên đầu tiên: " + first);
// Đổi tên sinh viên thứ hai
students.set(1, "Pavel");
System.out.println("Sau khi thay đổi: " + students);
// Xóa Masha
students.remove("Masha");
System.out.println("Sau khi xóa Masha: " + students);
// Kích thước danh sách
System.out.println("Tổng số sinh viên: " + students.size());
}
}
Kết quả:
Danh sách sinh viên: [Vasya, Petya, Masha]
Sinh viên đầu tiên: Vasya
Sau khi thay đổi: [Vasya, Pavel, Masha]
Sau khi xóa Masha: [Vasya, Pavel]
Tổng số sinh viên: 2
Điều này liên quan gì tới ứng dụng của chúng ta?
Giả sử chúng ta có một ứng dụng quản lý nhiệm vụ của sinh viên. Bây giờ có thể lưu danh sách nhiệm vụ dưới dạng List<String> thay vì mảng, và thêm nhiệm vụ mới một cách linh hoạt.
2. Lớp LinkedList: khi tốc độ chèn và xóa quan trọng
LinkedList là một hiện thực thay thế của danh sách. Nó được tổ chức như danh sách liên kết kép: mỗi nút biết phần tử trước và sau. Giống như đoàn tàu: chèn “toa” vào giữa nhanh chóng mà không phải sắp xếp lại cả đoàn.
Tạo LinkedList
import java.util.LinkedList;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
}
}
Đặc điểm của LinkedList
- Chèn và xóa nhanh ở đầu và giữa danh sách.
- Truy cập theo chỉ mục chậm (để tìm phần tử thứ 100, phải đi qua chuỗi liên kết).
- Phù hợp khi bạn thường xuyên thêm/xóa phần tử không chỉ ở cuối mà còn ở đầu hoặc giữa.
Ví dụ: Sử dụng LinkedList
import java.util.LinkedList;
import java.util.List;
public class TaskListDemo {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
tasks.add("Thức dậy");
tasks.add("Ăn sáng");
tasks.add("Đi học");
// Thêm nhiệm vụ vào đầu danh sách
tasks.add(0, "Đặt báo thức");
System.out.println("Danh sách nhiệm vụ: " + tasks);
// Xóa nhiệm vụ đầu tiên (sớm nhất)
tasks.remove(0);
System.out.println("Sau khi xóa nhiệm vụ đầu tiên: " + tasks);
}
}
3. So sánh ArrayList và LinkedList
| Tiêu chí | |
|
|---|---|---|
| Nền tảng | Mảng | Danh sách liên kết kép |
| Truy cập theo chỉ mục nhanh | Có (O(1)) | Không (O(n)) |
| Chèn/xóa nhanh ở đầu/giữa | Không (O(n)) | Có (O(1) — nếu có tham chiếu) |
| Chèn/xóa nhanh ở cuối | Có (thường là O(1)) | Có (O(1)) |
| Bộ nhớ | Tốn ít bộ nhớ hơn | Nhiều hơn (các tham chiếu bổ sung tới hàng xóm) |
| Tác vụ điển hình | Truy cập theo chỉ mục thường xuyên | Chèn/xóa thường xuyên |
Quy tắc đơn giản:
- Cần truy cập nhanh theo chỉ số — hãy dùng ArrayList.
- Thường xuyên thêm/xóa ở đầu hoặc giữa — hãy dùng LinkedList.
4. Các thao tác thường gặp với danh sách
Duyệt các phần tử của danh sách
Vòng lặp for thông thường
for (int i = 0; i < students.size(); i++) {
System.out.println("Sinh viên #" + i + ": " + students.get(i));
}
Vòng lặp for-each (cách phổ biến nhất)
for (String name : students) {
System.out.println("Tên: " + name);
}
Biểu thức lambda (Java 8+)
students.forEach(name -> System.out.println("Tên: " + name));
Tìm kiếm phần tử
- contains(element) — trả về true nếu phần tử có trong danh sách.
- indexOf(element) — trả về chỉ mục lần xuất hiện đầu tiên của phần tử, hoặc -1 nếu không tìm thấy.
if (students.contains("Vasya")) {
System.out.println("Vasya có trong danh sách!");
}
int index = students.indexOf("Vasya");
System.out.println("Chỉ mục của Vasya: " + index);
Xóa sạch danh sách
clear() — xóa tất cả phần tử.
students.clear();
System.out.println("Danh sách sau khi xóa sạch: " + students);
5. Khi nào dùng ArrayList, và khi nào LinkedList?
Cứ nghĩ đơn giản: ArrayList phù hợp khi cần truy cập phần tử theo chỉ mục nhanh và danh sách ít thay đổi. Ví dụ, một danh sách người dùng dài hoặc lịch sử tin nhắn — chủ yếu là đọc.
Ngược lại, LinkedList hữu ích khi bạn liên tục chèn hoặc xóa phần tử ở đầu/giữa. Đây có thể là hàng đợi, ngăn xếp hoặc lịch sử hoàn tác.
Trong thực tế, thường dùng ArrayList nhiều hơn. Còn LinkedList là “dụng cụ để dành”: nằm trong ngăn kéo nhưng đôi khi lại chính là thứ bạn cần.
6. Các lỗi thường gặp khi làm việc với danh sách
Lỗi số 1: Vượt ra ngoài phạm vi danh sách. Tình huống phổ biến nhất — truy cập bằng chỉ mục không tồn tại. Nếu danh sách có ba phần tử mà bạn viết students.get(5), bạn sẽ nhận IndexOutOfBoundsException. Hãy kiểm tra size() trước khi truy cập.
Lỗi số 2: Xóa phần tử trong khi duyệt. Khi duyệt bằng for-each và đồng thời xóa phần tử, sẽ phát sinh ConcurrentModificationException. Với các thao tác xóa phức tạp, hãy dùng vòng lặp theo chỉ mục hoặc Iterator.
Lỗi số 3: So sánh đối tượng không đúng. Nếu bạn lưu các đối tượng riêng (ví dụ, Student), các phương thức contains và remove dựa vào equals. Nếu không ghi đè nó, so sánh sẽ theo tham chiếu chứ không phải theo nội dung.
Lỗi số 4: Sử dụng kiểu thô (raw types). Đừng viết List list = new ArrayList() — luôn chỉ ra kiểu phần tử: List<String> list = new ArrayList<>(). Generics giúp tránh lỗi và làm mã dễ hiểu hơn.
GO TO FULL VERSION