1. Giới thiệu
Khi lập trình bằng Java, List là một trong những cấu trúc dữ liệu được sử dụng thường xuyên và quan trọng nhất. Việc sử dụng List cho phép bạn lưu trữ nhiều mục theo thứ tự và dễ dàng thực hiện các thao tác như thêm, xóa và tìm kiếm phần tử khi cần.
Tuy nhiên, để sử dụng List một cách hiệu quả, bạn cần hiểu đầy đủ các phương pháp khởi tạo. Việc khởi tạo sai có thể gây ra lỗi hoặc bug không mong muốn và ảnh hưởng đáng kể đến khả năng đọc và bảo trì mã nguồn.
Trong bài viết này, chúng tôi tập trung vào chủ đề “Khởi tạo List trong Java” và giải thích mọi thứ từ các phương pháp khởi tạo cơ bản dành cho người mới bắt đầu đến các kỹ thuật thực tiễn và những cạm bẫy thường gặp. Chúng tôi cũng đề cập đến sự khác nhau giữa các phiên bản Java và các thực tiễn tốt nhất dựa trên các tình huống lập trình thực tế.
Dù bạn mới bắt đầu học Java hay đã sử dụng List thường xuyên, đây là cơ hội tuyệt vời để ôn lại và sắp xếp các mẫu khởi tạo khác nhau.
Một phần FAQ được cung cấp ở cuối để giúp giải đáp các câu hỏi và vấn đề phổ biến.
2. Các phương pháp khởi tạo List cơ bản
Khi bắt đầu sử dụng List trong Java, bước đầu tiên là tạo một “List rỗng”, nghĩa là khởi tạo List. Ở đây, chúng tôi giải thích các phương pháp khởi tạo cơ bản bằng cách sử dụng triển khai phổ biến nhất, ArrayList.
2.1 Tạo List rỗng bằng new ArrayList<>()
Phương pháp khởi tạo được sử dụng nhiều nhất là new ArrayList<>(), viết như sau:
List<String> list = new ArrayList<>();
Điều này tạo ra một List rỗng không có phần tử.
Các điểm chính:
Listlà một interface, vì vậy bạn cần khởi tạo một lớp cụ thể nhưArrayListhoặcLinkedList.- Thông thường nên khai báo biến dưới dạng
Listđể tăng tính linh hoạt.
2.2 Khởi tạo với dung lượng ban đầu được chỉ định
Nếu bạn dự đoán sẽ lưu trữ một lượng lớn dữ liệu hoặc đã biết số lượng phần tử, việc chỉ định dung lượng ban đầu sẽ cải thiện hiệu suất.
Ví dụ:
List<Integer> numbers = new ArrayList<>(100);
Điều này dự trữ không gian cho 100 phần tử nội bộ, giảm chi phí thay đổi kích thước khi thêm mục và nâng cao hiệu năng.
2.3 Khởi tạo một LinkedList
Bạn cũng có thể sử dụng LinkedList tùy theo nhu cầu. Cách sử dụng gần như giống nhau:
List<String> linkedList = new LinkedList<>();
LinkedList đặc biệt hiệu quả trong các tình huống mà phần tử thường xuyên được thêm hoặc xóa.
Java cho phép khởi tạo List rỗng một cách dễ dàng bằng new ArrayList<>() hoặc new LinkedList<>().
3. Tạo List với các giá trị khởi tạo
Trong nhiều trường hợp, bạn muốn tạo một List đã chứa sẵn các giá trị khởi tạo. Dưới đây là các mẫu khởi tạo phổ biến nhất và đặc điểm của chúng.
3.1 Sử dụng Arrays.asList()
Một trong những phương pháp được sử dụng nhiều nhất trong Java là Arrays.asList().
Ví dụ:
List<String> list = Arrays.asList("A", "B", "C");
Điều này tạo ra một List với các giá trị khởi tạo.
Lưu ý quan trọng:
- List trả về có kích thước cố định và không thể thay đổi độ dài. Gọi
add()hoặcremove()sẽ gây raUnsupportedOperationException. - Thay thế phần tử (bằng
set()) là được phép.
3.2 Sử dụng List.of() (Java 9+)
Từ Java 9 trở đi, List.of() cho phép tạo List bất biến một cách dễ dàng:
List<String> list = List.of("A", "B", "C");
Đặc điểm:
- List hoàn toàn bất biến —
add(),set()vàremove()đều bị cấm. - Đọc dễ hiểu và lý tưởng cho các giá trị hằng.
3.3 Tạo List có thể thay đổi từ Arrays.asList()
Nếu bạn muốn một List có giá trị khởi tạo nhưng cũng muốn có thể sửa đổi sau này, phương pháp này rất hữu ích:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Điều này tạo ra một List có thể thay đổi.
add()vàremove()hoạt động bình thường.
3.4 Khởi tạo Double‑Brace
Một kỹ thuật nâng cao hơn sử dụng lớp ẩn danh:
List<String> list = new ArrayList<>() {{
add("A");
add("B");
add("C");
}};
Đặc điểm & Cảnh báo:
- Tạo mã ngắn gọn nhưng giới thiệu một lớp ẩn danh, gây ra chi phí phụ trội và có thể dẫn đến rò rỉ bộ nhớ.
- Chỉ sử dụng cho các bản demo nhanh hoặc mã thử nghiệm; không được khuyến nghị cho môi trường sản xuất.
Điều này cho thấy Java cung cấp nhiều cách khác nhau để tạo List với các giá trị khởi tạo tùy theo nhu cầu của bạn.
5. So sánh và Tiêu chí Lựa chọn
Java cung cấp đa dạng các phương pháp khởi tạo List, và lựa chọn tốt nhất phụ thuộc vào trường hợp sử dụng. Phần này tóm tắt từng phương pháp và giải thích khi nào nên chọn chúng.
5.1 List Có Thể Thay Đổi vs List Không Thể Thay Đổi
- List có thể thay đổi
- Các phần tử có thể được thêm, xóa hoặc sửa đổi.
- Ví dụ:
new ArrayList<>(),new ArrayList<>(Arrays.asList(...)) Thích hợp cho các thao tác động hoặc khi cần thêm phần tử trong vòng lặp.
List không thể thay đổi
- Không cho phép thêm, xóa hoặc sửa đổi.
- Ví dụ:
List.of(...),Collections.singletonList(...),Collections.nCopies(...) - Lý tưởng cho các hằng số hoặc việc truyền giá trị an toàn.
5.2 Bảng So sánh Các Phương pháp Thông dụng
| Method | Mutability | Java Version | Characteristics / Use Cases |
|---|---|---|---|
new ArrayList<>() | Mutable | All Versions | Empty List; add elements freely |
Arrays.asList(...) | Fixed Size | All Versions | Has initial values but size cannot change |
new ArrayList<>(Arrays.asList(...)) | Mutable | All Versions | Initial values + fully mutable; widely used |
List.of(...) | Immutable | Java 9+ | Clean immutable List; no modifications allowed |
Collections.singletonList(...) | Immutable | All Versions | Immutable List with a single value |
Collections.nCopies(n, obj) | Immutable | All Versions | Initialize with n identical values; useful for testing |
Stream.generate(...).limit(n) | Mutable | Java 8+ | Flexible pattern generation; good for random or sequential data |
5.3 Các Mẫu Khởi tạo Được Đề xuất Theo Trường hợp Sử dụng
- Khi bạn chỉ cần một List rỗng
new ArrayList<>()Khi bạn cần các giá trị khởi tạo và muốn sửa đổi sau này
new ArrayList<>(Arrays.asList(...))Khi sử dụng nó như một hằng số không thay đổi
List.of(...)(Java 9+)Collections.singletonList(...)Khi bạn muốn một số lượng cố định các giá trị giống nhau
Collections.nCopies(n, value)Khi các giá trị cần được tạo động
Stream.generate(...).limit(n).collect(Collectors.toList())

