Cơ bản về mảng

Mảng là một cấu trúc cố định dùng để lưu trữ một chuỗi tuần tự c&aaacute;c giá trị có cùng kiểu dữ liệu. Hay  nói cách khác, mảng là một cấu trúc được đánh chỉ mục dùng để lưu trữ các giá trị khác nhau có cùng kiểu dữ liệu.

Các giá trị được lưu trữ trong mảng được gọi là các phần tử (element). Để truy cập các phần tử của mảng các bạn sử dụng chỉ mục chỉ vị trí của phần tử đó trong mảng. Chỉ mục là một số nguyên cho biết vị trí của một phần tử nào đó trong một cấu trúc dữ liệu, trong trường hợp này là cấu trúc mảng.

Chỉ mục trong Java được đánh số bắt đầu từ 0 (phần tử thứ 0, phần tử thứ 1,…)

Kiến tạo và duyệt mảng

Giả sử bạn muốn lưu trữ các số đo nhiệt độ khác nhau. Bạn có thể lưu trữ các giá trị nhiệt độ đó trong một loạt các biến tương tự như sau:

double temperature1;
double temperature2;
double temperature3;

Đây không phải là một giải pháp tồi nếu bạn chỉ có 3 nhiệt độ, nhưng nếu bạn cần lưu trữ 3000 giá trị nhiệt độ khác nhau thì đây không phải là một giải pháp tốt. Trong trường hợp này bạn cần có một cấu trúc tốt hơn để lưu trữ các giá trị nhiệt độ, và bạn có thể lưu trữ các giá trị này trong một mảng.

Khi bạn sử dụng một mảng, đầu tiên bạn cần khai báo một biến mảng do đó bạn cần phải biết kiểu dữ liệu sử dụng cho mảng đó. Kiểu dữ liệu của mảng phụ thuộc vào kiểu dữ liệu của các phần tử lưu trữ trong mảng.

Biến mảng có thể khai báo theo một trong hai cách sau:

            Kiểu_dữ_liệu    tên_biến_mảng [ ];

hoặc:

            Kiểu_dữ_liêu[]    tên_biến_mảng;

Ví dụ:

            double temperature[];

hoặc:

            double[]  temperature;

Trong các khai báo trên thì kích thước của mảng chưa được xác định, việc khai báo biến mảng chưa tạo ra cấu trúc mà chỉ mới khai báo cấu trúc để tham chiếu. Việc khai báo như trên chưa đủ để có thể sử dụng biến mảng đó mà còn phải cấp phát bộ nhớ cho biến mảng.

Để cấp phát bộ nhớ cho cấu trúc mảng trong Java sử dụng từ khóa new với cú pháp như sau:

            tên_biến_mảng = new <Kiểu_dữ_liệu>[kích_thước_mảng];

Ví dụ: để lưu 3 giá trị nhiệt độ khác nhau như trên ta có thể cấp phát bộ nhớ cho mảng này như sau:

temperature = new double[3];

Khi thực thi dòng lệnh trên, Java sẽ tạo ra một mảng chứa 3 phần tử có giá trị kiểu double và biến temperatures sẽ trỏ vào mảng n&agraagrave;y như sau:

java co ban

Từ hình trên các bạn thấy rằng, biến mảng thực chất không phải là một mảng, thực chất nó chỉ lưu tham chiếu đến mảng đó trong bộ nhớ (lưu địa chỉ phẩn tử đầu tiên của mảng trong bộ nhớ). Chỉ mục của mảng nằm trong cặp dấu ngoặc vuông. Khi cần truy cập tới một phần tử xác định trong mảng cần sử dụng kết hợp tên biến mảng và vị trí chỉ mục xác định trong mảng ( [ 0 ], [ 1 ], hoặc [ 2 ] ). Do đó, đối với mảng lưu trữ các giá trị nhiệt độ khác nhau ở trên có 3 phần tử là temperature[0], temperature[1], và temperature[2].

Trong hình mô tả mảng temperature ở trên, mỗi phần tử của mảng này đều có giá trị là 0.0. Các giá trị này được gán một cách tự động khi mảng được khởi tạo với một giá trị mặc định tùy theo kiểu dữ liệu.

bài tập java cơ bản

Cú pháp gộp chung hai bước vừa khai báo vừa cấp phát bộ nhớ cho mảng như sau:

<Kiểu_dữ_liệu>[]   tên_biến_mảng = new <Kiểu_dữ_liệu>[kích_thước_mảng];

Ví dụ:

double[] temperature = new double[3];

Bạn có thể sử dụng nhiều kiểu dữ liệu khác nhau, tuy nhiên kiểu dữ liệu ở phía bên trái và kiểu dữ liệu ở phía bên phải phải giống nhau. Ví dụ:

