Chuyển tới nội dung chính

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.

Mục tiêu bài học

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ì?

Định nghĩa

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ểmMô tả
Fixed sizeKích thước cố định, không thể thay đổi sau khi tạo
Same typeTất cả elements phải cùng kiểu dữ liệu
Index-basedTruy cập elements qua index (bắt đầu từ 0)
ObjectArray là object, không phải primitive
Contiguous memoryElements đượ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;
Convention

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ệuGiá trị mặc định
byte, short, int, long0
float, double0.0
booleanfalse
char'\u0000' (null character)
Object referencesnull
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

Lỗi phổ biến

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
length vs length()
  • 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 vs For loop

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

Yêu cầu

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

Preview

ArrayList là một collection class sẽ được học chi tiết ở module Collections Framework.

Đặc điểmArrayArrayList
SizeFixed (cố định)Dynamic (tự động tăng)
TypePrimitives và ObjectsChỉ Objects (dùng wrapper)
PerformanceNhanh hơn một chútChậm hơn do overhead
Syntaxarr[i]list.get(i)
MethodsÍt (dùng Arrays utility)Nhiều (add, remove, contains...)
Lengtharr.lengthlist.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

OCP Trap — Array Covariance

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ạtCopy từ vị trí bất kỳ sang vị trí bất kỳLuôn copy từ đầu
PerformanceNhanh hơn (native method)Gọi System.arraycopy() bên trong
Khi nào dùngMerge arrays, copy vào giữa arrayTạ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
Kết hợp với equals
  • Arrays.equals(a, b) → chỉ biết có giống hay không
  • Arrays.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

Những điểm quan trọng cần nhớ
  1. Array là fixed-size container chứa elements cùng kiểu
  2. Index bắt đầu từ 0 và kết thúc ở length - 1
  3. Truy cập ngoài phạm vi gây ArrayIndexOutOfBoundsException
  4. length là property (không có ngoặc), cho biết số elements
  5. For-each loop dễ đọc nhưng không thể sửa array
  6. Arrays utility class cung cấp nhiều methods hữu ích
  7. Array phải được sort trước khi dùng binarySearch()
  8. Arrays.equals() để so sánh nội dung, không dùng ==
  9. ArrayList linh hoạt hơn nhưng chỉ dùng cho objects

Đọc thêm