Hế lô anh em ✌️✌️✌️
Trong bài viết hôm nay mình sẽ cùng anh em tìm hiểu về class ArrayList - một đối tượng trong Java Collection Framework.
Đây là đối tượng rất hay được sử dụng khi chúng ta làm việc với một danh sách (có thể là danh sách các giá trị số, chuỗi hay thậm chí là danh sách các đối tượng...)
1. Tổng quan
Trong Java thì List
(interface) là một tập hợp các phần tử được sắp xếp theo một tuần tự nhất định mà tại đó một phần tử có thể lặp lại một hoặc nhiều lần.
ArrayList
là một trong những class triển khai của interface List
và được xây dựng theo những tính chất của một Array
nhưng lại có thể thêm bớt phần tử linh hoạt hơn so với Array
.
ArrayList
nằm trong bộ thư viện của Java Core nên để sử dụng class này anh em chỉ cần import vào class muốn dùng theo cú pháp sau:
import java.util.ArrayList;
Sau đây là độ phức tạp thời gian của một số thao tác đối với ArrayList
- Truy cập một phần tử bất kỳ: O(1)
- Thêm mới một phần tử: O(1)
- Chèn hoặc xóa một phần tử: O(n)
- Tìm kiếm với một danh sách chưa được sắp xếp: O(n)
- Tìm kiếm với một danh sách đã được sắp xếp: O(log(n))
Hierarchy của class ArrayList trong Java
2. Khởi tạo một ArrayList
Chúng ta có thể khởi tạo ArrayList
theo nhiều kiểu constructor khác nhau. Mình sẽ cùng anh em tìm hiểu những kiểu khởi tạo đó nhưng trước tiên anh em lưu ý cho mình:
- ArrayList
là một generic class vì vậy chúng ta không nhất thiết phải truyền một loại tham số với kiểu dữ liệu cụ thể nào trong trường hợp muốn danh sách đó có nhiều kiểu dữ liệu khác nhau.
Ví dụ: Nếu khai báo như bên dưới chúng ta hoàn toàn có thể thêm nhiều thành phần với nhiều kiểu dữ liệu khác nhau vào ArrayList
.
ArrayList data = new ArrayList<>();
- Trường hợp muốn truyền mặc định tham số với một kiểu dữ liệu nào đó thì ArrayList
sẽ không thể chứa các thành phần có kiểu dữ liệu khác.
Ví dụ: Nếu khai báo như bên dưới chúng ta sẽ không thể thêm các kiểu dữ liệu khác Integer
vào ArrayList
này.
ArrayList<Integer> data = new ArrayList<>();
- Thông thường người ta sẽ sử dụng interface List
để làm kiểu dữ liệu của biến khi cần khai báo một ArrayList
vì bản chất class ArrayList
cũng là một trong số các triển khai của interface List
.
List<Integer> data = new ArrayList<>();
2.1 - Sử dụng constructor mặc định
Đầu tiên cũng là cách phổ biến hay được dùng nhất đó là sử dụng constructor mặc định không có tham số. Theo cách khởi tạo này chúng ta có một empty ArrayList
với kích thước khởi tạo ban đầu là 10 phần tử.
List<Integer> data = new ArrayList<>();
data.isEmpty(); // true
2.2 - Sử dụng constructor với kích thước định sẵn
Với cách khởi tạo bằng constructor mặc đinh chúng ta có một empty ArrayList
với kích thước ban đầu là 10 phần tử.
Nhưng để thay đổi kích thước mặc định đó chúng ta có thể sử dụng constructor với kích thước được định sẵn như bên dưới:
List<Integer> data = new ArrayList<>(200);
data.isEmpty(); // true
data.size(); // 0
Cách khởi tạo này đặc biệt có tác dụng trong các trường hợp chúng ta biết rõ kích thước dữ liệu của danh sách, đặc biệt là các danh sách có kích thước dữ liệu tương đối lớn.
2.3 - Sử dụng constructor với một danh sách ban đầu
List<String> initList = new ArrayList<>();
initList.add("A");
initList.add("B");
initList.add("C");
initList.add("D");
List<String> data = new ArrayList<>(initList);
data.size(); // 4
Cách này được sử dụng khi chúng ta có một danh sách ban đầu nhưng nhược điểm là nếu danh sách ban đầu null
có thể dẫn tới ngoại lệ kinh điển NullPointerException
(đây thực sự là một trường hợp rất hay gặp nếu chúng ta không để ý hoặc thiếu kinh nghiệm)
3. Thêm/chèn phần tử vào ArrayList
Chúng ta có thể thêm một phần tử ở cuối danh sách hoặc ở một vị trí bất kỳ (trong kích thước được khởi tạo) bằng cách sử dụng hàm add()
như sau:
List<Integer> data = new ArrayList<>();
data.add(1); // thêm vào vị trí index = 0 giá trị 1
data.add(2); // thêm vào vị trí index = 1 giá trị 2
data.add(1, 12); // chèn vào vị trí index = 1 giá trị 12 -> giá trị cũ tại index = 1 di chuyển sang vị trí index = 2
data.add(5, 24); // lỗi vì truy cập vị trí có index ngoài kích thước
Ngoài ra chúng ta cũng có thể thêm một lúc nhiều phần tử bằng cách sử dụng hàm addAll()
List<Integer> data = new ArrayList<>();
List<Integer> subData = new ArrayList<>(Arrays.asList(3, 4, 5));
data.add(1);
data.add(2);
data.addAll(subData);
Note: Việc thêm và chèn là hai thác tác khác nhau, nếu thêm chúng ta chỉ đơn giản thêm phần tử vào cuối cùng của danh sách nhưng chèn bản chất vẫn là thêm nhưng lại thêm ở một index nào đó.
4. Duyệt qua các phần tử của ArrayList
Có nhiều cách để duyệt qua các phần tử của một ArrayList
4.1 - Sử dụng vòng lặp for
Đây có lẽ là cách đơn giản mà chúng ta vẫn hay sử dụng đặc biệt là khi mới tiếp cận với ngôn ngữ lập trình và với các câu lệnh lặp.
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.printf("%s%10s%n", "index", "value");
for (int index = 0; index < numbers.size(); ++index) {
System.out.printf("%d%10d%n", index, numbers.get(index));
}
Output:
index value
0 1
1 2
2 3
3 4
4 5
4.2 - Sử dụng vòng lặp while
Vòng lặp while về cơ bản giống như vòng lặp for và được sử dụng như sau:
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.printf("%s%10s%n", "index", "value");
int index = 0;
while (numbers.size() > index) {
System.out.printf("%d%10d%n", index, numbers.get(index));
index++;
}
4.3 - Sử dụng vòng lặp for-each
Được thêm vào từ phiên bản Java 5, for-each (advanced for loop) là cách viết ngắn ngọn hơn giúp chúng ta làm việc với các danh sách chứa đối tượng thuận tiện hơn so với vòng for kiểu cổ điển.
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.printf("%s%10s%n", "index", "value");
int index = 0;
for (Integer number: numbers) {
System.out.printf("%d%10d%n", index, number);
index++;
}
Tuy nhiên nếu phải sử dụng đến index của bản ghi thì chúng ta buộc phải khởi tạo bên ngoài và điều này đôi khi cũng hơi bất tiện.
4.4 - Sử dụng ListIterator
Java cung cấp một interface là ListIterator
để chúng ta có thể làm việc với ArrayList
trong quá trình lặp qua các phần tử đồng thời sử dụng một số hàm như sau:
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.printf("%s%10s%n", "index", "value");
ListIterator iterator = numbers.listIterator();
while (iterator.hasNext()) {
System.out.printf("%d%10d%n", iterator.nextIndex(), iterator.next());
}
4.5 - Sử dụng Streams API
Stream API được hỗ trợ từ phiên bản Java 8 với nhiều bổ sung đáng kể trong đó có việc giúp chúng ta duyệt qua các phần tử của một List
nói chung bằng cách sử dụng vòng lặp forEach
như sau:
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.printf("%s%n", "value");
numbers.forEach(number -> {
System.out.printf("%d%n", number);
});
5. Tìm kiếm trong ArrayList
Tìm kiếm là một thao tác chúng ta sẽ thường xuyên sử dụng khi làm việc với ArrayList
và giống như việc duyệt các phần tử thì cũng có nhiều cách để triển khai tìm kiếm trong một ArrayList
.
5.1 - Sử dụng vòng lặp
Ý tưởng đơn giản nhất là sử dụng vòng lặp để duyệt qua toàn bộ phần tử và kiểm tra tại mỗi lần duyệt phần tử đó có phải là phần tử chúng ta đang muốn tìm.
List<String> names = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E", "F"));
String myName = "E";
names.forEach(name -> {
if(name.equals(myName)) {
System.out.printf("%s%d", myName + " found at index ", names.indexOf(myName));
}
});
Ngoài ra anh em có thể sử dụng chính hàm indexOf(Object obj)
để lấy được vị trí của phần tử muốn tìm kiếm rồi sử dụng hàm get(int index)
để lấy ra phần tử đó từ danh sách.
Ví dụ mình có một danh sách các đối tượng Student
:
List<Student> students = new ArrayList<>();
students.add(new Student("01", "A"));
students.add(new Student("02", "B"));
students.add(new Student("03", "C"));
students.add(new Student("04", "D"));
students.add(new Student("05", "E"));
Và mình có thể lấy ra một đối tượng như sau:
int index = students.indexOf(new Student("01", "B"));
if(index != -1) {
Student s = students.get(index);
}
Note: Trong trường hợp đối tượng anh em truyền vào hàm indexOf()
bị null hàm này sẽ trả về giá trị -1. Khi đó nếu không kiểm tra điều kiện index != -1
sẽ có thể gặp ngoại lệ Index -1 out of bounds for length...
5.2 - Sử dụng Streams API
Đối với các phiên bản từ Java 8, Streams API hỗ trợ chúng ta tìm kiếm trong danh sách như sau:
String stdName = "BB";
Student student = students.stream()
.filter(std -> stdName.equals(std.getName()))
.findAny()
.orElseThrow(() -> new Exception("Không tìm thấy sinh viên có tên " + stdName));
6. Xóa một phần tử khỏi ArrayList
Khi sử dụng List
làm kiểu dữ liệu chúng ta có thể xóa phần tử trong ArrayList
bởi bốn phương thức khác nhau:
- remove(int index)
: Xóa theo index
- remove(Object o)
: Xóa theo giá trị
- removeAll()
: Xóa nhiều phần tử
- removeIf()
: Xóa theo điều kiện
6.1 - Xóa theo index
int index = 1;
Student deletedStudent = students.remove(index);
Phương thức này phù hợp khi chúng ta biết vị trí của phần tử cần xóa và xóa theo cách này sẽ trả về thông tin của đối tượng tại vị trí được xóa.
6.2 - Xóa theo giá trị
boolean isDeleted = students.remove(new Student("01", "B"));
Nếu chúng ta không biết cụ thể index của phần tử muốn xóa thì đây là một phương thức phù hợp. Nó sẽ xóa phần tử đầu tiên trong số các phần tử có giá trị trùng với giá trị phần tử mà chúng ta truyền vào.
Nếu danh sách chứa phần tử có giá trị trùng với phần tử cần xóa thì hàm sẽ trả về true
và ngược lại sẽ trả về false
.
6.3 - Xóa nhiều phần tử
List<Student> deleteStudents = new ArrayList<>();
deleteStudents.add(new Student("01", "B"));
deleteStudents.add(new Student("02", "B"));
boolean isDeleted = students.removeAll(deleteStudents);
Hàm này sẽ nhận vào một danh sách các phần tử cần xóa và trả về true
nếu xóa thành công hoặc false
nếu ngược lại.
6.4 - Xóa theo điều kiện
String deletedCode = "01";
boolean isDeleted = students.removeIf(student -> deletedCode.equals(student.getCode()));
Về tư tưởng thì removeIf() giống như việc anh em lặp và kiểm tra với mỗi phần tử thỏa điều kiện thì dùng hàm remove(Object obj) hoặc remove(int index) để xóa khỏi danh sách.
7. Lời kết
Vậy là trong bài viết hôm nay mình đã cùng anh em tìm hiểu về một đối tượng rất hay được sử dụng khi làm việc với dữ liệu dạng danh sách.
Tất nhiên còn kiểu cấu trúc dữ liệu khác nhau nữa và mình sẽ cùng anh em tìm hiểu trong các bài viết tiếp theo.
Hi vọng qua bài viết này sẽ giúp anh em hiểu hơn về ArrayList nói riêng và nắm được cách sử dụng một số hàm thông dụng mà đối tượng này cung cấp.
Hẹn gặp lại anh em trong các bài viết tiếp theo nhé 👋👋👋
Không có nhận xét nào: