Bài tập

Định dạng dữ liệu char và String

Huy Erick

Chào mừng bạn đến với bài viết này! Trước đây, chúng ta đã làm việc chủ yếu với các loại dữ liệu int, double hoặc boolean. Tuy nhiên, chúng ta cũng biết rằng để in...

Chào mừng bạn đến với bài viết này! Trước đây, chúng ta đã làm việc chủ yếu với các loại dữ liệu int, double hoặc boolean. Tuy nhiên, chúng ta cũng biết rằng để in ra các đoạn văn bản hoặc kết quả, chúng ta sử dụng các chuỗi (String). Trong phần này, chúng ta sẽ đi vào chi tiết về các loại dữ liệu để làm việc với các ký tự và chuỗi.

Loại dữ liệu char

Loại dữ liệu char lưu trữ đúng một ký tự duy nhất. Tuy nhiên, nội bộ thực tế không lưu trữ ký tự (văn bản) mà lưu trữ một giá trị số từ 0 đến 65535. Tuy nhiên, khi khai báo là char, nó được hiểu là ký tự trong bộ mã UTF-16.

Để biết ký tự tương ứng với mỗi số, chúng ta có thể sử dụng bảng mã ASCII, nơi mỗi giá trị từ 0 đến 255 được gán cho một ký tự:

(nguồn: pctipp.ch)

Tuy nhiên, bảng mã này đã không đáp ứng đủ các ký tự cần thiết từ lâu. Bạn có thể xem tổng quan về tất cả các ký tự có thể của bộ mã UTF-16 tại đây: https://www.fileformat.info/info/charset/UTF-16/list.htm

Một ký tự của loại dữ liệu char được bao quanh bởi dấu nháy đơn. Bằng cách chuyển đổi (casting) bằng (int) hoặc (char), các giá trị riêng lẻ có thể được chuyển đổi thành các ký tự char và các ký tự char có thể được chuyển đổi thành các giá trị tương ứng.

Kết quả sẽ trông như sau (65 là giá trị số ASCII của ký tự 'A'):

Kết quả: A

Điều quan trọng là phân biệt rõ ràng giữa một giá trị và một ký tự:

char myChar = 'A'; // Đúng - khai báo ký tự int myInt = 65; // Đúng - khai báo giá trị

Vì các ký tự char được xử lý như là các số, chúng ta cũng có thể thực hiện các phép tính với chúng:

char myChar = 'A'; int myInt = myChar + 1; System.out.println(myInt); // Kết quả: 66

Loại dữ liệu String

Loại dữ liệu String, so với các loại dữ liệu int, double hoặc char, không phải là một loại dữ liệu đơn giản mà là một lớp (class). Chuỗi có thể được coi là một mảng các ký tự char. Tuy nhiên, để truy cập các giá trị riêng lẻ tại các vị trí mong muốn trong chuỗi, chúng ta không thể sử dụng toán tử mảng [], mà phải sử dụng phương thức charAt(int index) của lớp String, phương thức này sẽ trả về ký tự ở vị trí index:

String myString = "Hello"; char myChar = myString.charAt(0); // Ký tự ở vị trí 0 là 'H'

Vì chúng ta có thể truy cập từng ký tự của một chuỗi, chúng ta có thể giải quyết các tác vụ khác nhau như đếm số ký tự mong muốn, thay thế các ký tự hoặc làm thay đổi hoặc tách một đoạn văn bản.

Strings là các đối tượng, chúng ta làm việc với các đối tượng String

Như đã đề cập ở trên, Strings là các đối tượng được dẫn xuất từ lớp String. Chúng ta sẽ tìm hiểu chi tiết về đối tượng và lớp ở nơi khác, tuy nhiên chúng ta cần chuẩn bị cho một số điều đặc biệt.

So sánh các chuỗi

Chúng ta đã có thể so sánh các biến rất tốt cho đến nay, nhưng việc so sánh các chuỗi khác nhau một chút:

