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 các 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, thân thiện với người mới bắt đầu, đến các kỹ thuật thực tiễn và những 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 mã thực tế.
Dù bạn mới bắt đầu học Java hay đã thường xuyên sử dụng List, đâ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 quyết 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 mới 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 một 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ử nào.
Các điểm chính:
Listlà một interface, vì vậy bạn phải 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í mở rộng khi thêm mục và tăng 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 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 bạn dễ dàng khởi tạo List rỗ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ễ dàng và lý tưởng cho các giá trị hằng số.
3.3 Tạo một List có thể thay đổi từ Arrays.asList()
Nếu bạn muốn một List có cá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í bổ sung và có thể rò rỉ bộ nhớ.
- Chỉ sử dụng cho các demo nhanh hoặc mã kiểm tra; không khuyến nghị cho sản xuất.
Điều này cho thấy Java cung cấp nhiều cách khác nhau để tạo Lists với giá trị ban đầu tùy thuộc vào nhu cầu của bạn.
5. Tiêu chí So sánh và Lựa chọn
Java cung cấp nhiều phương pháp khởi tạo List khác nhau, 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 chọn cái nào.
5.1 Danh sách Có thể thay đổi vs Không thể thay đổi
- Danh sách 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(...)) - Tốt nhất cho các hoạt động động hoặc thêm mục trong vòng lặp.
- Danh sách Không thể thay đổi
- Không thêm, xóa hoặc sửa đổi.
- Ví dụ:
List.of(...),Collections.singletonList(...),Collections.nCopies(...) - Lý tưởng cho hằng số hoặc truyền giá trị an toàn.
5.2 Bảng So sánh Các Phương pháp Phổ biến
| 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 Khuyến nghị 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 giá trị ban đầu và muốn sửa đổi sau
new ArrayList<>(Arrays.asList(...))- Khi sử dụng nó như một hằng số không sửa đổ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 giá trị cần được tạo động
Stream.generate(...).limit(n).collect(Collectors.toList())

5.4 Ghi chú Quan trọng
- Việc cố gắng sửa đổi danh sách không thể thay đổi hoặc kích thước cố định sẽ dẫn đến ngoại lệ.
- Chọn phương pháp phù hợp nhất với tính có thể thay đổi yêu cầu và phiên bản Java của bạn.
Việc chọn phương pháp khởi tạo đúng ngăn chặn lỗi không mong muốn và cải thiện khả năng đọc và an toàn.
6. Các Lỗi Phổ biến và Cách Khắc phục
Một số lỗi thường xảy ra khi khởi tạo hoặc sử dụng Lists 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 tình huống phổ biến:
- Gọi
add()hoặcremove()trên một List được tạo quaArrays.asList(...) - Sửa đổi một List được tạo qua
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 Lists không thể thay đổi kích thước hoặc hoàn toàn không thể thay đổi.
Giải pháp:
- Bọc bằng một List có thể thay đổi:
new ArrayList<>(Arrays.asList(...))
6.2 NullPointerException
Tình huống 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 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
- Tạo một List mà không có generics 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 generics bất cứ khi nào có thể.
Việc hiểu các lỗi phổ biến này sẽ giúp bạn tránh các vấn đề khi khởi tạo hoặc sử dụng Lists.
7. Tóm tắ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 cái phù hợp.
Chúng ta đã đề cập đến:
.
- Tạo List rỗng cơ bản bằng
new ArrayList<>()vànew LinkedList<>() - List với các 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 bất biến
- 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, nhưng hiểu rõ các biến thể này và 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ác 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 ra UnsupportedOperationException. Hãy dùng new ArrayList<>(Arrays.asList(...)) để có một List có thể thay đổi.
Q2: Sự khác nhau giữa List.of() và Arrays.asList() là gì?
List.of()(Java 9+) → hoàn toàn bất biế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 dùng cách khởi tạo chuẩn thay thế.
Q4: Lợi ích của việc chỉ định dung lượng ban đầu là gì?
A4: 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 rồi. Sử dụng generic giúp tăng 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 một List mà chưa khởi tạo?
A6: Gọi bất kỳ phương thức nào trên nó sẽ gây ra NullPointerException. Hãy luôn khởi tạo trước.
Q7: Có sự khác biệt 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.