int[] iNumbers = new int[15];  //mảng gồm 15 phần tử kiểu số nguyên
char[] letters = new char[10];  // mảng gồm 10 phần tử kiểu ký tự
String[] strNames = new String[20];  //mảng gồm 20 phần tử kiểu chuỗi

Bạn có thể lưu trữ các giá trị nhiệt độ vào mảng temperature ở trên như sau:

temperature [ 0 ] = 74.3;
temperature [ 1 ] = 68.4;
temperature [ 2 ] = 70.3;

Đoạn mã trên thay đổi các giá trị đã lưu trong mảng thành các giá trị mới được minh họa như ở hìigrave;nh sau:

JAVA_CB_BAI5_3

Để lấy kích thước của mảng sử dụng cú pháp: Tên_biến_mảng.length;

Tuy nhiên, trong trường hợp chúng ta cần lưu trữ khoảng 150 giá trị biểu diễn nhiệt độ trong mảng temperature thì việc nhập từng giá trị như ở trên là không hợp lý. Thay vào đó, chúng ta sử dụng một vòng lặp để nhập các giá trị này như sau:

import java.util.Scanner;
Scanner input = new Scanner(System.in);
for(int i = 0; i < temperature.length ; i++){
	temperature[i] = input.nextDouble();
}

Vì vậy khuôn mẫu chung cho việc tương tác với các phần tử của mảng từ phần tử đầu cho đến phần tử cuối của mảng nên sử dụng như sau:

for(int i = 0; i < tên_biến_mảng.length ; i++){
//	Tương tác với phần tử tên_biến_mảng[i];
}

Truy cập một mảng

Như đã trình bày ở phần trên, chúng ta có thể truy cập vào các phần tử của mảng theo cú pháp sau:

Tên_biến_mảng[biểu_thức_nguyên]

Lưu ý rằng cú pháp trên cho thấy chỉ mục của phần tử có thể là một biểu thức trả về một giá trị nguyên. Để hiểu thêm về điều này chúng ta sẽ lưu trữ năm giá trị nguyên khác nhau trong một mảng số nguyên. Giả sử chúng ta có một mảng số nguyên có 5 phần tử và 5 phần tử này được điền các giá trị số nguyên lẻ như sau:

int[]  list = new int[5];
for(int i=0; i<list.length; i++){
	list[i] = 2*i + 1;
}

Dòng đầu tiên khai báaacute;o biến mảng list kiểu số nguyên kích thước 5. Các phần tử của mảng này sẽ được khởi tạo nhận giá trị 0 như sau:

JAVA_CB_BAI5_4

Sau đó vòng lặp for ở trên sẽ điền các giá trị vào mảng list là các số nguyên lẻ như sau:

JAVA_CB_BAI5_5

Giả sử chúng ta muốn hiển thị giá trị của phần tử đầu tiên, phần tử ở giữa và phần tử cuối cùng trong mảng list, như hình ở trên ta thấy các phần tử này nằm ở các vị trí chỉ mục 0, 2, 4. Do đó, để truy xuất các phần tử này chúng ta có thể viết code như sau:

//Chỉ thực hiện đối với mảng 5 phần tử ở trên
System.out.println("Phần tử đầu tiên: " + list[0]);
System.out.println("Phần tử ở giữa: " + list[2]);
System.out.println("Phần tử cuối cùng: " + list[4]);

Đoạn code trên chỉ thực hiện đúng với mảng gồm có 5 phần tử, vậy nếu chúng ta thay đổi số lượng các phần tử lưu trữ trong mảng thì điều gì sẽ xảy ra ? Giả sử chúng ta thay đổi số lượng phần tử của mảng trên thành 15 thì đoạn mã trên sẽ hiển thị sai các giá trị yêu cầu. Vì vậy chúng ta cần sửa đoạn mã trên để có thể sử dụng list.length vào trong vòng lặp for.

Phần tử đầu tiên của mảng luôn có chỉ mục là 0, do đó dòng lệnh đầu tiên không cần phải chỉnh sửa. Lúc đầu bạn có thể nghĩ rằng có thể chỉnh sửa dòng lệnh thứ ba ở trên bằng cách thay giá trị 4 bằng list.length như sau:

// Dòng lệnh này không thực hiện
System.out.println("Phần tử cuối cùng: " + list[list.length]);

Tuy  nhiên, dòng lệnh trên không thực hiện, bởi vì Java đánh chỉ mục bắt đầu từ 0. Trong ví dụ trên giá trị của phần tử cuối được lưu ở vị trí chỉ mục 4 chứ không phải là 5 trong khi list.length trả về giá trị 5. Chính vì vậy, giá trị của phần tử cuối cùng sẽ ở vị trí chỉ mục là list.length – 1. Vậy chúng ta sẽ sử dụng biểu thức này để chỉnh sửa câu lệnh trên như sau:

