1. Giới thiệu
Tầm quan trọng của List trong Java là gì?
Trong lập trình Java, “List” là một cấu trúc dữ liệu xuất hiện rất thường xuyên. Đặc biệt, trong các trường hợp muốn quản lý nhiều giá trị cùng lúc, List được ưa chuộng tại nhiều môi trường làm việc vì tính linh hoạt và dễ sử dụng hơn mảng.
“List” là một interface cốt lõi trong Java Collections Framework. Thông qua các lớp triển khai khác nhau như ArrayList và LinkedList, List có thể đáp ứng được nhiều tình huống đa dạng. Khả năng thực hiện các thao tác như thêm, xóa, tìm kiếm, cập nhật dữ liệu một cách trực quan cũng là một trong những lý do khiến List được ưa chuộng.
Mục đích và đối tượng độc giả của bài viết này
Trong bài viết này, chúng tôi sẽ **giải thích một cách có hệ thống từ cơ bản đến nâng cao về “Java List”, dễ hiểu cho cả người mới bắt đầu**. Đối tượng chính của bài viết này bao gồm:
- Người mới bắt đầu học Java và còn băn khoăn về cách sử dụng List
- Người muốn hiểu rõ sự khác biệt giữa mảng (Array) và List
- Người đang phân vân về việc lựa chọn giữa ArrayList và LinkedList
- Người muốn ôn lại kiến thức cơ bản về List để sử dụng trong thực tế
Khi đọc xong bài viết này, chúng tôi hy vọng bạn sẽ nắm vững cách tư duy cơ bản, phương pháp triển khai và các thao tác cụ thể với List trong Java, từ đó tự tin hơn khi code.
Từ chương tiếp theo, chúng tôi sẽ bắt đầu giải thích tuần tự từ phần cơ bản nhất là “List là gì?”.
2. List là gì?
Tổng quan và đặc điểm của List
“List” trong Java là một interface của **bộ sưu tập (collection) giữ các phần tử có thứ tự**. Đặc điểm lớn nhất là **thứ tự thêm phần tử được giữ nguyên** và **có thể truy cập từng phần tử bằng chỉ mục (index, bắt đầu từ 0)**.
List được cung cấp như một phần của Collections Framework và có các chức năng sau:
- Cho phép các phần tử trùng lặp
- Có thể lấy, cập nhật, xóa phần tử theo chỉ mục
- Có thể tăng giảm số lượng phần tử một cách động (không có độ dài cố định như mảng)
Nhờ đó, **các thao tác dữ liệu trở nên linh hoạt hơn** và List được sử dụng rất phổ biến trong thực tế.
Khác biệt với mảng (Array)
Trong Java, mảng (ví dụ: int[]
, String[]
) cũng là một phương tiện để lưu trữ nhiều giá trị, nhưng có một số khác biệt so với List.
Mục so sánh | Mảng (Array) | List |
---|---|---|
Thay đổi số lượng phần tử | Không thể (độ dài cố định) | Có thể (tăng giảm động) |
Chức năng cung cấp | Thao tác tối thiểu (truy cập theo chỉ số, lấy độ dài) | Nhiều phương thức phong phú (add, remove, contains, v.v.) |
Kiểu dữ liệu | Có thể xử lý kiểu nguyên thủy | Chỉ xử lý kiểu đối tượng (cần dùng Wrapper Class) |
An toàn kiểu dữ liệu | Mảng được kiểm tra kiểu khi biên dịch | Có thể chỉ định kiểu nghiêm ngặt bằng Generics |
Như vậy, **List là một bộ sưu tập linh hoạt và có nhiều chức năng hơn**, nên thường hữu ích hơn mảng trong nhiều tình huống thực tế.
Interface List và các lớp triển khai của nó
Khi sử dụng List trong Java, về cơ bản bạn khai báo biến bằng interface List
và tạo instance bằng một lớp cụ thể (lớp triển khai). Các lớp triển khai tiêu biểu bao gồm:
- ArrayList
Cấu trúc tương tự mảng, truy cập nhanh. Mạnh về tìm kiếm và truy cập ngẫu nhiên dữ liệu. - LinkedList
Mỗi phần tử liên kết với phần tử trước và sau. Thêm/xóa nhanh, phù hợp với danh sách có thao tác này thường xuyên. - Vector
Tương tự ArrayList nhưng an toàn cho thread (thread-safe) nên hơi chậm hơn. Hiện tại ít được sử dụng.
Nói chung, nếu không có lý do đặc biệt, **ArrayList** là lớp được sử dụng phổ biến nhất. Tùy theo mục đích sử dụng, bạn có thể tham khảo phần so sánh hiệu năng dưới đây để lựa chọn phù hợp.
3. Cách sử dụng List cơ bản
Chương này sẽ giải thích tuần tự các thao tác cơ bản khi sử dụng List trong Java. Chúng tôi sẽ lấy **ArrayList** làm ví dụ chính để giới thiệu các thao tác điển hình của List.
Khai báo và khởi tạo List
Đầu tiên là cách khai báo và khởi tạo List cơ bản sử dụng ArrayList
.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
}
}
Phổ biến nhất là khai báo biến bằng interface List
và khởi tạo instance bằng ArrayList
. Sử dụng Generics để chỉ định kiểu dữ liệu sẽ lưu trữ (ở đây là String
).
Thêm phần tử (add)
Để thêm phần tử vào List, sử dụng phương thức add()
.
fruits.add("りんご");
fruits.add("バナナ");
fruits.add("みかん");
Với đoạn code này, 3 phần tử sẽ được thêm vào List theo thứ tự. List **giữ nguyên thứ tự thêm vào**.
Lấy phần tử (get)
Để lấy phần tử tại chỉ mục được chỉ định, sử dụng get(int index)
.
System.out.println(fruits.get(0)); // Hiển thị "りんご"
Lưu ý rằng **chỉ mục bắt đầu từ 0**.
Cập nhật phần tử (set)
Nếu muốn cập nhật phần tử tại một vị trí nào đó, sử dụng set(int index, E element)
.
fruits.set(1, "ぶどう"); // Phần tử thứ 2 "バナナ" được thay thế bằng "ぶどう"
Xóa phần tử (remove)
Cũng có thể xóa phần tử bằng cách chỉ định chỉ mục hoặc chính phần tử đó.
fruits.remove(0); // Xóa phần tử đầu tiên
fruits.remove("みかん"); // Xóa "みかん" (chỉ xóa phần tử đầu tiên khớp)
Lấy kích thước của List (size)
Số lượng phần tử hiện tại có thể lấy bằng phương thức size()
.
System.out.println(fruits.size()); // Trả về 2 chẳng hạn
Kiểm tra sự tồn tại của phần tử (contains)
Để kiểm tra xem một phần tử cụ thể có nằm trong List hay không, sử dụng contains()
.
if (fruits.contains("ぶどう")) {
System.out.println("ぶどう có tồn tại");
}
Tóm tắt: Danh sách các thao tác cơ bản thường dùng
Thao tác | Ví dụ phương thức | Mô tả |
---|---|---|
Thêm | add("phần tử") | Thêm vào cuối |
Lấy | get(chỉ mục) | Tham chiếu đến phần tử |
Cập nhật | set(chỉ mục, phần tử mới) | Thay đổi phần tử tại vị trí chỉ định |
Xóa | remove(chỉ mục/phần tử) | Xóa phần tử được chỉ định |
Lấy kích thước | size() | Lấy số lượng phần tử |
Kiểm tra tồn tại | contains("phần tử") | Kiểm tra sự tồn tại của phần tử cụ thể |
4. Ví dụ về các thao tác với List
Trong chương này, chúng tôi sẽ giới thiệu các ví dụ thao tác thực tế sử dụng List trong Java. Việc xử lý các phần tử trong danh sách theo thứ tự là rất phổ biến, và ở đây chúng tôi sẽ trình bày các phương pháp tiêu biểu sử dụng **vòng lặp for, vòng lặp for mở rộng (for-each), và Stream API**.
Duyệt qua List bằng vòng lặp for
Phương pháp cơ bản nhất là sử dụng vòng lặp for
để lấy các phần tử bằng cách sử dụng chỉ mục.
List<String> fruits = new ArrayList<>();
fruits.add("りんご");
fruits.add("バナナ");
fruits.add("みかん");
for (int i = 0; i < fruits.size(); i++) {
System.out.println(fruits.get(i));
}
Phương pháp này cho phép **kiểm soát chi tiết bằng chỉ mục**. Ví dụ, nó hữu ích khi bạn chỉ muốn xử lý các phần tử ở vị trí chẵn.
Duyệt qua List bằng vòng lặp for mở rộng (for-each)
Nếu bạn muốn xử lý tất cả các phần tử theo thứ tự mà không cần quan tâm đến chỉ mục, vòng lặp for mở rộng rất tiện lợi.
for (String fruit : fruits) {
System.out.println(fruit);
}
Cú pháp đơn giản, dễ đọc và là **một trong những phương pháp được sử dụng phổ biến nhất**. Đối với các xử lý đơn giản, phương pháp này là đủ.
Duyệt qua List bằng Lambda Expression và Stream API
Từ Java 8 trở đi, bạn cũng có thể sử dụng cú pháp kết hợp Stream API và Lambda Expression.
fruits.stream().forEach(fruit -> System.out.println(fruit));
Ưu điểm của cách viết này là **có thể kết nối nhiều xử lý lại với nhau**. Ví dụ, bạn có thể dễ dàng lọc theo một điều kiện cụ thể rồi mới in ra.
fruits.stream()
.filter(fruit -> fruit.contains("ん"))
.forEach(System.out::println);
Ví dụ này sẽ in ra chỉ những loại trái cây có chứa ký tự “ん”. Phương pháp này **đặc biệt được khuyến khích cho những ai muốn làm quen với phong cách code hàm (functional programming)**.
Cách sử dụng phù hợp từng phương pháp
Phương pháp | Ưu điểm | Trường hợp phù hợp |
---|---|---|
Vòng lặp for thông thường | Có thể kiểm soát chỉ mục | Xử lý cần đến số thứ tự phần tử |
Vòng lặp for mở rộng | Cú pháp đơn giản, dễ đọc | Xử lý duyệt đơn giản |
Stream API | Mạnh về xử lý có điều kiện và xử lý liên tục | Kết hợp lọc, ánh xạ, tập hợp |
5. Sự khác biệt và cách sử dụng phù hợp giữa ArrayList và LinkedList
Là các lớp triển khai tiêu biểu của interface List trong Java, **ArrayList** và **LinkedList** tuy có thể sử dụng giống nhau như một List, nhưng cấu trúc nội bộ và đặc tính hiệu năng có sự khác biệt. Do đó, việc lựa chọn phù hợp với từng tình huống là rất quan trọng.
Đặc điểm và mục đích sử dụng phù hợp của ArrayList
ArrayList
**sử dụng mảng động (mảng có thể thay đổi kích thước) bên trong**.
Đặc điểm chính:
- Truy cập ngẫu nhiên (theo chỉ mục) rất nhanh
- Thêm phần tử vào cuối danh sách rất nhanh (trung bình O(1))
- Chèn/xóa ở giữa chậm hơn (O(n))
Trường hợp phù hợp:
- Thường xuyên tìm kiếm (
get()
) - Số lượng phần tử có thể dự đoán được ở mức độ nhất định trước đó
- Ít thêm/xóa phần tử, chủ yếu là đọc dữ liệu
List<String> list = new ArrayList<>();
Đặc điểm và mục đích sử dụng phù hợp của LinkedList
LinkedList
được triển khai với **cấu trúc danh sách liên kết đôi (doubly linked list)**.
Đặc điểm chính:
- Thêm/xóa phần tử nhanh (đặc biệt ở đầu hoặc cuối)
- Truy cập ngẫu nhiên (
get(index)
) chậm (O(n)) - Tiêu thụ bộ nhớ hơi nhiều hơn so với ArrayList
Trường hợp phù hợp:
- Thường xuyên chèn/xóa phần tử (đặc biệt ở đầu hoặc giữa)
- Muốn sử dụng như Queue hoặc Stack
- Chủ yếu duyệt qua các phần tử (iteration), không cần truy cập theo chỉ mục
List<String> list = new LinkedList<>();
So sánh hiệu năng
Bảng dưới đây là **độ phức tạp tính toán lý thuyết (ký hiệu Big O)** cho các thao tác thường dùng.
Thao tác | ArrayList | LinkedList |
---|---|---|
get(int index) | O(1) | O(n) |
add(E e) (cuối) | O(1) | O(1) |
add(int index, E e) | O(n) | O(n) |
remove(int index) | O(n) | O(n) |
Duyệt (Iteration) | O(n) | O(n) |
※ Thời gian xử lý thực tế còn bị ảnh hưởng bởi kích thước dữ liệu, tối ưu hóa của JVM, v.v.

