CodeGym /Các khóa học /JAVA 25 SELF /List: ArrayList và LinkedList, các thao tác cơ bản

List: ArrayList và LinkedList, các thao tác cơ bản

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

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 ListArrayList. 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 ArrayListLinkedList

Tiêu chí
ArrayList
LinkedList
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 containsremove 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.

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