Header Ads

Seo Services

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:

Được tạo bởi Blogger.