5.4 Lưu ý Quan trọng
- Cố gắng sửa đổi các List không thể thay đổi hoặc có kích thước cố định sẽ gây ra ngoại lệ.
- Chọn phương pháp phù hợp nhất với mức độ thay đổi cần thiết và phiên bản Java bạn đang dùng.
Việc chọn đúng phương pháp khởi tạo giúp ngăn ngừa lỗi không mong muốn và cải thiện tính đọc được cũng như độ an toàn của mã.
6. Các Lỗi Thường Gặp và Cách Khắc Phục
Một số lỗi thường xuất hiện khi khởi tạo hoặc sử dụng List trong Java. Dưới đây là các ví dụ phổ biến và giải pháp của chúng.
6.1 UnsupportedOperationException
Các kịch bản phổ biến:
- Gọi
add()hoặcremove()trên một List được tạo bằngArrays.asList(...) - Sửa đổi một List được tạo bằng
List.of(...),Collections.singletonList(...)hoặcCollections.nCopies(...)
Ví dụ:
List<String> list = Arrays.asList("A", "B", "C");
list.add("D"); // Throws UnsupportedOperationException
Nguyên nhân:
- Các phương pháp này tạo ra các List không thể thay đổi kích thước hoặc hoàn toàn bất biến.
Giải pháp:
- Bao bọc bằng một List có thể thay đổi:
new ArrayList<>(Arrays.asList(...))
6.2 NullPointerException
Kịch bản phổ biến:
- Truy cập một List chưa được khởi tạo
Ví dụ:
List<String> list = null;
list.add("A"); // NullPointerException
Nguyên nhân:
- Một phương thức được gọi trên một tham chiếu
null.
Giải pháp:
- Luôn khởi tạo trước khi sử dụng:
List<String> list = new ArrayList<>();
6.3 Các Vấn đề Liên quan đến Kiểu Dữ liệu
- Tạo List mà không chỉ định generic làm tăng nguy cơ lỗi kiểu thời gian chạy.
Ví dụ:
List list = Arrays.asList("A", "B", "C");
Integer i = (Integer) list.get(0); // ClassCastException
Giải pháp:
- Luôn sử dụng generic bất cứ khi nào có thể.
Hiểu rõ các lỗi thường gặp này sẽ giúp bạn tránh các vấn đề khi khởi tạo hoặc sử dụng List.
7. Tổng kết
Bài viết này đã giải thích các phương pháp khởi tạo List khác nhau trong Java và cách chọn lựa phù hợp.
Chúng tôi đã đề cập tới:
- Tạo List rỗng cơ bản bằng
new ArrayList<>()vànew LinkedList<>() - List với giá trị khởi tạo bằng
Arrays.asList(),List.of()vànew ArrayList<>(Arrays.asList(...)) - Các mẫu khởi tạo đặc biệt như
Collections.singletonList(),Collections.nCopies()vàStream.generate() - Sự khác biệt chính giữa List có thể thay đổi và List không thể thay đổi
- Những cạm bẫy thường gặp và cách xử lý lỗi
Mặc dù việc khởi tạo List có vẻ đơn giản, việc hiểu rõ các biến thể này và lựa chọn phương pháp phù hợp là rất quan trọng để viết mã an toàn và hiệu quả.
8. FAQ (Câu hỏi thường gặp)
Q1: Tôi có thể thêm phần tử vào List được tạo bằng Arrays.asList() không?
A1: Không. Arrays.asList() trả về một List có kích thước cố định. Gọi add() hoặc remove() sẽ ném UnsupportedOperationException. Sử dụng new ArrayList<>(Arrays.asList(...)) để có List có thể thay đổi.
Q2: Sự khác nhau giữa List.of() và Arrays.asList() là gì?
List.of()(Java 9+) → bất biến hoàn toàn; ngay cảset()cũng không được phép.Arrays.asList()→ kích thước cố định nhưng cho phépset().
Q3: Tôi có nên sử dụng Double‑Brace Initialization không?
A3: Không nên vì nó tạo ra một lớp ẩn danh và có thể gây rò rỉ bộ nhớ. Hãy sử dụng cách khởi tạo tiêu chuẩn thay thế.
Q4: Lợi ích của việc chỉ định dung lượng ban đầu là gì?
A4: Nó giảm việc thay đổi kích thước nội bộ khi thêm nhiều phần tử, cải thiện hiệu năng.
Q5: Tôi có nên luôn luôn sử dụng generic khi khởi tạo List không?
A5: Chắc chắn. Sử dụng generic cải thiện tính an toàn kiểu và ngăn ngừa lỗi thời gian chạy.
Q6: Điều gì sẽ xảy ra nếu tôi sử dụng List mà không khởi tạo nó?
A6: Gọi bất kỳ phương thức nào trên nó sẽ gây ra NullPointerException. Luôn khởi tạo trước.
Q7: Có sự khác nhau về phiên bản trong việc khởi tạo List không?
A7: Có. List.of() chỉ có sẵn từ Java 9 trở lên.