Lưu ý khi lựa chọn trong thực tế
- **Sử dụng ArrayList nếu “xử lý dữ liệu như một danh sách và truy cập bằng chỉ mục”**
- **Sử dụng LinkedList nếu “thường xuyên thêm/xóa ở đầu hoặc giữa”**
- **Trong các xử lý đòi hỏi hiệu năng nghiêm ngặt, hãy luôn đo lường (benchmark) để kiểm chứng**
6. Cách sử dụng List nâng cao
Tại đây, chúng tôi sẽ giới thiệu các kỹ thuật nâng cao để sử dụng List trong Java một cách tiện lợi hơn. List không chỉ là một tập hợp dữ liệu đơn thuần mà còn có thể thực hiện **nhiều xử lý đa dạng thông qua sắp xếp, trộn ngẫu nhiên (shuffle), lọc (filter), chuyển đổi (transform), v.v.**.
Sắp xếp List (Collections.sort)
Sử dụng Collections.sort()
, bạn có thể **sắp xếp các phần tử trong List theo thứ tự tăng dần**. Các phần tử cần phải triển khai interface Comparable
.
import java.util.*;
List<String> fruits = new ArrayList<>();
fruits.add("バナナ");
fruits.add("りんご");
fruits.add("みかん");
Collections.sort(fruits);
System.out.println(fruits); // Hiển thị [みかん, りんご, バナナ]
Sắp xếp theo thứ tự tùy chỉnh (sử dụng Comparator)
fruits.sort(Comparator.reverseOrder()); // Sắp xếp giảm dần
Trộn ngẫu nhiên List (Collections.shuffle)
Để thay đổi thứ tự các phần tử một cách ngẫu nhiên, bạn có thể sử dụng Collections.shuffle()
.
Collections.shuffle(fruits);
System.out.println(fruits); // Ví dụ: [バナナ, みかん, りんご]
Nó hữu ích khi bạn muốn tạo bộ bài trong game hoặc hiển thị theo thứ tự ngẫu nhiên.
Lọc bằng Stream API (filter)
Sử dụng Stream
từ Java 8 trở đi, bạn có thể viết một cách ngắn gọn xử lý **chỉ trích xuất các phần tử thỏa mãn điều kiện**.
List<String> filtered = fruits.stream()
.filter(fruit -> fruit.contains("ん"))
.collect(Collectors.toList());
System.out.println(filtered); // [みかん, りんご]
Chuyển đổi bằng Stream API (map)
Để chuyển đổi các phần tử sang một định dạng khác, sử dụng map()
.
List<Integer> lengths = fruits.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); // Độ dài tên từng loại trái cây [3, 3, 2] chẳng hạn
map()
là một công cụ mạnh mẽ trong việc **chuyển đổi định dạng hoặc tiền xử lý dữ liệu**.
Tóm tắt các thao tác nâng cao
Thao tác | Ví dụ sử dụng | Mục đích chính |
---|---|---|
Sắp xếp | Collections.sort(list) | Sắp xếp theo thứ tự tăng dần |
Trộn ngẫu nhiên | Collections.shuffle(list) | Thay đổi thứ tự các phần tử một cách ngẫu nhiên |
Lọc | stream().filter(...).collect() | Trích xuất chỉ các phần tử phù hợp với điều kiện |
Chuyển đổi | stream().map(...).collect() | Chuyển đổi kiểu hoặc giá trị của phần tử |
7. Các lỗi thường gặp và cách xử lý
Khi làm việc với List trong Java, lỗi mà người mới bắt đầu dễ mắc phải nhất là “ngoại lệ (error)”. Tại đây, chúng tôi sẽ giải thích cụ thể về các lỗi điển hình thường xảy ra trong thực tế, nguyên nhân và cách giải quyết.
IndexOutOfBoundsException (Lỗi chỉ mục nằm ngoài phạm vi)
Nguyên nhân:
Xảy ra khi cố gắng truy cập vào một chỉ mục không tồn tại.
List<String> list = new ArrayList<>();
list.add("りんご");
System.out.println(list.get(1)); // Lỗi: Index 1 out of bounds
Cách xử lý:
Kiểm tra kích thước trước khi truy cập hoặc sử dụng điều kiện để kiểm soát xem chỉ mục có hợp lệ hay không.
if (list.size() > 1) {
System.out.println(list.get(1));
}
NullPointerException (Ngoại lệ con trỏ null)
Nguyên nhân:
Xảy ra khi gọi phương thức trên List hoặc phần tử của List mà chúng đang ở trạng thái null
.
List<String> list = null;
list.add("りんご"); // Xảy ra NullPointerException
Cách xử lý:
Kiểm tra xem biến có phải là null trước khi sử dụng, hoặc sử dụng Optional, v.v.
if (list != null) {
list.add("りんご");
}
Hoặc chú ý không quên khởi tạo:
List<String> list = new ArrayList<>(); // Khởi tạo đúng
ConcurrentModificationException (Ngoại lệ sửa đổi đồng thời)
Nguyên nhân:
Xảy ra khi trực tiếp thay đổi List trong lúc duyệt List bằng vòng lặp for-each
hoặc Iterator
.
for (String fruit : list) {
if (fruit.equals("バナナ")) {
list.remove(fruit); // ConcurrentModificationException
}
}
Cách xử lý:
Sử dụng Iterator
để xóa an toàn hoặc sử dụng các phương thức như removeIf()
.
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("バナナ")) {
it.remove(); // Xóa an toàn
}
}
Hoặc, từ Java 8 trở đi có thể dùng cách ngắn gọn hơn:
list.removeIf(fruit -> fruit.equals("バナナ"));
Các lưu ý khác
- **Kiểm tra xem List có phải là null hay không**
- Trường hợp chỉ khai báo biến mà không sử dụng rất phổ biến. Việc khởi tạo là bắt buộc.
- **Hiểu rằng chỉ mục bắt đầu từ 0**
- Người mới bắt đầu thường nhầm “phần tử thứ 1 có chỉ mục là 1”.
Tóm tắt phòng chống lỗi
Tên lỗi | Nguyên nhân chính | Ví dụ cách xử lý |
---|---|---|
IndexOutOfBoundsException | Truy cập vào chỉ mục không tồn tại | Kiểm tra độ dài bằng size() |
NullPointerException | List hoặc phần tử là null | Không quên khởi tạo, thực hiện kiểm tra null |
ConcurrentModificationException | Thay đổi trực tiếp List khi đang duyệt | Thao tác bằng Iterator hoặc sử dụng removeIf() |
8. Tóm tắt
Ôn lại kiến thức cơ bản về Java List
Trong bài viết này, chúng tôi đã giải thích từng bước từ cơ bản đến nâng cao về List trong Java. List là một trong những collection được sử dụng rất thường xuyên trong Java và là **công cụ quan trọng để xử lý dữ liệu một cách linh hoạt**.
Đầu tiên, sau khi hiểu List là gì, chúng ta đã học được các điểm sau:
- List là một **collection có thứ tự, cho phép trùng lặp** và có thể thao tác bằng chỉ mục
- Có các lớp triển khai tiêu biểu như **ArrayList và LinkedList**, mỗi loại có đặc điểm và mục đích sử dụng khác nhau
- Nắm vững các thao tác cơ bản (thêm, lấy, cập nhật, xóa, tìm kiếm) cho phép thao tác dữ liệu một cách linh hoạt
- Các phương pháp lặp lại **tùy thuộc vào tình huống**, như vòng lặp for, vòng lặp for mở rộng, Stream API
- Hỗ trợ các xử lý nâng cao như sắp xếp, lọc, chuyển đổi
- **Hiểu nguyên nhân và cách xử lý** các lỗi thường gặp giúp ngăn ngừa sự cố
Cách lựa chọn phù hợp giữa ArrayList và LinkedList
Việc nên sử dụng loại List nào là **quan trọng để lựa chọn phù hợp tùy theo nội dung xử lý và lượng dữ liệu**. Dưới đây là một số gợi ý để đưa ra quyết định:
- **ArrayList**: Thường xuyên truy cập ngẫu nhiên, chủ yếu là đọc dữ liệu
- **LinkedList**: Thường xuyên xảy ra thêm/xóa phần tử, thứ tự truy cập là quan trọng
Hướng tới việc học tiếp theo
List chỉ là “điểm khởi đầu” của Java Collections. Để xử lý các cấu trúc dữ liệu và tiện ích nâng cao hơn, bạn nên tìm hiểu sâu hơn về các lớp/chức năng sau:
- **Set, Map**: Quản lý các phần tử duy nhất, cấu trúc cặp khóa-giá trị
- **Lớp tiện ích Collections**: Sắp xếp, lấy giá trị nhỏ nhất/lớn nhất, v.v.
- **Sử dụng Stream API**: Giới thiệu về lập trình hàm
- **Hiểu về Generics**: Thao tác collection an toàn kiểu dữ liệu
Khi bạn thành thạo kiến thức cơ bản về List, **lập trình Java nói chung sẽ trở nên dễ dàng hơn rất nhiều.**
Các câu hỏi thường gặp (FAQ)
Chúng tôi đã tổng hợp các điểm mà người mới bắt đầu thường băn khoăn về Java List. Chúng tôi đã chọn lọc các nội dung thường gặp trong thực tế.
Q1. Sự khác biệt giữa Java List và mảng (Array) là gì?
A. Mảng có số lượng phần tử cố định, cần xác định kích thước khi khai báo. Ngược lại, List có kích thước có thể thay đổi và có thể thêm hoặc xóa phần tử một cách linh hoạt. Hơn nữa, List có nhiều phương thức tiện lợi (add
, remove
, contains
, v.v.) và vượt trội hơn về tính dễ đọc và khả năng bảo trì.
Q2. Nên sử dụng ArrayList hay LinkedList?
A. ArrayList
phù hợp khi bạn chủ yếu thực hiện **truy cập ngẫu nhiên (lấy theo chỉ mục)**. LinkedList
phù hợp khi **thường xuyên xảy ra việc chèn/xóa phần tử**. Khi phân vân, thường bắt đầu với ArrayList
.
Q3. List có thể lưu trữ kiểu nguyên thủy (int, double, v.v.) không?
A. Không trực tiếp được. Java List chỉ xử lý kiểu đối tượng, do đó, đối với các kiểu nguyên thủy như int
, bạn cần sử dụng các lớp Wrapper tương ứng (Integer
, Double
, v.v.).
List<Integer> numbers = new ArrayList<>();
numbers.add(10); // Được autoboxing và lưu trữ dưới dạng kiểu Integer
Q4. Tôi muốn sắp xếp các phần tử trong List, làm thế nào?
A. Bạn có thể sắp xếp theo thứ tự tăng dần bằng cách sử dụng Collections.sort(list)
. Ngoài ra, nếu muốn sắp xếp theo thứ tự tùy chỉnh, bạn có thể chỉ định Comparator
để sắp xếp tự do.
Q5. Nếu tôi muốn quản lý các phần tử không bị trùng lặp thì làm thế nào?
A. List là collection cho phép trùng lặp. Nếu muốn tránh trùng lặp, bạn nên xem xét sử dụng Set
(ví dụ: HashSet
). Tuy nhiên, cần lưu ý là thứ tự không được đảm bảo. Nếu muốn loại bỏ trùng lặp trong khi vẫn giữ nguyên là List, bạn có thể sử dụng xử lý Stream như sau:
List<String> distinctList = list.stream()
.distinct()
.collect(Collectors.toList());
Q6. Làm thế nào để xóa tất cả nội dung của List?
A. Sử dụng phương thức clear()
sẽ xóa tất cả các phần tử của List.
list.clear();
Q7. Thao tác nào với List được sử dụng phổ biến nhất?
A. Các thao tác được sử dụng thường xuyên nhất trong thực tế là add
(thêm), get
(lấy), remove
(xóa), size
(lấy kích thước). Nắm vững các thao tác này sẽ giúp bạn xử lý được hầu hết các công việc cơ bản.