// Dòng lệnh này thực hiện đúng
System.out.println("Phần tử cuối cùng: " + list[list.length]);

Một cách đơn giản để truy xuất phần tử giữa của mảng thiết lập vị trí chỉ mục của phần tử này nhận giá trị độ dài của mảng chia 2 như sau:

// Dòng lệnh này có đúng không ?
System.out.println("Phần tử ở giữa: " + list[list.length / 2]);

Khi mảng có kích thước là 5, biểu thức này trả về kết quả là 2, do đó dòng lệnh trên in ra giá trị đúng. Nhưng điều gì xảy ra khi mảng có kích thước là 10 ? Lúc này biểu thức trả về kết quả là 5 và dòng lệnh sẽ in ra list[5]. Khi số lượng phần tử là chẵn thì chúng ta có tới hai giá trị nằm ở giữa của mảng, trong trường hợp đang xét là list[4] và list[5]. Do đó, trong các xử lý thông thường chúng ta thường in ra cả hai giá trị này.

Nếu chúng ta muốn đoạn mã của mình chỉ xuất ra phần tử chính giữa đầu tiên trong hai phần tử ở giữa, chúng ta lấy độ dài của mảng – 1 rồi chia cho 2. Lúc này các lệnh sẽ đúng trong cả hai trường hợp số phần tử của mảng lẻ và chẵn. Đoạn mã trên được chỉnh sửa lại như sau:

//Chỉ thực hiện đối với mảng 5 phần tử ở trên
System.out.println("Phần tử đầu tiên: " + list[0]);
System.out.println("Phần tử ở giữa: " + list[(list.length - 1) / 2]);
System.out.println("Phần tử cuối cùng: " + list[list.length - 1]);

Đôi khi trong quá trình lập trình không kiểm soát hết mã, và bạn có thể truy xuất các phần tử trong mảng với vị trí chỉ mục không hợp lệ, lúc này Java sẽ thông báo cho bạn một lỗi ngoại lệ (exception). Ví dụ, đối với mảng gồm có 5 phần tử thì các giá trị chỉ mục hợp lệ là các số nguyên nằm trong đoạn từ 0 đến 4. Các giá trị chỉ mục không thuộc đoạn này đều được thông báo là vượt ra khỏi giới hạn của mảng “out of bounds”.

Chương trình Java tính nhiệt độ trung bình trong n ngày với số ngày được nhập từ bàn phím như sau:

package ANM;

import java.util.Scanner;

/**
 * Đọc vào nhiệt độ của n ngày và xuất ra nhiệt độ trung bình

 */
public class TemperatureAverageOne {
    public static void main(String[] args) {JAVA_CB_BAI5_6
        Scanner reader = new Scanner(System.in);
        
        System.out.print("Nhập số ngày: ");
        int numDays = reader.nextInt();
        int sum = 0;
        System.out.println("");
        
        for (int i = 1; i <= numDays; i++) {
            System.out.print("Nhiệt độ ngày thứ " + i + " = ");
            sum += reader.nextInt();
            System.out.println("");
        }
        
        double averageTemperate = (double) sum / numDays;
        System.out.println("Nhiệt độ trung bình là " + averageTemperate);
    }
}

Chương trình thực hiện chức năng tương tự chương trình trên nhưng sử dụng mảng để lưu nhiệt độ của các ngày do người dùng  nhập vào, hiển thị nhiệt độ trung bình và số lượng ngày có nhiệt độ trên nhiệt độ trung bình.

package anm;

import java.util.Scanner;

/**
 * Đọc vào nhiệt độ của n ngày và xuất ra giá trị nhiệt độ trung bình
 * và số lượng ngày có nhiệt độ lớn hơn nhiệt độ trung bình
 
 */
public class TemperatureAverageTwo {
    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        
        System.out.print("Nhập số ngày: ");
        int numDays = reader.nextInt();
        int[] temperatures = new int[numDays];
        System.out.println("");
        
        //Lưu trữ nhiệt độ vào mảng và tính nhiệt độ trung bình
        int sum = 0;   // Lưu tổng nhiệt độ của tất cả các ngày
        
        for (int i = 0; i < numDays; i++) {
            System.out.print("Nhiệt độ ngày thứ " + i + " = ");
            temperatures[i] = reader.nextInt();
            sum += temperatures[i];
            System.out.println("");
        }
        
        double averageTemperature = (double) sum/numDays;
        
        //Đếm số ngày có nhiệt độ trên nhiệt độ trung bình
        int count = 0;
        