String person1 = "John"; String person2 = "John";  if (person1 == person2) {     System.out.println("Hai người này giống nhau!"); // Kết quả: Hai người này giống nhau! } else {     System.out.println("Hai người này khác nhau!"); }

Đoạn mã trên cho ra kết quả "Hai người này khác nhau!" dù hai chuỗi có giá trị giống nhau. Vì hai đối tượng String này có cùng giá trị, nhưng không được coi là giống nhau. Đối với person1 và person2, mỗi đối tượng sẽ được cấp phát một vị trí bộ nhớ để lưu trữ giá trị. person1 và person2 chỉ là "con trỏ" đến vị trí bộ nhớ mà đối tượng String đó đang lưu trữ. Chúng ta thực sự muốn so sánh giá trị, nhưng trên đây chúng ta chỉ kiểm tra xem hai đối tượng có trỏ đến cùng một vị trí bộ nhớ không. Để so sánh giá trị, chúng ta cần sử dụng phương thức của lớp String (equals). Điều kiện đúng sẽ là:

if (person1.equals(person2)) {     System.out.println("Hai người này giống nhau!"); } else {     System.out.println("Hai người này khác nhau!"); }

Một sự nhầm lẫn nhỏ cho người mới học

Trong các ví dụ trước, chúng ta đã tạo ra chuỗi bằng cách sử dụng từ khóa new - điều mà chúng ta chưa bao giờ sử dụng cho đến bây giờ. Vì vậy, chúng ta sẽ thử lại mà không sử dụng new:

String person1 = "John"; String person2 = "John";  if (person1 == person2) {     System.out.println("Hai người này giống nhau!"); } else {     System.out.println("Hai người này khác nhau!"); // Kết quả: Hai người này giống nhau! }

Ở đây, kết quả là "Hai người này giống nhau!" mặc dù chúng ta đang sử dụng hai đối tượng khác nhau. Lời giải thích nằm ở "Pool của biểu thức" (Literal Pool), đó là một vùng nhớ được lưu trữ trong lớp String, nơi một bản sao của các chuỗi đã được tạo ra trước đó và giống nhau từ mặt từ ngữ được lưu trữ để tiết kiệm bộ nhớ và tăng hiệu suất. Ban đầu, nó trống. Khi một chuỗi mới được tạo ra, nó sẽ kiểm tra xem đã nhập một chuỗi giống nhau trong Pool hay chưa. Nếu có, nó chỉ tạo ra một tham chiếu đến nó (và KHÔNG PHẢI một đối tượng mới), nếu không, nó được thêm vào Pool. Trên ví dụ này, person1 và person2 đều trỏ đến cùng một đối tượng được tham chiếu trong Pool của biểu thức.

Tuy nhiên, khi chúng ta tạo đối tượng String sử dụng new, nó sẽ tạo ra một đối tượng độc lập có vị trí bộ nhớ riêng biệt, và sẽ không giống nhau:

String person1 = new String("John"); String person2 = new String("John");  if (person1 == person2) {     System.out.println("Hai người này giống nhau!"); } else {     System.out.println("Hai người này khác nhau!"); // Kết quả: Hai người này khác nhau! }

Chuỗi và phép gán

Vấn đề khác xảy ra khi gán các chuỗi. Các dòng mã sau:

String person1 = "John"; String person2 = "Doe";  person1 = person2; System.out.println(person1); // Kết quả: Doe

Với câu lệnh person1 = person2, đối tượng person1 (con trỏ) sẽ được ghi đè (gán trỏ) bởi đối tượng person2. Cả hai đều trỏ đến cùng một vị trí bộ nhớ, tức là vị trí mà person2 đang trỏ đến. Quan trọng hơn, giá trị của person1 đã bị mất.

Để sao chép nội dung của một chuỗi vào chuỗi khác, chúng ta sử dụng phương thức valueOf (có cách khác cũng có thể):

String person1 = "John"; String person2 = "Doe";  person1 = String.valueOf(person2); System.out.println(person1); // Kết quả: Doe

Các phương thức quan trọng của lớp String

Vì chuỗi là đối tượng của lớp String, tất cả các chuỗi đều có nhiều phương thức quan trọng, sau đây là những phương thức quan trọng nhất:

length()

Phương thức length() trả về độ dài của một chuỗi:

String myString = "Hello"; int length = myString.length(); System.out.println(length); // Kết quả: 5

charAt(int index)

Phương thức charAt(int index) trả về ký tự tại vị trí index trong chuỗi:

String myString = "Hello"; char myChar = myString.charAt(1); // Trả về ký tự tại vị trí 1, tức là 'e'

substring(int i, int j)

Phương thức substring(int i, int j) sao chép một phần của chuỗi ban đầu. i là chỉ số ký tự đầu tiên và j-1 là chỉ số ký tự cuối cùng:

String myString = "Hello, world!"; String subString = myString.substring(7, 12); // Sao chép từ ký tự thứ 7 đến ký tự thứ 11 System.out.println(subString); // Kết quả: world

equals(String s)

Phương thức equals(String s) so sánh nội dung của hai chuỗi:

String person1 = "John"; String person2 = "Doe";  if (person1.equals(person2)) {     System.out.println("Hai người này giống nhau!"); } else {     System.out.println("Hai người này khác nhau!"); // Kết quả: Hai người này khác nhau! }

toUpperCase() / toLowerCase()

Phương thức toUpperCase() chuyển đổi tất cả các ký tự trong chuỗi thành ký tự viết hoa, và phương thức toLowerCase() chuyển đổi tất cả các ký tự trong chuỗi thành ký tự viết thường:

String myString = "Hello"; String upperCaseString = myString.toUpperCase(); // Chuyển đổi thành "HELLO" String lowerCaseString = myString.toLowerCase(); // Chuyển đổi thành "hello"

compareTo(String s)

Phương thức compareTo(String s) xác định thứ tự từ điển của chuỗi so với s:

String string1 = "apple"; String string2 = "banana"; int result = string1.compareTo(string2); System.out.println(result); // Kết quả: -1 (vì "apple" đứng trước "banana" theo thứ tự từ điển)

Phương thức compareTo() cũng có một phiên bản compareToIgnoreCase(), ở đó không quan tâm đến viết hoa viết thường.

indexOf(String s) / indexOf(String s, int index) / lastIndexOf(String s)

Phương thức indexOf(String s) tìm kiếm lần xuất hiện đầu tiên của chuỗi s trong chuỗi hiện tại. Nếu tìm thấy s, nó trả về chỉ số của ký tự khớp đầu tiên, nếu không tìm thấy, nó trả về -1. Phương thức này cũng có một phiên bản nhận một tham số kiểu char. Trong trường hợp này, nó tìm kiếm sự xuất hiện đầu tiên của ký tự đã cho.

Phiên bản thứ hai của indexOf bắt đầu tìm kiếm từ vị trí fromIndex. Nếu nhìn thấy s bắt đầu từ vị trí này, phương thức trả về chỉ số của ký tự khớp đầu tiên, nếu không tìm thấy, nó trả về -1. Phương thức này cũng có một phiên bản nhận một tham số kiểu char. Hành vi của nó tương tự như trước.

Phương thức lastIndexOf tìm sự xuất hiện cuối cùng của chuỗi con s trong chuỗi hiện tại. Nếu tìm thấy s, nó trả về chỉ số của ký tự khớp đầu tiên, nếu không tìm thấy, nó trả về -1. Có hai phiên bản của phương thức này, một phiên bản nhận một tham số kiểu char và một tham số thứ hai xác định vị trí bắt đầu tìm kiếm.

replace(char oldchar, char newchar)

Phương thức replace(char oldchar, char newchar) thực hiện một quá trình thay thế từng ký tự trong chuỗi hiện tại. Mỗi lần gặp oldchar, nó sẽ được thay thế bằng newchar:

String myString = "Hello, world!"; String replacedString = myString.replace('o', '0'); // Thay thế 'o' bằng '0' System.out.println(replacedString); // Kết quả: Hell0, w0rld!
1