Mảng và chuỗi trong Java
- 01-10-2023
- Toanngo92
- 0 Comments
Mục lục
Giới thiệu mảng
Xét tình huống người dùng muốn lưu trữ điểm của 10 học sinh. Với mục đích này, người dùng có thể tạo ra mười biến khác nhau có kiểu số nguyên và lưu trữ các dấu trong đó. Nếu người dùng muốn lưu trữ điểm của hàng trăm, hàng nghìn học sinh thì sao? Trong trường hợp như vậy, người ta sẽ cần tạo càng nhiều biến càng tốt. Đây có thể là một công việc rất khó khăn, tẻ nhạt và tốn thời gian. Ở đây, cần phải có một tính năng cho phép lưu trữ tất cả các nhãn hiệu ở một vị trí và truy cập nó bằng các tên biến tương tự. Mảng, trong Java, là một tính năng cho phép lưu trữ nhiều giá trị có kiểu giống nhau trong cùng một biến.
Mảng là một kho lưu trữ dữ liệu đặc biệt có thể chứa một số giá trị cố định của một loại trong các vị trí bộ nhớ liền kề. Nó được thực hiện như các đối tượng. Kích thước của một mảng phụ thuộc vào số lượng giá trị mà nó có thể lưu trữ và được chỉ định khi mảng được tạo. Sau khi tạo một mảng, kích thước hoặc độ dài của mảng sẽ cố định.
Hình trên hiển thị một mảng gồm mười số nguyên lưu trữ các giá trị như 20, 100, 40, v.v. Mỗi giá trị trong mảng được gọi là một phần tử của mảng. Các số từ 0 đến 9 biểu thị chỉ mục hoặc chỉ số dưới của các phần tử trong mảng. Độ dài hoặc kích thước của mảng là 10. Chỉ mục đầu tiên bắt đầu bằng 0. Vì chỉ mục bắt đầu bằng 0 nên chỉ mục của phần tử cuối cùng luôn có chỉ mụng bằng length – 1. Do đó, phần tử cuối cùng, tức là phần tử thứ mười trong mảng đã cho có giá trị chỉ mục là 9. Mỗi phần tử của mảng có thể được truy cập bằng cách sử dụng chỉ số dưới hoặc chỉ mục. Mảng có thể được tạo từ các kiểu dữ liệu nguyên thủy như int, float, boolean cũng như từ kiểu tham chiếu như đối tượng. Các phần tử mảng được truy cập bằng một tên duy nhất nhưng có các chỉ số dưới khác nhau. Các giá trị của mảng được lưu trữ tại các vị trí liền kề trong bộ nhớ. Điều này gây ra ít chi phí hơn cho hệ thống trong khi tìm kiếm các giá trị. Việc sử dụng mảng có những lợi ích sau:
- Mảng là cách tốt nhất để vận hành trên nhiều phần tử dữ liệu cùng loại cùng một lúc.
- Mảng tận dụng tối ưu tài nguyên bộ nhớ so với các biến.
- Bộ nhớ chỉ được gán cho một mảng tại thời điểm mảng đó thực sự được sử dụng. Do đó, mảng không bị tiêu tốn bộ nhớ ngay từ khi nó được khai báo.
Mảng trong Java có hai loại sau:
- Mảng một chiều
- Mảng đa chiều
Khai báo, khởi tạo và khởi tạo giá trị mảng một chiều (declare, Instantiation, Initialization)
Ở đây , khái niệm instantiation và initialization đều dịch là khởi tạo, tuy nhiên có một chút khác biệt, tham khảo thêm tại link này:
Mảng một chiều chỉ có một chiều và được biểu diễn trực quan dưới dạng có một cột duy nhất với nhiều hàng dữ liệu. Mỗi phần tử được truy cập bằng cách sử dụng tên mảng và chỉ mục nơi phần tử đó được đặt.
Hình trên cho thấy một mảng một chiều có tên là marks. Kích thước hoặc độ dài của mảng được chỉ định là 4 trong ngoặc vuông. Điều này có nghĩa là mảng mark có thể chứa tối đa bốn giá trị. Mỗi phần tử của mảng được truy cập bằng cách sử dụng tên mảng và chỉ mục. Ví dụ: dấu [0] biểu thị phần tử đầu tiên trong mảng, Tương tự, dấu [3], tức là dấu [Length-1] biểu thị phần tử cuối cùng của mảng, Lưu ý rằng không có phần tử nào có chỉ số 4. Do đó , việc cố gắng viết dấu [4] sẽ đưa ra một ngoại lệ. Ngoại lệ là một sự kiện bất thường xảy ra trong quá trình thực hiện chương trình và làm gián đoạn luồng lệnh thông thường. Việc tạo mảng bao gồm các nhiệm vụ sau:
- Khai báo mảng
- Khởi tạo mảng (instantiation)
- Khởi tạo giá trị mảng (initialization)
Khai báo mảng
Khai báo một mảng cũng tương tự như khai báo một biến khác. Khai báo một mảng sẽ thông báo cho trình biên dịch rằng biến sẽ chứa một mảng có kiểu dữ liệu đã chỉ định. Nó không tạo ra một mảng. Cú pháp khai báo mảng một chiều như sau:
datatype[] <arrayname>;
Trong đó:
- datatype: Cho biết loại phần tử sẽ được lưu trữ trong mảng.
- []: Cho biết biến là một mảng.
- arrayname: Cho biết tên mà các phần tử của mảng sẽ được truy cập.
Ví dụ:
int[] marks;
byte[] byteArray;
float[] floatArray;
boolean[] booleanArray;
char[] charArray;
String[] stringArray;
Khởi tạo mảng
Vì mảng là một đối tượng nên bộ nhớ chỉ được cấp phát khi được khởi tạo. Cú pháp để khởi tạo một mảng như sau:
Cú pháp:
datatype[] arrayname = new datatype[size];
Trong đó:
- new: Cấp phát vùng nhớ cho mảng.
- size: Cho biết số phần tử có thể được lưu trữ trong mảng.
Ví dụ:
int[] marks = new int[4];
Tương tự, các mảng thuộc loại khác cũng có thể được khởi tạo theo yêu cầu.
Khởi tạo giá trị mảng
Vì mảng là một đối tượng có thể lưu trữ nhiều giá trị nên mảng phải được khởi tạo với các giá trị được lưu trữ trong đó. Mảng có thể được khởi tạo theo hai cách sau:
Trong quá trình tạo:
Để khởi tạo mảng một chiều trong quá trình tạo, người ta phải chỉ định các giá trị sẽ được lưu trữ khi tạo mảng như sau:
int[] mark = {65, 47, 75, 50};
Lưu ý rằng khi khởi tạo một mảng trong quá trình tạo, không cần phải có từ khóa new hoặc kích thước mảng. Điều này là do tất cả các phần tử cần lưu trữ đã được chỉ định và theo đó bộ nhớ sẽ được phân bổ tự động dựa trên số lượng phần tử. Điều này còn được gọi là khai báo có khởi tạo.
Sau quá trình tạo:
Mảng một chiều cũng có thể được khởi tạo sau khi tạo và khởi tạo. Trong trường hợp này, các phần tử riêng lẻ của mảng phải được khởi tạo với các giá trị thích hợp.
Ví dụ:
int[] marks = new int[4];
marks[0] = 65;
marks[1] = 47;
marks[2] = 75;
marks[3] = 50;
Ngoài ra, chúng ta có thể tạo mảng theo cách sau:
int[] marks; // Declaration of an integer array
marks = new int[4]; // Instantiation: Creating an integer array of size 4
marks[0] = 65; // Initialization: Assigning a value of 65 to the first element of the array
Ví dụ cụ thể khởi tạo mảng đơn chiều:
package com;
public class OneDimension {
// Declare a single-dimensional array named marks
int marks[]; // line 6
// Instantiates and initializes a single-dimensional array
// @return void
public void storeMarks() {
// Instantiate the array
marks = new int[4]; // line 12
System.out.println("Storing Marks. Please wait...");
// Initialize array elements
marks[0] = 65; // line 16
marks[1] = 47;
marks[2] = 757;
marks[3] = 50;
}
// Displays marks from a single-dimensional array
// @return void
public void displayMarks() {
System.out.println("Marks are:");
// Display the marks
System.out.println(marks[0]); // line 27
System.out.println(marks[1]);
System.out.println(marks[2]);
System.out.println(marks[3]);
}
public static void main(String[] args) {
// Instantiate class OneDimension
OneDimension oneDimenObj = new OneDimension(); // line 36
// Invoke the storeMarks() method
oneDimenObj.storeMarks(); // line 39
// Invoke the displayMarks() method
oneDimenObj.displayMarks(); // line 42
}
}
Lớp “OneDimension” bao gồm một mảng có tên là “marks” được khai báo ở dòng 1. Để tạo và khởi tạo các phần tử trong mảng, phương thức “storeMarks()” được định nghĩa. Mảng được khởi tạo bằng cách sử dụng từ khóa “new” ở dòng 2.
Các giá trị cho các phần tử của mảng được gán bằng cách sử dụng tên mảng sau đó là chỉ số của phần tử, như “marks[0]“. Tương tự, để hiển thị các phần tử của mảng, phương thức “displayMarks()” được định nghĩa, xuất giá trị được lưu trữ trong mỗi phần tử của mảng “marks“.
Một đối tượng có tên “oneDimendObj” của lớp “OneDimension” được tạo ra ở dòng 4. Đối tượng này được sử dụng để gọi các phương thức “storeMarks()” và “displayMarks()“,
Output:
Khai báo, Khởi tạo và Khởi tạo giá trị mảng đa chiều
Người dùng có thể tạo một mảng đa chiều trong Java bằng cách sử dụng hai hoặc nhiều cặp dấu ngoặc vuông, biểu thị số chiều, như int[][] marks. Mỗi phần tử trong một mảng đa chiều phải được truy cập bằng cách sử dụng số chỉ mục tương ứng.
Trong Java, một mảng đa chiều là một mảng mà các phần tử của nó cũng là mảng, cho phép các hàng có độ dài khác nhau.
Cú pháp để khai báo và khởi tạo một mảng đa chiều như sau:
datatype[][] arrayName = new datatype[rowsize][colsize];
Trong đó:
- datatype chỉ ra loại dữ liệu của các phần tử sẽ được lưu trữ trong mảng.
- rowsize và colsize chỉ ra số hàng và số cột trong mảng.
- new là một từ khóa được sử dụng để cấp phát bộ nhớ cho các phần tử của mảng.
int[][] marks = new int[4][2];
Trong ví dụ này, mảng có tên “marks” bao gồm bốn hàng và hai cột. Tương tự, mảng của các loại khác cũng có thể được khởi tạo theo nhu cầu.
Mảng đa chiều cũng có thể khởi tạo theo 2 cách
Trong khi khởi tạo
Một mảng đa chiều có thể được khởi tạo trong quá trình tạo bằng cách chỉ định các giá trị cần lưu trữ, như sau:
int[][] marks = {
{23, 65},
{42, 47},
{60, 75},
{75, 50}
};
Giải pháp này khởi tạo mảng “marks” với các giá trị đã cho.
Khi khởi tạo một mảng trong quá trình tạo, các phần tử trong các hàng được chỉ định bên trong dấu ngoặc nhọn và được phân tách bằng dấu phẩy. Hơn nữa, các phần tử trong hàng được phân tách bằng dấu phẩy.
Đây là mảng hai chiều có thể được biểu diễn dưới dạng bảng như trong hình:
Sau khi khởi tạo
Mảng đa chiều cũng có thể được khởi tạo sau khi tạo và khởi tạo. Trong trường hợp này, các phần tử riêng lẻ của mảng phải được khởi tạo với các giá trị thích hợp. Mỗi phần tử được truy cập bằng chỉ số hàng và cột. Ví dụ:
int[][] marks = new int[4][2];
marks[0][0] = 23; // first row, first column
marks[0][1] = 65; // first row, second column
marks[1][0] = 42; // second row, first column
marks[1][1] = 47; // second row, second column
marks[2][0] = 60; // third row, first column
marks[2][1] = 75; // third row, second column
marks[3][0] = 75; // fourth row, first column
marks[3][1] = 50; // fourth row, second column
Ở đây, phần tử 23 được cho là ở vị trí (0,0), tức là hàng đầu tiên và cột đầu tiên. Do đó, để lưu trữ hoặc truy cập giá trị 23, người ta phải sử dụng dấu cú pháp [0] [0] . Tương tự đối với các giá trị khác, phải sử dụng kết hợp hàng-cột thích hợp. Tương tự như chỉ mục hàng, chỉ mục cột cũng bắt đầu từ 0. Do đó, trong trường hợp nhất định, việc cố gắng viết dấu [0][2] sẽ dẫn đến ngoại lệ vì kích thước cột là 2 và chỉ số cột là 0 và 1.
Sử dụng vòng lặp để xử lý và khởi tạo mảng
Hãy xem xét một tình huống khi người dùng cần hiển thị hàng trăm hoặc hàng nghìn giá trị được lưu trữ trong một mảng. Những phương pháp đã thảo luận trước đây để truy cập từng phần tử riêng lẻ của mảng sẽ rất phiền toái và tốn thời gian. Trong trường hợp như vậy, người dùng có thể sử dụng vòng lặp để xử lý và khởi tạo các mảng. Đoạn mã dưới minh họa phương thức displayMarks() đã được điều chỉnh cho một mảng một chiều có tên là “marks“.
public void displayMarks() {
System.out.println("Marks are:");
// Hiển thị điểm sử dụng vòng lặp for
for (int count = 0; count < marks.length; count++) {
System.out.println(marks[count]);
}
}
Một vòng lặp for đã được sử dụng để lặp qua mảng từ 0 đến marks.length. Thuộc tính length của đối tượng mảng được sử dụng để xác định kích thước của mảng. Trong vòng lặp, mỗi phần tử được hiển thị bằng cách sử dụng tên phần tử và biến count, tức là marks[count].
Ngoài ra, chúng ta có thể sử dụng vòng lặp for cải tiến để duyệt mảng:
public void displayMarks() {
System.out.println("Điểm là:");
// Hiển thị điểm bằng cách sử dụng vòng lặp for cải tiến
for (int value : marks) {
System.out.println(value);
}
}
Ở đây, vòng lặp sẽ in ra tất cả các giá trị của mảng “marks[]” cho đến “marks.length” mà không cần phải xác định rõ điều kiện khởi tạo và kết thúc cho vòng lặp.
Ví dụ vòng lặp xử lý mảng 2 chiều:
public void displayMarks() {
System.out.println("Marks are:");
// Hiển thị điểm sử dụng vòng lặp lồng nhau
// Vòng lặp bên ngoài
for (int row = 0; row < marks.length; row++) {
System.out.print("Rollno." + (row + 1) + ": ");
// Vòng lặp bên trong
for (int col = 0; col < marks[row].length; col++) {
System.out.print(marks[row][col] + " ");
}
System.out.println();
}
}
Trong ví dụ trên, một vòng lặp lồng nhau (ngoại và trong) đã được sử dụng để duyệt qua mảng 2 chiều “marks“. Vòng lặp ngoại theo dõi số hàng, trong khi vòng lặp trong theo dõi số cột trong mỗi hàng. Đối với mỗi hàng, vòng lặp trong duyệt qua tất cả các cột bằng cách sử dụng “marks[row].length“. Bên trong vòng lặp trong, mỗi phần tử được hiển thị bằng cách sử dụng tên phần tử và đếm hàng-cột, tức là “marks[row][column]“.
Tất nhiên, hoàn toàn có thể sử dụng vòng lặp for cải tiến để duyệt mảng 2 chiều. Ví dụ:
public void totalMarks() {
System.out.println("Tổng điểm là:");
// Hiển thị điểm bằng cách sử dụng vòng lặp for và vòng lặp for cải tiến
for (int row = 0; row < marks.length; row++) {
System.out.print("Rollno." + (row + 1) + ": ");
int sum = 0; // Khởi tạo tổng điểm cho mỗi học sinh
// Vòng lặp for cải tiến
for (int value : marks[row]) {
sum += value; // Cộng giá trị vào tổng điểm
}
System.out.println(sum);
}
}
Khởi tạo một đối tượng ArrayList
Một trong những hạn chế lớn của mảng là kích thước của nó được xác định cố định khi tạo. Kích thước này không thể thay đổi sau này. Tuy nhiên, đôi khi không thể dự đoán trước được số lượng phần tử sẽ được lưu trữ trong một mảng. Ví dụ, khi một người mua sắm trực tuyến, số lượng sản phẩm sẽ được thêm vào giỏ hàng không được xác định từ đầu. Trong trường hợp như vậy, người dùng có thể phải tạo ra một mảng có kích thước lớn nhất có thể, điều này có thể dẫn đến lãng phí bộ nhớ nếu chỉ có một vài sản phẩm được thêm vào giỏ hàng. Ngược lại, nếu người dùng cố gắng thêm nhiều sản phẩm hơn vào giỏ hàng mà kích thước của mảng không đủ, điều này sẽ gây ra lỗi. Ngoài ra, việc thêm hoặc xóa giá trị từ một mảng có thể khá khó khăn. Một hạn chế khác của mảng là nó chỉ có thể chứa một loại phần tử (các phần tử có kiểu dữ liệu giống nhau).
Để giải quyết vấn đề này, cần có một cấu trúc dữ liệu cho phép cấp phát bộ nhớ dựa trên yêu cầu và việc thêm hoặc xóa giá trị có thể thực hiện dễ dàng. Java cung cấp khái niệm về các tập hợp (collections) để giải quyết vấn đề này.
Một tập hợp là một đối tượng duy nhất nhóm nhiều phần tử thành một đơn vị duy nhất. Tập hợp được sử dụng để lưu trữ, truy xuất và thao tác dữ liệu tổng hợp. Thông thường, chúng đại diện cho các mục dữ liệu nằm trong cùng một nhóm tự nhiên, như một tập hợp sinh viên, một bộ chữ cái hoặc một tập hợp tên và số điện thoại….
Java cung cấp một tập hợp các interface tập hợp để tạo ra các loại tập hợp khác nhau. Các interface Tập hợp cốt lõi (core Collection interfaces) bao gồm các loại tập hợp khác nhau thể hiện như hình dưới:
Các mục đích triển khai được tổng kết trong bảng dưới:
Interfaces | Hash table | Resizable Array | Tree | Linked list | Hash table + Linked List |
Set | HashSet | – | TreeSet | – | LinkedHashSet |
List | – | ArrayList | – | LinkedList | – |
Queue | – | – | – | – | – |
Map | HashMap | – | TreeMap | – | LinkedHashMap |
Có thể sử dụng các triển khai khác nhau của các tập hợp cốt lõi trong các tình huống khác nhau. Tuy nhiên, HashSet, ArrayList và HashMap thường được sử dụng trong hầu hết các ứng dụng. Ngoài ra, SortedSet và SortedMap interface không được liệt kê trong bảng vì mỗi interface có một Treeset và TreeMap triển khai tương ứng và được liệt kê trong các hàng Set và Map. Queue có hai cách triển khai là LinkedList là cách triển khai Danh sách và PriorityQueue không được liệt kê trong bảng.
Hai cách triển khai này cung cấp ngữ nghĩa rất khác nhau. LinkedList sử dụng thứ tự First In First Out (FIFO), trong khi Queue sắp xếp các phần tử dựa theo giá trị của chúng.
Mỗi triển khai cung cấp tất cả các hoạt động tùy chọn có trong interface của nó. Việc triển khai cho phép các phần tử, khóa và giá trị được null. Để sử dụng các interface, người dùng phải import package java.util vào lớp.
Lưu ý – package là tập hợp các lớp liên quan. Package java.util bao gồm tất cả các lớp và inteface bộ sưu tập (collection).
Lớp ArrayList là một tập hợp được sử dụng thường xuyên trong Java với những đặc điểm sau đây:
- Linh hoạt: ArrayList linh hoạt và có thể được thay đổi kích thước một cách động, có thể tăng hoặc giảm kích thước theo nhu cầu. Quá trình thay đổi kích thước này được xử lý tự động, giúp người lập trình tránh khỏi công việc thủ công.
- Các Phương thức Hữu ích: ArrayList cung cấp một bộ phong phú các phương thức cho việc thao tác trên tập hợp. Các phương thức này giúp dễ dàng thêm, xóa và điều chỉnh các phần tử trong danh sách.
- Thêm và Xóa: Việc thêm và xóa dữ liệu trong ArrayList rất dễ dàng nhờ các phương thức có sẵn. Các phương thức này đơn giản hóa quá trình sửa đổi nội dung của danh sách.
- Duyệt: ArrayList có thể được duyệt bằng nhiều cách khác nhau, chẳng hạn như vòng lặp for, vòng lặp for cải tiến hoặc duyệt (iterators). Sự linh hoạt này cho phép truy cập thuận tiện đến các phần tử trong quá trình xử lý.
- Sức chứa: Sức chứa của ArrayList tăng tự động khi các phần tử được thêm vào. Điều này có nghĩa là nhà phát triển không cần quan tâm đến việc quản lý kích thước của mảng cơ sở; ArrayList xử lý điều này một cách thông minh.
Bảng dưới liệt kê các hàm khởi tạo nạp chồng của ArrayList
Constructor | Mô tả |
ArrayList() | Tạo một ArrayList trống không chứa phần tử ban đầu |
ArrayList(Collection c) | Tạo một ArrayList được khởi tạo bằng các phần tử của tập hợp c cùng cấp |
ArrayList(int capacity) | Tạo một ArrayList với sức chứa ban đầu cụ thể. Sức chứa này đại diện cho kích thước của mảng cơ sở được sử dụng để lưu trữ các phần tử. ArrayList có thể tự động mở rộng vượt quá sức chứa ban đầu này khi thêm phần tử. |
ArrayList cung cấp nhiều phương thức để thêm các phần tử, có thể được phân loại thành hai nhóm:
- Các phương thức thêm một hoặc nhiều phần tử vào cuối danh sách.
- Các phương thức chèn một hoặc nhiều phần tử vào một vị trí cụ thể trong danh sách.
Danh sách phương thức lớp ArrayList:
Phương thức | Mô tả |
void add(int index, Object element) | Chèn phần tử đã chỉ định vào chỉ mục được đưa ra trong danh sách này. Nếu index >= size() hoặc index < 0, nó sẽ ném ra IndexOutOfBoundsException. |
boolean add(Object) | Thêm phần tử đã chỉ định vào cuối danh sách này. |
boolean addAll(Collection c) | Thêm tất cả các phần tử trong tập hợp được chỉ định vào cuối danh sách này. Nếu tập hợp được chỉ định là null, nó sẽ ném ra NullPointerException. |
boolean addAll(int index, Collection c) | Chèn tất cả các phần tử trong tập hợp được chỉ định vào danh sách này, bắt đầu từ chỉ mục được chỉ định. Nếu tập hợp là null, nó sẽ ném ra NullPointerException. |
void clear() | Xóa tất cả các phần tử khỏi danh sách này. |
Object clone() | Trả về một bản sao của ArrayList. |
boolean contains(Object) | Trả về true nếu và chỉ nếu danh sách chứa phần tử đã chỉ định. |
void ensureCapacity(int minCapacity) | Tăng sức chứa của ArrayList, nếu cần, để đảm bảo rằng nó có thể lưu trữ ít nhất bằng số lượng phần tử được chỉ định bởi sức chứa tối thiểu. |
Object get(int index) | Trả về phần tử tại chỉ mục đã chỉ định trong danh sách này. Nếu index >= size() hoặc index < 0, nó sẽ ném ra IndexOutOfBoundsException. |
int indexOf(Object) | Trả về chỉ mục của sự xuất hiện đầu tiên của phần tử đã chỉ định trong danh sách. Nếu phần tử không được tìm thấy, nó trả về -1. |
int lastIndexOf(Object) | Trả về chỉ mục của sự xuất hiện cuối cùng của phần tử đã chỉ định trong danh sách này. Nếu phần tử không được tìm thấy, nó trả về -1. |
Object remove(int index) | Xóa phần tử tại chỉ mục đã chỉ định trong danh sách này. Nếu index >= size() hoặc index < 0, nó sẽ ném ra IndexOutOfBoundsException. |
protected void removeRange(int fromIndex, int toIndex) | Xóa tất cả các phần tử giữa fromIndex (bao gồm) và toIndex (không bao gồm) của danh sách. |
Object set(int index, Object element) | Thay thế phần tử tại chỉ mục đã chỉ định trong danh sách này bằng phần tử mới đã chỉ định. Nếu index >= size() hoặc index < 0, nó sẽ ném ra IndexOutOfBoundsException. |
int size() | Trả về số lượng phần tử trong danh sách này. |
Object[] toArray() | Trả về một mảng chứa tất cả các phần tử trong danh sách theo thứ tự đúng. Nếu mảng là null, nó sẽ ném ra NullPointerException. |
Object[] toArray(Object[] a) | Trả về một mảng chứa tất cả các phần tử trong danh sách theo thứ tự đúng. Loại của mảng được trả về giống như loại của mảng được chỉ định. |
void trimToSize() | Thu gọn sức chứa của ArrayList để phù hợp với kích thước thực tế của nó. |
Để duyệt một ArrayList, người ta có thể sử dụng một trong các cách tiếp cận sau:
- Một vòng lặp for
- Một vòng lặp for cải tiến
- Iterator
- ListIterator
Interface Iterator cung cấp các phương thức để duyệt qua một dữ liệu. Nó có thể được sử dụng cả với mảng cũng như các lớp khác của Collection framework.
Interface Iterator cung cấp các phương thức sau đây để duyệt qua một tập hợp:
- next(): Phương thức này trả về phần tử tiếp theo trong tập hợp.
- hasNext(): Phương thức này trả về true nếu còn các phần tử khác trong tập hợp.
- remove(): Phương thức này loại bỏ phần tử khỏi danh sách trong quá trình duyệt qua tập hợp.
Không có các phương thức cụ thể trong lớp ArrayList để sắp xếp. Tuy nhiên, người ta có thể sử dụng phương thức sort() của lớp Collections để sắp xếp một ArrayList. Cú pháp sử dụng phương thức sort() như sau:
Collections.sort(<collection-name>);
Ví dụ khởi tạo và khởi tạo giá trị cho ArrayList
ArrayList marks = new ArrayList();
marks.add(67);
marks.add(50);
Một ArrayList có thể được lặp bằng cách sử dụng vòng lặp for hoặc bằng cách sử dụng giao diện iterator. Đoạn mã dưới trình bày cách sử dụng các dấu có tên ArrayList để thêm và hiển thị điểm của học sinh:
package sessionarray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
public class ArrayListDemo {
// Create an ArrayList instance
ArrayList<Integer> marks = new ArrayList<>(); // Line 1
/**
* Stores marks in ArrayList
*/
public void storeMarks() {
System.out.println("Storing marks. Please wait...");
marks.add(67); // Line 2
marks.add(50);
marks.add(45);
marks.add(75);
}
/**
* Displays marks from ArrayList
*/
public void displayMarks() {
System.out.println("Marks are:");
// Iterating the List using for loop
System.out.println("Iterating ArrayList using for loop:");
for (int i = 0; i < marks.size(); i++) {
System.out.println(marks.get(i));
}
System.out.println();
// Iterate the List using Iterator interface
Iterator<Integer> marksIterator = marks.iterator(); // Line 3
System.out.println("Iterating ArrayList using Iterator:");
while (marksIterator.hasNext()) { // Line 4
System.out.println(marksIterator.next()); // Line 5
}
System.out.println();
// Sort the List
Collections.sort(marks); // Line 6
System.out.println("Sorted list is: " + marks);
}
public static void main(String[] args) {
// Instantiate the class ArrayListDemo
ArrayListDemo obj = new ArrayListDemo(); // Line 7
// Invoke the storeMarks() method
obj.storeMarks();
// Invoke the displayMarks() method
obj.displayMarks();
}
}
Giới thiệu về Strings
Xét tình huống trong đó người dùng muốn lưu trữ tên của một người. Người ta có thể tạo một mảng ký tự như đoạn mã:
char[] name = {'J','u','l','i','a'}
Các chuỗi ký tự như “Hello” trong Java được triển khai dưới dạng các phiên bản của lớp Chuỗi. Chuỗi là hằng số và bất biến, nghĩa là giá trị của chúng không thể thay đổi sau khi được tạo. Bộ đệm chuỗi cho phép tạo các chuỗi có thể thay đổi. Một thể hiện chuỗi đơn giản có thể được tạo bằng cách đặt một chuỗi ký tự bên trong dấu ngoặc kép như hình dưới:
Các chuỗi ký tự (string literal) như “Hello” trong Java được triển khai dưới dạng các phiên bản của lớp Chuỗi. Chuỗi là hằng số và bất biến, nghĩa là giá trị của chúng không thể thay đổi sau khi được tạo. Bộ đệm chuỗi (string buffer) cho phép tạo các chuỗi có thể thay đổi. Một thể hiện chuỗi đơn giản có thể được tạo bằng cách đặt một chuỗi ký tự bên trong dấu ngoặc kép như:
...
String name = "Mary";
char name[] = {'M', 'a', 'r', 'y'}
...
Một thể hiện của lớp String cũng có thể được tạo bằng từ khóa new, như trong hình:
String str = new String();
Java cũng cung cấp hỗ trợ đặc biệt cho việc nối các chuỗi bằng toán tử dấu cộng (+) và để chuyển đổi dữ liệu thuộc các kiểu dữ liệu khác thành chuỗi như được mô tả trong đoạn mã dưới:
String str="Hello";
String str1 ="World";
System.output.println(str+str1);
Ngoài ra, có thể chuyển đổi một mảng ký tự thành một chuỗi như được mô tả trong đoạn mã:
char[] name = {'J','o','h','n'};
String empName = new String(name);
Lớp java.lang.String là lớp final, nghĩa là không có lớp nào có thể mở rộng nó. Lớp java.lang.String khác với các lớp khác ở chỗ người ta có thể sử dụng toán tử “+=” và ‘+’ với các đối tượng chuỗi để nối.
Nếu sau này chuỗi không có khả năng thay đổi, người ta có thể sử dụng lớp String. Vì vậy, một lớp String có thể được sử dụng vì những lý do sau:
- chuỗi bất biến và do đó nó có thể được chia sẻ an toàn giữa nhiều luồng.
- Các luồng (thread) sẽ chỉ đọc chúng, đây thường là một hoạt động an toàn cho luồng.
Lưu ý – Một luồng Là một đơn vị thực thi duy nhất trong một chương trình. JVM cho phép một ứng dụng thực thi đồng thời nhiều luồng của một tiến trình.
Tuy nhiên, nếu chuỗi này có thể thay đổi sau đó và nó sẽ được chia sẻ giữa nhiều luồng, thì người ta có thể sử dụng lớp StringBuffer. Việc sử dụng lớp StringBuffer đảm bảo rằng chuỗi được cập nhật chính xác. Tuy nhiên, nhược điểm là việc thực thi phương thức tương đối chậm hơn.
Nếu sau này chuỗi có thể thay đổi nhưng không được chia sẻ giữa nhiều luồng thì người ta có thể sử dụng lớp StringBuilder. Lớp StringBuilder có thể được sử dụng vì những lý do sau:
- Nó cho phép sửa đổi các chuỗi mà không cần phải đồng bộ hóa.
- Các phương thức của lớp stringBuilder thực thi nhanh bằng hoặc nhanh hơn các phương thức của lớp StringBuffer
Làm việc với lớp String
Lớp String cung cấp các phương thức để thao tác các ký tự riêng lẻ của chuỗi, so sánh các chuỗi, trích xuất chuỗi con, tìm kiếm chuỗi và để chuyển đổi một chuỗi thành chữ hoa hoặc chữ thường.
Một số phương thức thường được sử dụng của lớp String như sau:
length(string str)
Phương thức length() được sử dụng để tìm độ dài của chuỗi.
Ví dụ:
String str = "Hello";
System.out.println(str.length());
charAt(int index)
Phương thức charAt() được sử dụng để lấy giá trị ký tự tại một chỉ mục cụ thể. Chỉ số nằm trong khoảng từ 0 đến length() – 1. Chỉ mục của ký tự đầu tiên bắt đầu từ 0. Ví dụ:
System.out.println(str.charAt(2));
concat(string str)
Phương thức concat() được sử dụng để nối một chuỗi được chỉ định làm đối số vào cuối chuỗi khác. Nếu độ dài của chuỗi bằng 0, đối tượng String ban đầu sẽ được trả về, nếu không thì sẽ trả về một đối tượng String mới.
Ví dụ:
compareTo(string str)
Phương thức so compareTo() được sử dụng để so sánh hai đối tượng String. Kết quả so sánh trả về một giá trị số nguyên. Việc so sánh dựa trên giá trị Unicode của từng ký tự trong chuỗi. Nghĩa là, kết quả sẽ trả về giá trị âm, nếu chuỗi đối số lớn hơn chuỗi gốc theo thứ tự bảng chữ cái. Kết quả sẽ trả về giá trị dương, nếu chuỗi đối số nhỏ hơn chuỗi gốc theo thứ tự bảng chữ cái và kết quả sẽ trả về giá trị bằng 0, nếu cả hai chuỗi đều bằng nhau.
Ví dụ:
System.out.println (str.compareTo("World")); // output: -15
Đầu ra là -15 vì chuỗi thứ hai “World” bắt đầu bằng ‘w’, lớn hơn theo thứ tự bảng chữ cái so với ký tự đầu tiên ‘H’ của chuỗi gốc, str. Sự khác biệt giữa vị trí của ‘H’ và ‘W là 15. Vì ‘a’ nhỏ hơn ‘W nên kết quả sẽ là -15.
Lưu ý – Unicode là tiêu chuẩn cung cấp một số duy nhất cho mỗi ký tự bất kể nền tảng, chương trình hoặc ngôn ngữ. Tiêu chuẩn Unicode đã được áp dụng để lập trình bởi các nhà lãnh đạo lớn trong ngành như IBM, Apple, Microsoft, HP, Oracle, Sun, SAP và một số công ty khác.
indexOf(string str)
Phương thức indexOf() trả về chỉ mục lần xuất hiện đầu tiên của ký tự hoặc chuỗi được chỉ định trong một chuỗi. Nếu không tìm thấy ký tự hoặc chuỗi, phương thức trả về -1. Ví dụ:
system.out.println(str.indexOf("e")); // output: 1
lastIndexOf(String str)
Phương thức LastIndexOf() trả về chỉ mục lần xuất hiện cuối cùng của một ký tự hoặc chuỗi được chỉ định trong một chuỗi. Ký tự hoặc chuỗi đã chỉ định được tìm kiếm ngược tức là tìm kiếm bắt đầu từ ký tự cuối cùng. Ví dụ:
system.out.println(str.lastIndexOf("1")); // đầu ra: 3
replace(char old, char new)
Phương thức replace() được sử dụng để thay thế tất cả các lần xuất hiện của một ký tự được chỉ định trong chuỗi hiện tại bằng một ký tự mới đã cho: Nếu ký tự được chỉ định không tồn tại, tham chiếu của chuỗi gốc sẽ được trả về. Ví dụ:
System.out.println(str.replace('e','a')); // output: 'Hallo'
substring(int beginIndex, int endIndex)
Phương thức substring() được sử dụng để lấy một phần của chuỗi, tức là chuỗi con từ chuỗi đã cho. Người ta có thể chỉ định chỉ mục bắt đầu và chỉ mục kết thúc cho chuỗi con. Nếu chỉ mục cuối không được chỉ định, tất cả các ký tự từ chỉ mục bắt đầu đến cuối chuỗi sẽ được trả về.
Chuỗi con bắt đầu tại vị trí đã chỉ định được biểu thị bằng chỉ mục hoặc beginIndex và kéo dài đến ký tự được chỉ định bởi chỉ mục hoặc endindex – 1, Như vậy, độ dài của chuỗi con là endIndex – BeginIndex. Ở đây, BeginIndex được bao gồm trong đầu ra trong khi endIndex bị loại trừ.
Ví dụ:
system.out.println(str.substring(2,5));
toSring()
Phương thức toString() được sử dụng để trả về một đối tượng chuỗi (String). Nó được sử dụng để chuyển đổi giá trị từ các kiểu dữ liệu khác thành chuỗi. Ví dụ:
Integer length = 5;
System.out.println(length.toString()); // Outpput: "5"
Chú ý rằng đầu ra bây giờ là “5,” biểu diễn dưới dạng một chuỗi thay vì một số nguyên.
Lưu ý – Lớp Integer được sử dụng trong ví dụ là một lớp bọc (wrapper class) cho kiểu dữ liệu nguyên (int).
trim()
Phương thức trim() trả về một chuỗi mới sau khi đã loại bỏ các khoảng trắng ở đầu và cuối chuỗi hiện tại. Ví dụ:
String str1 = " Hello ";
System.out.println(str1.trim()); // Output: "Hello"
Phương thức trim() sẽ trả về “Hello” sau khi loại bỏ các khoảng trắng.
Đoạn mã mô phỏng các cách sử dụng các phương thức của lớp chuỗi:
public class Strings {
String str = "Xin chào"; // Khởi tạo biến chuỗi
Integer strLength = 5; // Sử dụng lớp bọc Integer
// Hiển thị chuỗi bằng cách sử dụng các phương thức của lớp chuỗi
public void displayStrings() {
// Sử dụng các phương thức của lớp chuỗi
System.out.println("Chiều dài chuỗi: " + str.length());
System.out.println("Ký tự tại vị trí 2 là: " + str.charAt(2));
System.out.println("Chuỗi sau khi nối là: " + str.concat("Thế giới"));
System.out.println("So sánh chuỗi: " + str.compareTo("Thế giới"));
System.out.println("Vị trí của 'o' là: " + str.indexOf("o"));
System.out.println("Vị trí cuối cùng của 'l' là: " + str.lastIndexOf("l"));
// Sửa lỗi chính tả và sử dụng phương thức replace
System.out.println("Chuỗi sau khi thay thế: " + str.replace("e", "a"));
// Sử dụng phương thức substring
System.out.println("Chuỗi con: " + str.substring(2, 5));
// Sử dụng phương thức toString cho Integer
System.out.println("Integer toString là: " + strings.strLength.toString());
String str1 = " Hello";
System.out.println("Chuỗi chưa cắt: " + str1);
// Sử dụng phương thức trim
System.out.println("Chuỗi sau khi cắt: " + str1.trim());
}
public static void main(String[] args) {
Strings strings = new Strings();
strings.displayStrings();
}
}
Làm việc với Lớp StringBuilder
Đối tượng StringBuilder tương tự như đối tượng String, trừ việc chúng có thể thay đổi. Bên trong, hệ thống xem xét những đối tượng này như một mảng có độ dài biến thiên chứa một chuỗi các ký tự. Độ dài và nội dung của chuỗi ký tự có thể thay đổi thông qua các phương thức có sẵn trong lớp StringBuilder. Tuy nhiên, các nhà phát triển thường ưa chuộng việc sử dụng lớp String trừ khi StringBuilder mang lại lợi ích của mã nguồn đơn giản hơn trong những trường hợp cụ thể. Ví dụ, khi nối một lượng lớn chuỗi, việc sử dụng đối tượng StringBuilder hiệu quả hơn.
Lớp StringBuilder cũng cung cấp phương thức length(), trả về độ dài của chuỗi ký tự trong lớp.
Tuy nhiên, khác với chuỗi, đối tượng StringBuilder còn có thuộc tính được gọi là “capacity” (sức chứa), xác định số lượng khoảng trống ký tự đã được cấp phát. Sức chứa có thể được lấy thông qua phương thức capacity() và luôn luôn lớn hơn hoặc bằng độ dài. Sức chứa để chứa dữ liệu trong một thể hiện của lớp StringBuilder sẽ tự động mở rộng theo yêu cầu của người dùng để thích nghi với các chuỗi mới khi được thêm vào StringBuilder.
Do đó, lớp StringBuilder được sử dụng để thao tác với đối tượng chuỗi. Đối tượng của lớp StringBuilder có thể thay đổi và linh hoạt. Đối tượng StringBuilder cho phép chèn ký tự và chuỗi, cũng như nối ký tự và chuỗi vào cuối.
Các hàm khởi tạo của lớp StringBuilder như sau:
- StringBuilder(): Hàm khởi tạo mặc định cung cấp không gian cho 16 ký tự.
- StringBuilder(int capacity): Tạo một đối tượng mà không chứa ký tự nào. Tuy nhiên, nó dự trữ không gian cho số lượng ký tự được chỉ định trong đối số, capacity.
- StringBuilder(String str) Tạo một đối tượng được khởi tạo với nội dung của chuỗi đã chỉ định, str.
Phương thức của Lớp StringBuilder
Lớp StringBuilder cung cấp một số phương thức để nối, chèn, xóa và đảo ngược chuỗi như sau:
append()
Phương thức append() được sử dụng để nối các giá trị vào cuối đối tượng StringBuilder. Phương thức này chấp nhận các loại đối số khác nhau, bao gồm char, int, float, double, boolean,.. nhưng đối số phổ biến nhất là String.
Đối với mỗi phương thức append(), phương thức String.valueOf() được gọi để chuyển đổi tham số thành giá trị biểu diễn dưới dạng chuỗi tương ứng, sau đó, chuỗi mới được nối vào đối tượng StringBuilder.
Ví dụ:
StringBuilder str = new StringBuilder("JAVA ");
System.out.println(str.append("SE")); // Output: "JAVA SE"
System.out.println(str.append(7)); // Output: "JAVA SE 7"
insert()
Phương thức insert() được sử dụng để chèn một chuỗi vào một chuỗi khác. Tương tự như phương thức append(), nó gọi phương thức String.valueOf() để lấy giá trị biểu diễn dưới dạng chuỗi. Chuỗi mới được chèn vào đối tượng StringBuilder gọi.
Phương thức insert() có một số phiên bản như sau:
- StringBuilder insert(int insertPosition, string str)
- StringBuilder insert(int insertPosition, char ch)
- StringBuilder insert(int insertPosition, float f)
Ví dụ:
StringBuilder str = new StringBuilder("JAVA 7 ");
System.out.println(str.insert(5, "SE")); // Output: "JAVA SE 7"
delete()
Phương thức delete() xóa số lượng ký tự đã chỉ định khỏi đối tượng StringBuilder gọi.
Ví dụ:
StringBuilder str = new StringBuilder("JAVA SE 7");
System.out.println(str.delete(4, 7)); // Output: "JAVA 7"
reverse()
Phương thức reverse() được sử dụng để đảo ngược các ký tự trong một đối tượng StringBuilder. Ví dụ:
StringBuilder str = new StringBuilder("JAVA SE 7");
System.out.println(str.reverse()); // Output: "7 ES AVAJ"
Đooạn mã mô tả cách sử dụng phương thức của lớp StringBuilder
public class StringBuilders {
// Khởi tạo một đối tượng StringBuilder
StringBuilder str = new StringBuilder("JAVA");
// Hiển thị chuỗi bằng cách sử dụng các phương thức khác nhau của StringBuilder
public void displayStrings() {
// Sử dụng các phương thức khác nhau của lớp StringBuilder
System.out.println("Chuỗi sau khi nối: " + str.append(" 7"));
System.out.println("Chuỗi sau khi chèn: " + str.insert(5, " SE"));
System.out.println("Chuỗi sau khi xóa: " + str.delete(4, 7));
System.out.println("Chuỗi sau khi đảo ngược: " + str.reverse());
}
// Phương thức main để chạy lớp StringBuilders
public static void main(String[] args) {
// Khởi tạo đối tượng lớp StringBuilders
StringBuilders objStrBuild = new StringBuilders();
// Gọi phương thức displayStrings()
objStrBuild.displayStrings();
}
}
Mảng Chuỗi
Đôi khi chúng ta cần lưu trữ một tập hợp các chuỗi. Mảng chuỗi có thể được tạo trong Java bằng cách tương tự như các mảng của các loại dữ liệu nguyên thủy. Ví dụ:
String[] empNames = new String[10];
Lệnh này sẽ cấp phát bộ nhớ để lưu trữ tham chiếu của 10 chuỗi. Tuy nhiên, không có bộ nhớ nào được cấp phát để lưu trữ các ký tự tạo nên các chuỗi cụ thể. Vòng lặp có thể được sử dụng để khởi tạo và hiển thị giá trị của một mảng chuỗi.
Ví dụ:
public class StringArray {
// Khai báo một mảng chuỗi
String[] empID;
// Tạo một mảng chuỗi
public void createArray() {
System.out.println("Đang tạo mảng. Vui lòng chờ đợi...");
// Sử dụng vòng lặp for để khởi tạo mảng
for (int count = 0; count < empID.length; count++) {
empID[count] = "00" + count; // Lưu trữ giá trị trong mảng
}
}
// Hiển thị mảng
public void printArray() {
System.out.println("Mảng là:");
// Sử dụng vòng lặp for để hiển thị mảng
for (int count = 0; count < empID.length; count++) {
System.out.println("Mã nhân viên: " + empID[count]);
}
}
public static void main(String[] args) {
// Khởi tạo đối tượng lớp StringArray
StringArray objStrArray = new StringArray();
// Gọi phương thức createArray()
objStrArray.createArray();
// Gọi phương thức printArray()
objStrArray.printArray();
}
}
Tham số Dòng Lệnh
Người dùng có thể truyền bất kỳ số lượng đối số nào cho ứng dụng Java tại thời điểm chạy từ dòng lệnh hệ thống điều khiển OS. Phương thức main() khai báo một tham số có tên là args, đó là một mảng chuỗi (String array) chấp nhận các đối số từ dòng lệnh. Các đối số này được đặt trên dòng lệnh và theo sau tên lớp khi ứng dụng được thực thi. Ví dụ:
java EmployeeDetail Roger Smith Manager
Ở đây, EmployeeDetail là tên của một lớp và Roger, Smith, và Manager là các đối số dòng lệnh được lưu trữ trong mảng theo thứ tự mà chúng được chỉ định. Khi ứng dụng được khởi chạy, hệ thống thời gian chạy sẽ chuyển các đối số dòng lệnh vào phương thức main() của ứng dụng bằng một mảng chuỗi kiểu String, args[]. Lưu ý rằng mảng chuỗi này có thể có tên khác. Mảng args[] chấp nhận các đối số và lưu trữ chúng tại các vị trí thích hợp trong mảng. Độ dài của mảng được xác định từ số lượng đối số được truyền tại thời điểm chạy. Các đối số được ngăn cách bởi khoảng trắng.
Mục đích cơ bản của các đối số dòng lệnh là để xác định thông tin cấu hình cho ứng dụng.
Như đã mô tả, phương thức main() là điểm vào của một chương trình Java, nơi đối tượng được tạo ra và các phương thức được gọi.
Phương thức main() tĩnh (static) nhận một mảng chuỗi kiểu String là đối số như được minh họa trong mã:
public static void main(String[] args) {
// Tham số của phương thức main() là một mảng chuỗi kiểu String đại diện cho các đối số dòng lệnh.
// Kích thước của mảng được đặt bằng số lượng đối số được chỉ định tại thời điểm chạy.
// Tất cả các đối số dòng lệnh đều được truyền dưới dạng chuỗi.
}
Ví dụ:
package mypackage;
public class EmployeeDetail {
public static void main(String[] args) {
// Kiểm tra số lượng đối số dòng lệnh
if (args.length == 3) {
// Hiển thị giá trị của các đối số cá nhân
System.out.println("First Name: " + args[0]);
System.out.println("Last Name: " + args[1]);
System.out.println("Designation: " + args[2]);
} else {
System.out.println("Please provide paramenters.");
}
}
}
Lớp EmployeeDetail bao gồm phương thức main(). Trong phương thức main(), câu lệnh if kiểm tra xem số lượng đối số dòng lệnh đã chỉ định có bằng 3 hay không. Nếu có, nó sẽ in ra các giá trị tương ứng trong các phần tử của mảng như args[0], args[1] và args[2]; ngược lại, nó sẽ in ra thông báo được chỉ định trong phần else.
Để chạy chương trình với đối số dòng lệnh tại dòng lệnh, thực hiện các bước sau:
Mở cửa sổ dòng lệnh.
Biên dịch chương trình Java bằng cách gõ lệnh sau:
javac EmployeeDetail.java
Chạy chương trình bằng cách gõ lệnh sau:
java EmployeeDetail Roger Smith Manager
Để chạy chương trình với đối số dòng lệnh bằng IDE NetBeans, thực hiện các bước sau:
Nhấp chuột phải vào tên dự án trong tab Dự án (Projects) và chọn Thuộc tính (Properties). Hộp thoại Các thuộc tính dự án hiện ra.
Chọn Mục Run trong mục Thể loại (Categories) ở bên trái. Các thuộc tính thời gian chạy sẽ hiện ra ở bên phải.
Nhập các đối số Roger, Smith và Manager vào hộp Argument như trong hình
Bấm OK để đóng Project Properties dialog box.
Click chuột nút Run trên thanh công cụ hoặc bấm phím F6. Tham số command line đã được cung cấp cho hàm main().
Output:
Lớp Bao (Wrapper Classes)
Java cung cấp một tập hợp các lớp được biết đến là lớp bao (wrapper classes) cho mỗi kiểu dữ liệu nguyên thủy của nó, giúp “bao quanh” kiểu nguyên thủy thành một đối tượng của lớp đó. Nói cách khác, các lớp bao cho phép truy cập kiểu dữ liệu nguyên thủy dưới dạng đối tượng. Các lớp bao cho các kiểu dữ liệu nguyên thủy bao gồm: Byte, Character, Integer, Long, Short, Float, Double và Boolean.
Các lớp bao này thuộc gói java.lang. Các kiểu dữ liệu nguyên thủy và các lớp bao tương ứng của chúng là:
Primitive Type | Wrapper Class |
byte | Byte |
char | Character |
int | Integer |
long | Long |
short | Short |
float | Float |
double | Double |
boolean | Boolean |
Việc sử dụng các kiểu dữ liệu nguyên thủy dưới dạng đối tượng có thể làm đơn giản hóa các nhiệm vụ trong một số trường hợp. Ví dụ, hầu hết các bộ sưu tập(collection) lưu trữ đối tượng và không phải kiểu dữ liệu nguyên thủy. Nói cách khác, nhiều hoạt động dành cho đối tượng sẽ không có sẵn cho kiểu dữ liệu nguyên thủy. Hơn nữa, nhiều phương thức tiện ích được cung cấp bởi các lớp bao giúp thao tác với dữ liệu. Vì lớp bao chuyển đổi kiểu dữ liệu nguyên thủy thành đối tượng, chúng có thể lưu trữ trong bất kỳ loại bộ sưu tập (collection) nào và cũng có thể được truyền làm tham số cho các phương thức.
Các lớp bao có thể chuyển đổi chuỗi số thành giá trị số. Phương thức valueOf() có sẵn với tất cả các lớp bao để chuyển đổi một kiểu thành một kiểu khác. Tuy nhiên, phương thức valueOf() của lớp Character chỉ chấp nhận char làm đối số trong khi bất kỳ lớp bao nào khác đều chấp nhận cả kiểu nguyên thủy tương ứng hoặc chuỗi (String) làm đối số. Phương thức typeValue() cũng có thể được sử dụng để trả về giá trị của một đối tượng dưới dạng kiểu nguyên thủy của nó.
Mốt số ví dụ của các phương thức của Class Wrapper:
- Byte:
- byteValue() – trả về giá trị byte của đối tượng đang gọi. Ví dụ: byte byteVal = Byte.byteValue();
- parseByte(String s) – trả về giá trị byte từ một chuỗi chứa giá trị byte. Ví dụ: byte byteVal = Byte.parseByte(“45”);
- Character:
- isDigit() – kiểm tra xem một ký tự có phải là số không. Ví dụ: Character.isDigit(‘5’);
- isLowerCase() – kiểm tra xem một ký tự có phải là chữ thường không. Ví dụ: Character.isLowerCase(‘a’);
- Integer:
- intValue(): Phương thức này trả về giá trị nguyên (int) của đối tượng Integer đang gọi. Ví dụ: int intval = Integer.intValue();
- parseInt(String s): Phương thức này trả về giá trị nguyên (int) từ một chuỗi chứa giá trị nguyên. Ví dụ: int intval = Integer.parseInt(“45”);
Không chỉ Integer, mà cả các lớp bao số khác (Byte, Long, Short, Float, Double) cũng cung cấp các phương thức tương tự để chuyển đổi giữa các kiểu dữ liệu nguyên thủy và đối tượng của chúng.
Sự khác biệt giữa việc tạo kiểu dữ liệu nguyên thủy và Wrapper Class như sau:
Kiểu dữ liệu nguyên thủy:
int x = 107;
Wrapper Class:
Integer y = new Integer(20);
Câu lệnh đầu tiên khai báo và khởi tạo biến int x với giá trị 10, trong khi câu lệnh thứ hai khởi tạo một đối tượng Integer y và gán giá trị 20 cho nó. Trong trường hợp này, tham chiếu của đối tượng được gán cho biến đối tượng y. Phân bổ bộ nhớ cho hai câu lệnh được hiển thị trong hình:
Tù hình vẽ, x là một biến giữ một giá trị, trong khi y là một biến đối tượng giữ một tham chiếu đến một đối tượng.
Các phương thức parsexxx() (ví dụ: parseInt(), parseFloat()) và valueOf() cho phép bạn chuyển đổi giữa các kiểu nguyên thủy và kiểu bao. Tuy nhiên, lưu ý rằng nếu đối số chuỗi không được hình thành đúng cách, cả hai phương thức này đều ném ra một ngoại lệ NumberFormatException. Các phương thức này có thể chuyển đổi đối tượng chuỗi từ các cơ sở khác nhau nếu kiểu nguyên thủy cơ bản là một trong bốn kiểu số nguyên.
Phương thức parsexxx() trả về một giá trị nguyên thủy được đặt tên, trong khi phương thức valueOf() trả về một đối tượng bao mới của kiểu đã gọi phương thức.
Ví dụ:
package session5;
public class Wrappers {
public void calcResult(int num1, int num2, String choice) {
// Switch case to evaluate the choice
switch (choice) {
case "+":
System.out.println("Result after addition is: " + (num1 + num2));
break;
case "-":
System.out.println("Result after subtraction is: " + (num1 - num2));
break;
case "*":
System.out.println("Result after multiplication is: " + (num1 * num2));
break;
case "/":
System.out.println("Result after division is: " + (num1 / num2));
break;
default:
System.out.println("Invalid operator");
}
}
public static void main(String[] args) {
// Check the number of command line arguments
if (args.length == 3) {
int num1 = Integer.parseInt(args[0]);
int num2 = Integer.parseInt(args[1]);
String operator = args[2];
// Instantiate the Wrappers class
Wrappers objWrap = new Wrappers();
// Invoke the calcResult() method
objWrap.calcResult(num1, num2, operator);
} else {
System.out.println("Usage: num1 num2 operator");
}
}
}
Để chạy ví dụ trên, cấu hình project netbean như sau:
AutoBoxing và Unboxing
Trong Java, có tính năng tự động chuyển đổi các kiểu dữ liệu nguyên thủy như int, float \ thành các kiểu đối tượng tương ứng như Integer, Float và các kiểu khác trong quá trình gán giá trị và gọi phương thức và hàm khởi tạo. Quá trình tự động chuyển đổi này từ kiểu dữ liệu nguyên thủy sang kiểu đối tượng được gọi là tự động đóng gói (autoboxing).
Ví dụ:
ArrayList<Integer> danhSachSoNguyen = new ArrayList<Integer>();
danhSachSoNguyen.add(10); // autoboxing
Integer y = 20; // autoboxing
Tương tự, quá trình chuyển đổi từ kiểu đối tượng sang kiểu dữ liệu nguyên thủy được gọi là tự động mở gói (unboxing). Ví dụ:
int z = y; // tự động mở gói
Tự động đóng gói và tự động mở gói giúp người phát triển viết mã nguồn sạch sẽ hơn. Ngoài ra, bằng cách sử dụng tự động đóng gói và tự động mở gói, bạn có thể sử dụng các phương thức của các lớp bao đối tượng khi cần thiết.
Ví dụ:
public class Main {
public static void main(String[] args) {
Character chBox = 'A'; // Autoboxing a character
char chUnbox = chBox; // Unboxing a character
// Print the values
System.out.println("Character after autoboxing is: " + chBox);
System.out.println("Character after unboxing is: " + chUnbox);
}
}
Output:
Compact Strings
Compact String là một trong những cải tiến về hiệu suất được giới thiệu trong JVM như một phần của JDK 9.
Cho đến JDK 8, Java biểu diễn các đối tượng String dưới dạng char[] vì mỗi ký tự trong Java có 2 byte do Java sử dụng UTF-16. Nhiều ký tự yêu cầu 2 byte để biểu diễn chúng, tuy nhiên, một số ký tự chỉ yêu cầu một byte (còn được gọi là LATIN-1). Do đó, có tiềm năng để cải thiện việc tiêu thụ bộ nhớ và hiệu suất bằng cách thay đổi triển khai nội bộ của String. Điều này dẫn đến khái niệm về chuỗi kín (compact strings).
Trong Java 9 và các phiên bản cao hơn, triển khai của lớp String đã được xem xét lại để được hỗ trợ bởi một mảng byte thay vì một mảng char. Khi chúng ta tạo một String, nếu tất cả các ký tự của nó có thể được biểu diễn bằng một byte duy nhất (LATIN-1), một mảng byte sẽ được sử dụng nội bộ để tiết kiệm một nửa không gian cần thiết. Điều này giúp tiết kiệm bộ nhớ và ứng dụng sẽ hoạt động tốt hơn.
Một trường final có tên là coder được sử dụng trong triển khai nội bộ của String với một mảng byte như sau:
private final byte[] value;
/* can be LATIN1 = 0 or UTF16 = 1 */
private final byte coder;
Hầu hết các thao tác trên chuỗi phải kiểm tra giá trị của coder và chuyển hướng đến triển khai cụ thể.
Xét một số ví dụ để hiểu khái niệm về chuỗi kín. Đoạn mã dưới hiển thị mã để minh họa việc tiêu thụ bộ nhớ bởi chuỗi trước JDK 9:
public class RegularStringDemo {
public static void main(String[] args) {
String str = new String("Demo Examples");
}
}
Ở đây, chúng ta tạo một đối tượng String với 13 ký tự và các ký tự bên trong đối tượng có thể được biểu diễn bằng một byte, đó chính là biểu diễn LATIN-1. Nếu chúng ta chạy mã này với phiên bản JDK 8 hoặc trước đó, thì bên trong, String sẽ được biểu diễn dưới dạng char[]. Trong trường hợp này, chúng ta không cần char[] vì mỗi ký tự có thể được biểu diễn bằng một byte duy nhất. Tuy nhiên, trong JDK 8, thay vì tạo byte[], một char[] sẽ được tạo bên trong và cho mỗi ký tự, 2 byte sẽ được gán trong bộ nhớ heap. Điều này là sự lãng phí của bộ nhớ heap.
Tùy chọn Compact String VM được bật mặc định trong tất cả các phiên bản JDK từ JDK 9 trở lên. Nếu bạn đang sử dụng JDK phiên bản cao hơn như 17,21 nhưng muốn kiểm tra mã với JDK 8, bạn có thể tắt nó bằng cách sử dụng đối số lệnh sau:
+XX:-CompactStrings
Ví dụ tiếp theo:
public class CompactStringDemo {
public static void main(String[] args) {
String str1 = new String("Demo Examples");
String str2 = new String("Demo Examples €");
}
}
Xét tình huống mã này được biên dịch bằng JDK 9 trở lên. Do đó, char[] hoặc byte[] sẽ được tạo ra tùy theo yêu cầu cho các đối tượng String. Chúng ta đã tạo đối tượng String str1 với 13 ký tự và đối tượng str2 với 14 ký tự. Mỗi ký tự trong đối tượng str có thể được biểu diễn bằng một byte duy nhất. Do đó, cho đối tượng str, sẽ được tạo ra một byte[]. Còn đối với str2, chúng ta có một ký tự bổ sung ngoài các ký tự có trong đối tượng str1, đó là €. Ký tự € này không thể được biểu diễn bằng bảng mã LATIN-1. Ở đây, chúng ta cần 2 byte để biểu diễn €. Do đó, Java sẽ sử dụng UTF-16 để biểu diễn các ký tự trong str2. Đối với đối tượng str2, một char[] sẽ được tạo ra. Do đó, thay vì sử dụng hai mảng char, chỉ có một mảng char và một mảng byte sẽ được sử dụng, từ đó tiết kiệm bộ nhớ và cải thiện hiệu suất.
Bài tập
Bài 1:
Viết một chương trình Java để nhập thông tin nhân viên của bộ phận Sales như Mã số nhân viên, Tên, Chức vụ, Lương cơ bản và Doanh số bán hàng. Dựa trên doanh số bán hàng của nhân viên, chương trình sẽ tính hoa hồng theo các quy tắc được nêu phía dưới:
- >=10000 30% lương cơ bản
- >=8000 30% lương cơ bản
- >=6000 20% lương cơ bản
- >=4000 10% lương cơ bản
Dựa trên số hoa hồng tính được, chương trình sẽ tính tổng lương bằng cách cộng thêm số hoa hồng vào lương cơ bản. Hiển thị thông tin chi tiết của nhân viên như sau:
Employee ID:
Employee Name:
Designation:
Basic Salary:
Sales Done:
Total Salary:
Bài 2:
Viết chương trinh mô phỏng một phần mềm quản lý tài khoản mxh theo yêu cầu sau:
Tạo kiểu dữ liệu theo cấu trúc sau:
- username String
- password String
- user_type String
- register_date Integer
- cookie String
- price Float
Tạo header menu chương trình
Chuong trinh quan ly tai khoan |
1. Nhap lieu |2. Sap xep username theo ten |3. Sap xep theo gia |3. Phan tich |4. Tim user theo ten | |5. Thoat |
Menu sẽ hiển thị khi chương trình bắt đầu
Sau khi người dung chọn tính năng và thực thi xong, trừ option 7 sẽ hiển thị thông báo xem người dung mong muốn tiếp tục không.
Ban muon tiep tuc khong ?
- Co. (bam phim ‘y’, ‘Y’)
- Khong (bam phim ‘n’, ‘N’)
– Clear man hinh ! (bam ‘c’, ‘C’)
Lua chon cua ban:
Nếu người dùng chọn trường hợp không hợp lệ, yêu cầu nhập lại
Nếu chọn y, quay lại in menu để người dùng tiếp tục nhập liệu, nếu N thì thoát, c thì clear screen
Lưu ý: chương trình cần đáp ứng được yêu cầu người dùng nhập liệu trước khi sử dụng các option khác, nếu không sẽ thông báo lỗi cho người dùng, buộc người dùng phải nhập liệu tài khoản trước.
- Nhập vào danh sách tài hoản
Nếu người dùng chọn option 1, chương trình thực hiện những yêu cầu sau:
- Yêu cầu người dùng nhập tổng số tài khoản cần nhập liệu
- Nhập thông tin của mỗi tài khoản
- Validate thông tin nhập vào theo yêu cầu:
0<= giá (price) <= 100000
Register year phải là 5 năm trở lại đây
Mô tả tính năng khi input tài khoản:
Nhap vao username [1]:
Name: toanngo92
Password: abcdef
user_type: facebook
Reister year: 1992
price: 1200
Yêu cầu sử dụng pointer cho các hàm làm tính năng làm việc với tài khoản
- Khi người dùng chọn option 2, hiển thị ra bảng danh sách sắp xếp theo alphabet
username |
Password |
user_type |
Reg date |
Cookie |
price |
||
Toanngo |
1234 |
|
2005 |
randomstring |
10000 |
||
User2 |
abcde |
|
2007 |
randomstring |
20000 |
||
User3 3 |
1234 |
|
2008 |
randomstring |
1000 |
Khi người dùng chọn option 3, hiển thị ra danh sách sắp xếp giá giảm dần
- Thuật toán Tìm kiếm số lượng nhân sự theo uer_type
Khi người dùng chọn option 3: tính toán số lượng tài khoản theo kiểu mxh
+ 2 tai khoan mxh ‘facebook.
+ 1 tai khoan mxh ‘twitter.
…
- Tìm kiếm tài khoản theo user_type và giá thấp nhất
Khi người dùng chọn option 4: Yêu cầu người dùng nhập mxh cần tìm kiếm và giá thấp nhất (min)
Hiển thị tất cả tài khoản dùng với user_type trùng khớp với input của người dùn và giá cao hơn giá thấp nhất ng dùng nhập vào