Mảng (Arrays)
Bài trước: Wrapper Classes — Đã học về các wrapper class cho primitive types. Bài này sẽ học về arrays - cấu trúc dữ liệu cơ bản để lưu trữ nhiều giá trị cùng kiểu.
Sau bài này, bạn sẽ:
- Hiểu array là fixed-size container chứa elements cùng kiểu dữ liệu
- Khai báo, khởi tạo, và truy cập elements trong array an toàn
- Duyệt array với for loop và enhanced for-each loop
- Sử dụng Arrays utility class cho sort, search, copy, và compare
- Áp dụng các thuật toán cơ bản: tìm min/max, tính tổng, đảo ngược, tìm kiếm
Array là một trong những cấu trúc dữ liệu cơ bản và quan trọng nhất trong Java, cho phép lưu trữ nhiều giá trị cùng kiểu dữ liệu trong một biến duy nhất.
Array là gì?
Array (mảng) là một container object chứa một số lượng cố định các giá trị có cùng kiểu dữ liệu.
Đặc điểm của Array
| Đặc điểm | Mô tả |
|---|---|
| Fixed size | Kích thước cố định, không thể thay đổi sau khi tạo |
| Same type | Tất cả elements phải cùng kiểu dữ liệu |
| Index-based | Truy cập elements qua index (bắt đầu từ 0) |
| Object | Array là object, không phải primitive |
| Contiguous memory | Elements được lưu liên tiếp trong bộ nhớ |
Array vs Variable đơn
// Không dùng array - Lặp lại nhiều
int score1 = 85;
int score2 = 90;
int score3 = 78;
int score4 = 92;
int score5 = 88;
// Dùng array - Gọn gàng hơn
int[] scores = {85, 90, 78, 92, 88};
Khai báo và Khởi tạo Array
1. Khai báo Array
// Cú pháp 1: Type[] arrayName (Khuyến khích)
int[] numbers;
String[] names;
double[] prices;
// Cú pháp 2: Type arrayName[] (Giống C/C++)
int scores[];
String cities[];
// Khai báo nhiều arrays cùng lúc
int[] arr1, arr2, arr3;
Java khuyến khích dùng Type[] name thay vì Type name[] vì rõ ràng hơn: int[] là kiểu dữ liệu.
2. Khởi tạo Array
// Cách 1: Khai báo + Tạo array với size
int[] numbers = new int[5]; // Array 5 elements, mặc định = 0
// Cách 2: Khai báo + Khởi tạo với giá trị
int[] scores = {85, 90, 78, 92, 88};
// Cách 3: Tách khai báo và khởi tạo
String[] names;
names = new String[3];
// Cách 4: Array initializer với new
int[] values = new int[]{10, 20, 30, 40};
// Không hợp lệ - Không thể dùng {} sau khai báo
int[] data;
// data = {1, 2, 3}; // COMPILE ERROR
data = new int[]{1, 2, 3}; // OK
3. Giá trị mặc định
Khi tạo array với new, các elements được khởi tạo với giá trị mặc định:
| Kiểu dữ liệu | Giá trị mặc định |
|---|---|
byte, short, int, long | 0 |
float, double | 0.0 |
boolean | false |
char | '\u0000' (null character) |
| Object references | null |
int[] numbers = new int[3];
System.out.println(numbers[0]); // 0
boolean[] flags = new boolean[3];
System.out.println(flags[0]); // false
String[] names = new String[3];
System.out.println(names[0]); // null
Truy cập và Thay đổi Elements
Truy cập elements
int[] scores = {85, 90, 78, 92, 88};
// Truy cập bằng index (bắt đầu từ 0)
System.out.println(scores[0]); // 85 (element đầu tiên)
System.out.println(scores[2]); // 78
System.out.println(scores[4]); // 88 (element cuối cùng)
// Lấy element cuối cùng
int lastScore = scores[scores.length - 1]; // 88
Thay đổi elements
int[] numbers = {10, 20, 30, 40, 50};
// Thay đổi giá trị
numbers[0] = 15;
numbers[2] = 35;
System.out.println(Arrays.toString(numbers)); // [15, 20, 35, 40, 50]
ArrayIndexOutOfBoundsException
Truy cập index không hợp lệ sẽ gây ra ArrayIndexOutOfBoundsException runtime error.
int[] numbers = {10, 20, 30};
// Valid indexes: 0, 1, 2
System.out.println(numbers[0]); // OK
System.out.println(numbers[2]); // OK
// Invalid indexes
System.out.println(numbers[3]); // ArrayIndexOutOfBoundsException
System.out.println(numbers[-1]); // ArrayIndexOutOfBoundsException
Array Length Property
Mọi array đều có property length cho biết số lượng elements.
int[] numbers = {10, 20, 30, 40, 50};
System.out.println(numbers.length); // 5
// Lưu ý: length là property, KHÔNG phải method
// numbers.length() // COMPILE ERROR
// numbers.length // CORRECT
// Sử dụng length để tránh index out of bounds
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// Empty array
int[] empty = new int[0];
System.out.println(empty.length); // 0
- Array:
array.length(property, không có ngoặc) - String:
string.length()(method, có ngoặc) - ArrayList:
list.size()(method)
Duyệt Array
1. For loop truyền thống
int[] scores = {85, 90, 78, 92, 88};
// Duyệt từ đầu đến cuối
for (int i = 0; i < scores.length; i++) {
System.out.println("Score " + i + ": " + scores[i]);
}
// Duyệt ngược từ cuối về đầu
for (int i = scores.length - 1; i >= 0; i--) {
System.out.println(scores[i]);
}
// Duyệt chỉ phần tử chẵn
for (int i = 0; i < scores.length; i += 2) {
System.out.println(scores[i]);
}
2. Enhanced For Loop (For-each)
int[] scores = {85, 90, 78, 92, 88};
// For-each loop - Dễ đọc hơn
for (int score : scores) {
System.out.println(score);
}
// For-each với String array
String[] names = {"Alice", "Bob", "Charlie"};
for (String name : names) {
System.out.println("Hello, " + name);
}
For-each loop:
- Dễ đọc, ít lỗi
- Không cần index
- Không thể sửa đổi array
- Không duyệt ngược
For loop:
- Có index để sửa đổi array
- Linh hoạt hơn (duyệt ngược, skip elements)
- Phức tạp hơn một chút
So sánh For-each vs For loop
int[] numbers = {10, 20, 30, 40, 50};
// For-each: Không thể sửa đổi array
for (int num : numbers) {
num = num * 2; // Chỉ thay đổi biến local, KHÔNG thay đổi array
}
System.out.println(Arrays.toString(numbers)); // [10, 20, 30, 40, 50] - Không đổi
// For loop: Có thể sửa đổi array
for (int i = 0; i < numbers.length; i++) {
numbers[i] = numbers[i] * 2;
}
System.out.println(Arrays.toString(numbers)); // [20, 40, 60, 80, 100] - Đã đổi
Arrays Utility Class
java.util.Arrays cung cấp nhiều utility methods để làm việc với arrays.
1. toString() - In array
import java.util.Arrays;
int[] numbers = {10, 20, 30, 40, 50};
// Không dùng Arrays.toString()
System.out.println(numbers); // [I@15db9742 (hash code, không hữu ích)
// Dùng Arrays.toString()
System.out.println(Arrays.toString(numbers)); // [10, 20, 30, 40, 50]
2. sort() - Sắp xếp array
import java.util.Arrays;
// Sắp xếp số nguyên
int[] numbers = {50, 10, 40, 20, 30};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers)); // [10, 20, 30, 40, 50]
// Sắp xếp String (alphabetically)
String[] names = {"Charlie", "Alice", "Bob"};
Arrays.sort(names);
System.out.println(Arrays.toString(names)); // [Alice, Bob, Charlie]
// Sắp xếp một phần của array
int[] data = {5, 3, 8, 1, 9, 2};
Arrays.sort(data, 0, 3); // Chỉ sort index 0-2
System.out.println(Arrays.toString(data)); // [3, 5, 8, 1, 9, 2]
3. binarySearch() - Tìm kiếm nhị phân
Array PHẢI được sắp xếp trước khi dùng binarySearch().
import java.util.Arrays;
int[] numbers = {10, 20, 30, 40, 50};
Arrays.sort(numbers); // Đảm bảo đã sort
// Tìm thấy
int index = Arrays.binarySearch(numbers, 30);
System.out.println(index); // 2
// Không tìm thấy (trả về số âm)
index = Arrays.binarySearch(numbers, 25);
System.out.println(index); // -3 (insertion point = 2 + 1)
4. fill() - Điền giá trị
import java.util.Arrays;
int[] numbers = new int[5];
Arrays.fill(numbers, 100);
System.out.println(Arrays.toString(numbers)); // [100, 100, 100, 100, 100]
// Fill một phần
int[] data = {1, 2, 3, 4, 5};
Arrays.fill(data, 1, 4, 0); // Fill từ index 1-3 với 0
System.out.println(Arrays.toString(data)); // [1, 0, 0, 0, 5]
5. copyOf() và copyOfRange() - Copy array
import java.util.Arrays;
int[] original = {10, 20, 30, 40, 50};
// Copy toàn bộ array
int[] copy1 = Arrays.copyOf(original, original.length);
System.out.println(Arrays.toString(copy1)); // [10, 20, 30, 40, 50]
// Copy với length khác
int[] copy2 = Arrays.copyOf(original, 3); // [10, 20, 30]
int[] copy3 = Arrays.copyOf(original, 7); // [10, 20, 30, 40, 50, 0, 0]
// Copy range
int[] copy4 = Arrays.copyOfRange(original, 1, 4); // index 1-3
System.out.println(Arrays.toString(copy4)); // [20, 30, 40]
6. equals() - So sánh arrays
import java.util.Arrays;
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = {1, 2, 4};
// Dùng == so sánh reference, không phải nội dung
System.out.println(arr1 == arr2); // false
// Dùng Arrays.equals() so sánh nội dung
System.out.println(Arrays.equals(arr1, arr2)); // true
System.out.println(Arrays.equals(arr1, arr3)); // false
7. Các methods khác
import java.util.Arrays;
int[] numbers = {10, 20, 30, 40, 50};
// asList() - Chuyển array thành List (chỉ dùng với object arrays)
String[] names = {"Alice", "Bob", "Charlie"};
List<String> list = Arrays.asList(names);
// stream() - Tạo Stream từ array (Java 8+)
int sum = Arrays.stream(numbers).sum();
double average = Arrays.stream(numbers).average().getAsDouble();
// parallelSort() - Sắp xếp song song (hiệu quả với array lớn)
int[] bigArray = new int[1000000];
Arrays.parallelSort(bigArray);
Common Array Operations
1. Tìm min/max
public class ArrayOperations {
public static int findMin(int[] arr) {
if (arr == null || arr.length == 0) {
throw new IllegalArgumentException("Array is empty");
}
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}
public static int findMax(int[] arr) {
if (arr == null || arr.length == 0) {
throw new IllegalArgumentException("Array is empty");
}
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
public static void main(String[] args) {
int[] numbers = {45, 12, 78, 23, 90, 56};
System.out.println("Min: " + findMin(numbers)); // 12
System.out.println("Max: " + findMax(numbers)); // 90
}
}
2. Tính tổng và trung bình
public class ArrayStatistics {
public static int sum(int[] arr) {
int total = 0;
for (int num : arr) {
total += num;
}
return total;
}
public static double average(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return (double) sum(arr) / arr.length;
}
public static void main(String[] args) {
int[] scores = {85, 90, 78, 92, 88};
System.out.println("Sum: " + sum(scores)); // 433
System.out.println("Average: " + average(scores)); // 86.6
}
}
3. Đảo ngược array
public class ArrayReverser {
public static void reverse(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
// Swap elements
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
System.out.println("Original: " + Arrays.toString(numbers));
reverse(numbers);
System.out.println("Reversed: " + Arrays.toString(numbers));
// [5, 4, 3, 2, 1]
}
}
4. Tìm kiếm tuyến tính
public class LinearSearch {
public static int search(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i; // Trả về index
}
}
return -1; // Không tìm thấy
}
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 40, 50};
System.out.println(search(numbers, 30)); // 2
System.out.println(search(numbers, 25)); // -1
}
}
5. Remove duplicates (sắp xếp trước)
import java.util.Arrays;
public class DuplicateRemover {
public static int[] removeDuplicates(int[] arr) {
if (arr == null || arr.length == 0) {
return arr;
}
// Sắp xếp trước
Arrays.sort(arr);
// Đếm unique elements
int uniqueCount = 1;
for (int i = 1; i < arr.length; i++) {
if (arr[i] != arr[i - 1]) {
uniqueCount++;
}
}
// Tạo array mới với unique elements
int[] result = new int[uniqueCount];
result[0] = arr[0];
int index = 1;
for (int i = 1; i < arr.length; i++) {
if (arr[i] != arr[i - 1]) {
result[index++] = arr[i];
}
}
return result;
}
public static void main(String[] args) {
int[] numbers = {5, 2, 8, 2, 9, 5, 3, 8};
int[] unique = removeDuplicates(numbers);
System.out.println(Arrays.toString(unique)); // [2, 3, 5, 8, 9]
}
}
Array vs ArrayList
ArrayList là một collection class sẽ được học chi tiết ở module Collections Framework.
| Đặc điểm | Array | ArrayList |
|---|---|---|
| Size | Fixed (cố định) | Dynamic (tự động tăng) |
| Type | Primitives và Objects | Chỉ Objects (dùng wrapper) |
| Performance | Nhanh hơn một chút | Chậm hơn do overhead |
| Syntax | arr[i] | list.get(i) |
| Methods | Ít (dùng Arrays utility) | Nhiều (add, remove, contains...) |
| Length | arr.length | list.size() |
Ví dụ so sánh
import java.util.ArrayList;
public class ArrayVsArrayList {
public static void main(String[] args) {
// Array - Fixed size
int[] arr = new int[3];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
// arr[3] = 40; // ArrayIndexOutOfBoundsException
// ArrayList - Dynamic size
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
list.add(40); // OK - tự động tăng size
list.remove(1); // Xóa được
System.out.println(list); // [10, 30, 40]
}
}
Array Covariance và ArrayStoreException
Java arrays là covariant: String[] là subtype của Object[]. Điều này cho phép code compile OK nhưng fail lúc runtime:
// ✅ Compile OK: String[] IS-A Object[] (covariance)
Object[] objects = new String[5];
// ✅ Compile OK: "Hello" là String, String IS-A Object
objects[0] = "Hello";
// ✅ Compile OK nhưng ❌ RUNTIME ERROR!
objects[1] = 123; // ArrayStoreException: Integer không phải String!
Tại sao lỗi? Biến objects có kiểu Object[], nên compiler cho phép gán Integer. Nhưng array thực tế là String[] → JVM kiểm tra lúc runtime và ném ArrayStoreException.
So sánh với Generics (invariant)
// Arrays: covariant → không type-safe
Object[] arr = new String[5]; // ✅ Compile OK
arr[0] = 123; // ❌ Runtime: ArrayStoreException
// Generics: invariant → type-safe
// List<Object> list = new ArrayList<String>(); // ❌ COMPILE ERROR
// Compiler chặn ngay, không để lọt đến runtime
Đây là lý do Generics (List, Set, Map) an toàn hơn arrays khi làm việc với kiểu dữ liệu.
System.arraycopy() vs Arrays.copyOf()
System.arraycopy() | Arrays.copyOf() | |
|---|---|---|
| Trả về | void (copy vào array có sẵn) | Array mới |
| Linh hoạt | Copy từ vị trí bất kỳ sang vị trí bất kỳ | Luôn copy từ đầu |
| Performance | Nhanh hơn (native method) | Gọi System.arraycopy() bên trong |
| Khi nào dùng | Merge arrays, copy vào giữa array | Tạo bản sao đơn giản |
int[] src = {1, 2, 3, 4, 5};
// Arrays.copyOf: tạo bản sao (đơn giản, gọn)
int[] copy = Arrays.copyOf(src, src.length); // [1, 2, 3, 4, 5]
// System.arraycopy: copy vào vị trí cụ thể (linh hoạt)
int[] dest = new int[10];
System.arraycopy(src, 0, dest, 3, src.length);
// dest = [0, 0, 0, 1, 2, 3, 4, 5, 0, 0]
Arrays.mismatch() (Java 9+)
Tìm vị trí khác nhau đầu tiên giữa 2 arrays. Trả về -1 nếu giống hoàn toàn.
int[] a = {1, 2, 3, 4, 5};
int[] b = {1, 2, 9, 4, 5};
int[] c = {1, 2, 3, 4, 5};
System.out.println(Arrays.mismatch(a, b)); // 2 — khác nhau tại index 2
System.out.println(Arrays.mismatch(a, c)); // -1 — giống hoàn toàn
// So sánh 1 phần
int[] d = {1, 2, 3};
System.out.println(Arrays.mismatch(a, d)); // 3 — d hết phần tử tại index 3
Arrays.equals(a, b)→ chỉ biết có giống hay khôngArrays.mismatch(a, b)→ biết khác nhau ở đâu (hữu ích khi debug)
Bài tập thực hành
Bài 1: Second Largest Element
Tìm số lớn thứ hai trong array.
public class SecondLargest {
public static int findSecondLargest(int[] arr) {
// TODO: Implement this
// Hint: Tìm largest trước, rồi tìm số lớn nhất < largest
return 0;
}
public static void main(String[] args) {
int[] numbers = {12, 35, 1, 10, 34, 1};
System.out.println(findSecondLargest(numbers)); // 34
}
}
Bài 2: Rotate Array
Xoay array sang phải k vị trí.
import java.util.Arrays;
public class ArrayRotation {
public static void rotate(int[] arr, int k) {
// TODO: Implement this
// Example: [1,2,3,4,5], k=2 -> [4,5,1,2,3]
}
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
rotate(numbers, 2);
System.out.println(Arrays.toString(numbers)); // [4, 5, 1, 2, 3]
}
}
Bài 3: Merge Two Sorted Arrays
Merge 2 sorted arrays thành 1 sorted array.
import java.util.Arrays;
public class ArrayMerger {
public static int[] merge(int[] arr1, int[] arr2) {
// TODO: Implement this
// arr1 = [1, 3, 5], arr2 = [2, 4, 6]
// result = [1, 2, 3, 4, 5, 6]
return new int[0];
}
public static void main(String[] args) {
int[] arr1 = {1, 3, 5, 7};
int[] arr2 = {2, 4, 6, 8};
int[] merged = merge(arr1, arr2);
System.out.println(Arrays.toString(merged));
// [1, 2, 3, 4, 5, 6, 7, 8]
}
}
Bài 4: Find Pairs with Sum
Tìm tất cả các cặp số có tổng bằng target.
public class PairSum {
public static void findPairs(int[] arr, int target) {
// TODO: Implement this
// arr = [1, 5, 7, -1, 5], target = 6
// Output: (1, 5), (7, -1), (1, 5)
}
public static void main(String[] args) {
int[] numbers = {1, 5, 7, -1, 5};
findPairs(numbers, 6);
}
}
Bài 5: Move Zeros to End
Di chuyển tất cả số 0 về cuối array, giữ nguyên thứ tự các số khác 0.
import java.util.Arrays;
public class MoveZeros {
public static void moveZerosToEnd(int[] arr) {
// TODO: Implement this
// [0, 1, 0, 3, 12] -> [1, 3, 12, 0, 0]
}
public static void main(String[] args) {
int[] numbers = {0, 1, 0, 3, 12};
moveZerosToEnd(numbers);
System.out.println(Arrays.toString(numbers));
// [1, 3, 12, 0, 0]
}
}
Tổng kết
- Array là fixed-size container chứa elements cùng kiểu
- Index bắt đầu từ 0 và kết thúc ở length - 1
- Truy cập ngoài phạm vi gây ArrayIndexOutOfBoundsException
- length là property (không có ngoặc), cho biết số elements
- For-each loop dễ đọc nhưng không thể sửa array
- Arrays utility class cung cấp nhiều methods hữu ích
- Array phải được sort trước khi dùng binarySearch()
- Arrays.equals() để so sánh nội dung, không dùng ==
- ArrayList linh hoạt hơn nhưng chỉ dùng cho objects