        for (int i = 0; i < temperatures.length; i++) {
            if (temperatures[i]>averageTemperature) {
                count++;
            }
        }
        
        //In kết quả
        System.out.println("Nhiệt độ trung bình: " + averageTemperature);
        System.out.println(count + " ngày có nhiệt độ trên nhiệt độ trung bình.");
    }
}

Mảng và phương thức

Bạn sẽ nhận ra rằng khi truyền một mảng làm đối số cho một phương thức thì phương thức đó có khả năng thay đổi nội dung của mảng. Điều quan trọng lúc này là chúng ta cần hiểu phương thức có thể thay đổi nội dung của mảng được truyền cho nó dưới dạng đối số.

Chúng ta cùng đi vào một ví dụ cụ thể để hiểu rõ hơn cách sử dụng các mảng như là các tham số và các giá trị trả về của một phương thức, chúng ta đã thấy các mã sau đây xây dựng một mảng bao gồm các số lẻ và tăng các phần tử của mảng:

int[] list =new int[5];
        
for (int i = 0; i < list.length; i++) {
    list[i] = 2 * i + 1;            
}
        
for (int i = 0; i < list.length; i++) {
    list[i]++;            
}

Xem điều gì xảy khi khi chúng ta đưa vòng lặp tăng giá trị của mỗi phần tử mỗi đơn vị vào trong một phương thức và nhận một mảng dưới dạng một tham số. Chúng ta sẽ đặt tên mảng làm tham số cho phương thức là data thay vì sử dụng list để dễ phân biệt mảng này với mảng gốc. Lúc này chúng ta viết phương thức như sau:

public static void incrementAll(int[] data){
        for (int i = 0; i < data.length; i++) {
            data[i]++;
        }
}

Bạn có thể nghĩ rằng phương thức trên không có tác dụng gì, hoặc chúng ta phải trả về một mảng đã có sự thay đổi. Nhưng khi chúng ta sử dụng một mảng làm tham số thì phương thức này thực sự làm việc. Các bạn có thể thay thế vòng lặp làm gia tăng giá trị của các phần tử trong mảng ở ví dụ trên bằng một lời gọi phương thức như sau:

int[] list =new int[5];
        
for (int i = 0; i < list.length; i++) {
      list[i] = 2 * i + 1;            
}
incrementAll(list);

Tương tự như vậy, chúng ta thay thế vòng lặp điền các số lẻ vào cho các phần tử của mảng bởi một phương thức:

public static void fillWithOdds(int[] data){
        for (int i = 0; i < data.length; i++) {
            data[i] = 2 * i + 1;
        }
}

Lúc này đoạn mã đầu tiên có thể thay lại như sau:

int[] list =new int[5];
incrementAll(list);
fillWithOdds(list);

Giống như phương thức incrementAll, phương thức này cũng sẽ thay đổi mảng mặc dù nó không có giá trị trả về. Nhưng đây không phải là giải pháp tốt nhất để sử dụng trong trường hợp này. Như bạn đã thấy, phương thức này cần bạn tạo ra một mảng và truyền mảng đó cho nó dưới dạng một đối số, vậy tại sao phương thức fillWithOdds không tự khởi tạo một mảng, lúc này lời gọi hàm sẽ trở nên đơn giản hơn.

Vì vậy đoạn code:

int[] list = new int[5];

sẽ được thay thế bởi đoạn code:

int[] list = fillWithOdds();

hoặc linh động về số phần tử của mảng như sau:

int[] list = fillWithOdds(5);

Và chúng ta viết lại mã cho phương thức này như sau:

public static int[] fillWithOdds(int size){
        int[] data = new int[size];        
        for (int i = 0; i < data.length; i++) {
            data[i] = 2 * i + 1;
        }        
        return data;
}

Dưới đây là một chương trình hoàn chỉnh minh họa cách truyền mảng dưới dạng tham số:

package ANM;

/**
 * Chương trình đơn giản truyền mảng dưới dạng các đối số

 */
public class IncrementOdds {
    //Trả về một mảng số nguyên lẻ
    public static int[] buildOddArray(int size){
        int[] data = new int[size];
        
        for (int i = 0; i < data.length; i++) {
            data[i] = 2 * i + 1;
        }
        
        return data;
    }%MINIFYHTML921184426d9a5170a2758e40231424bc16% %MINIFYHTML921184426d9a5170a2758e40231424bc17%//Tăng mỗi phần tử của mảng một đơn vị public static void incrementAll(int[] data){ for (int i = 0; i < data.length; i++) { data[i]++; } } public static void main(String[] args) { int[] list = buildOddArray(5); incrementAll(list); for (int i = 0; i < list.length; i++) { System.out.print(list[i] + " "); } System.out.println(""); } }