;

Tổng Quan Về String (Chuỗi)

Ⅰ. String (Chuỗi) là gì?

Trong Java, String là một chuỗi các ký tự. Nó được sử dụng để biểu diễn văn bản và là một trong những kiểu dữ liệu cơ bản và quan trọng nhất. Mặc dù được coi là một kiểu dữ liệu nguyên thủy, nhưng String thực chất là một đối tượng, thuộc lớp java.lang.String.

Ví dụ

String str = "abc";

Tương đương với:

Ví dụ

char data[] = {'a', 'b', 'c'};
String str = new String(data);

Sau đây là một số ví dụ khác về cách sử dụng chuỗi:

Ví dụ

System.out.println("abc");
String cde = "cde";
System.out.println("abc" + cde);
String c = "abc".substring(2,3);
String d = cde.substring(1, 2);

Ⅱ. Các tính chất của String (Chuỗi) trong Java

Chuỗi trong Java (được đại diện bởi lớp java.lang.String) là một trong những kiểu dữ liệu cơ bản và quan trọng nhất. Chúng ta thường sử dụng chuỗi để biểu diễn văn bản, từ các câu đơn giản cho đến những đoạn văn phức tạp.

Các tính chất của String trong Java - minh họa
Ảnh mô tả các tính chất của String.

Một trong những đặc điểm nổi bật nhất của chuỗi trong Java chính là tính bất biến. Dưới đây là những tính chất quan trọng của chuỗi, chúng ta hãy cùng tìm hiểu sâu hơn về tính chất này và các đặc điểm khác của chuỗi nhé:

1. Tính bất biến (Immutable) của String (Chuỗi)

Tính bất biến là một đặc tính quan trọng của lớp java.lang.String trong Java. Điều này có nghĩa là một khi một đối tượng String được tạo ra, giá trị của nó không thể thay đổi. Bất kỳ thao tác nào trên chuỗi sẽ tạo ra một chuỗi mới và gán nó cho một biến khác, chứ không thay đổi giá trị của chuỗi ban đầu.

Dưới đây là một ví dụ đơn giản để minh họa cách hoạt động của tính bất biến trong lớp String:

Ví dụ: Example.java

public class Example {
    public static void main(String[] args) {
        // Khởi tạo một chuỗi ban đầu
        String originalString = "Hello";

        // Tạo một chuỗi mới bằng cách nối thêm
        String newString = originalString + " World";

        // In ra giá trị của cả hai chuỗi
        System.out.println("String mới: " + newString); 
        System.out.println("String ban đầu: " + originalString); 
    }
}

Kết quả của chương trình là:

String mới: Hello World
String ban đầu: Hello

Trong ví dụ này:

Không thay đổi đối tượng cũ: Khi bạn thực hiện phép nối chuỗi hoặc bất kỳ thao tác nào khác trên chuỗi, đối tượng chuỗi cũ không bị thay đổi. Một đối tượng chuỗi mới được tạo ra để chứa kết quả của thao tác đó.

Tạo đối tượng mới: Thao tác originalString + " World" không thay đổi nội dung của originalString. Thay vào đó, nó tạo ra một đối tượng chuỗi mới "Hello World" và gán cho biến newString.

Tính bất biến của chuỗi giúp đảm bảo rằng các đối tượng chuỗi không bị thay đổi ngẫu nhiên trong quá trình xử lý, làm cho chúng trở nên an toàn và dễ dự đoán hơn khi làm việc trong các ứng dụng phức tạp.

Tại sao các đối tượng String là bất biến trong Java?

1. Lý do đối tượng chuỗi là bất biến

↳ String Literal: Java sử dụng khái niệm "String literal". Ví dụ, nếu có 5 biến tham chiếu đều trỏ đến cùng một đối tượng chuỗi "Hello", nếu một biến thay đổi giá trị của đối tượng, tất cả các biến tham chiếu khác sẽ bị ảnh hưởng. Để tránh vấn đề này, chuỗi trong Java là bất biến. Khi chuỗi đã được tạo ra, nó không thể bị thay đổi.

2. Các tính năng giúp đối tượng chuỗi bất biến

ClassLoader

↳ Khái niệm: Một ClassLoader trong Java sử dụng đối tượng chuỗi như một tham số. Nếu đối tượng chuỗi có thể thay đổi, giá trị của nó có thể bị thay đổi và lớp cần được nạp có thể bị khác đi.

↳ Giải pháp: Để tránh sự hiểu lầm này, chuỗi được thiết kế là bất biến. Điều này đảm bảo rằng giá trị của chuỗi không thay đổi và lớp được nạp đúng như dự kiến.

An toàn đa luồng (Thread Safety)

↳ Khái niệm: Vì đối tượng chuỗi là bất biến, chúng ta không cần phải lo lắng về đồng bộ hóa khi chia sẻ đối tượng chuỗi giữa nhiều luồng.

↳ Giải pháp: Điều này giúp bảo đảm rằng không cần đồng bộ hóa khi làm việc với chuỗi trong môi trường đa luồng, làm cho chương trình dễ dàng và an toàn hơn khi chạy đồng thời.

Bảo mật

↳ Khái niệm: Trong trường hợp nạp lớp, đối tượng chuỗi bất biến giúp tránh các lỗi thêm bằng cách đảm bảo nạp lớp chính xác. Điều này làm cho ứng dụng trở nên an toàn hơn.

↳ Ví dụ: Trong phần mềm ngân hàng, tên người dùng và mật khẩu không thể bị thay đổi bởi kẻ tấn công vì đối tượng chuỗi là bất biến. Điều này làm cho ứng dụng bảo mật hơn.

Tiết kiệm bộ nhớ (Heap Space)

↳ Khái niệm: Tính bất biến của chuỗi giúp giảm sử dụng bộ nhớ heap. Khi tạo một đối tượng chuỗi mới, JVM kiểm tra xem giá trị đã tồn tại trong String Pool hay chưa. Nếu đã tồn tại, giá trị đó sẽ được gán cho đối tượng mới.

↳ Giải pháp: Tính năng này cho phép Java sử dụng bộ nhớ heap một cách hiệu quả, vì các chuỗi giống nhau không được lưu trữ nhiều lần.

2. Lưu trữ trong String Pool

Các đối tượng String trong Java được lưu trữ trong một vùng nhớ đặc biệt gọi là "String Pool" hoặc "Pool hằng số chuỗi (String constant pool)". String Pool là một vùng nhớ đặc biệt trong heap của Java, được dùng để lưu trữ các đối tượng String. Mục đích chính của nó là tối ưu hóa việc sử dụng bộ nhớ và tăng hiệu suất khi làm việc với các chuỗi.

Khi bạn khai báo một String bằng cách sử dụng literal (ví dụ: String str1 = "Xin chào";), JVM sẽ trước tiên tìm kiếm trong String Pool xem đã có đối tượng String nào có giá trị giống như vậy chưa. Nếu tìm thấy, biến str sẽ tham chiếu đến đối tượng đó trong Pool. Nếu không tìm thấy, JVM sẽ tạo một đối tượng String mới trong Pool và cho biến str tham chiếu đến đối tượng này.

Ví dụ String Literal được tạo bằng cách sử dụng dấu nháy kép:

Ví dụ

String str1 = "Xin chào";
String str2 = "Xin chào"; // Không tạo ra một thể hiện (instance) mới

Trong ví dụ trên, chỉ có một đối tượng được tạo ra. Đầu tiên, JVM sẽ không tìm thấy bất kỳ đối tượng chuỗi nào có giá trị "Xin chào" trong bộ nhớ đệm chuỗi, đó là lý do tại sao nó sẽ tạo ra một đối tượng mới. Sau đó, nó sẽ tìm thấy chuỗi có giá trị "Xin chào" trong bộ nhớ đệm, nó sẽ không tạo ra một đối tượng mới mà sẽ trả về tham chiếu đến cùng một thể hiện (instance).

Tại sao lớp string là final trong Java?

Bảo vệ tính bất biến: Để đảm bảo rằng không ai có thể thay đổi hành vi của chuỗi bằng cách kế thừa và ghi đè các phương thức của nó, lớp String được khai báo là final. Điều này giữ nguyên tính bất biến của chuỗi.

JVM kiểm tra 'bộ nhớ đệm chuỗi' (string constant pool) - minh họa
Ảnh mô tả JVM kiểm tra "bộ nhớ đệm chuỗi" (string constant pool).

Tóm lại

Chuỗi literal là chuỗi được xác định trực tiếp trong mã nguồn bằng dấu nháy kép.

JVM tối ưu hóa việc tạo chuỗi bằng cách sử dụng vùng nhớ đặc biệt gọi là "String Pool" hoặc "Pool hằng số chuỗi (String constant pool)".

Nếu chuỗi đã tồn tại trong "String Pool" hoặc "Pool hằng số chuỗi (String constant pool)", sẽ không tạo đối tượng mới mà chỉ trả về tham chiếu đến đối tượng cũ.

Điều này giúp tiết kiệm bộ nhớ và tăng hiệu suất.

3. Đối tượng (Object)

Trong Java, String là một đối tượng, không phải là kiểu dữ liệu nguyên thủy. Điều này có nghĩa là bạn có thể sử dụng các phương thức và thuộc tính của đối tượng với chuỗi.

Kế thừa: Chuỗi kế thừa từ lớp Object, do đó nó có thể sử dụng các phương thức như equals(), hashCode(), toString().

Để minh họa rằng trong Java, String là một đối tượng và không phải là kiểu dữ liệu nguyên thủy, hãy xem ví dụ cụ thể về cách bạn có thể sử dụng các phương thức và thuộc tính của đối tượng với chuỗi.

Trong ví dụ dưới đây, chúng ta sẽ sử dụng một số phương thức của lớp String và thuộc tính của lớp Object để làm việc với chuỗi.

Ví dụ: Example.java

public class Example {
    public static void main(String[] args) {
        // Tạo một đối tượng chuỗi
        String myString = "Hello, World!";

        // Sử dụng các phương thức của lớp String
        // 1. Phương thức length() - trả về độ dài của chuỗi
        int length = myString.length();
        System.out.println("Độ dài của chuỗi: " + length);

        // 2. Phương thức toUpperCase() - chuyển đổi chuỗi thành chữ hoa
        String upperCaseString = myString.toUpperCase();
        System.out.println("Chuyển đổi chuỗi thành chữ hoa: " + upperCaseString);

        // 3. Phương thức substring() - trích xuất một phần của chuỗi
        String substring = myString.substring(7, 12);
        System.out.println("Trích xuất một phần của chuỗi: " + substring);

        // 4. Phương thức replace() - thay thế ký tự hoặc chuỗi
        String replacedString = myString.replace("World", "Java");
        System.out.println("Thay thế ký tự: " + replacedString);

        // Sử dụng các phương thức của lớp Object
        // 5. Phương thức hashCode() - trả về mã băm của đối tượng
        int hashCode = myString.hashCode();
        System.out.println("mã băm của chuỗi: " + hashCode);

        // 6. Phương thức equals() - so sánh hai chuỗi
        boolean isEqual = myString.equals("Hello, World!");
        System.out.println("chuỗi myString có bằng chuổi 'Hello, World!': " + isEqual);

        // 7. Phương thức toString() - trả về chuỗi đại diện cho đối tượng
        String stringRepresentation = myString.toString();
        System.out.println("Chuỗi đại diện cho đối tượng: " + stringRepresentation);
    }
}

Kết quả của chương trình là:

Độ dài của chuỗi: 13
Chuyển đổi chuỗi thành chữ hoa: HELLO, WORLD!
Trích xuất một phần của chuỗi: World
Thay thế ký tự: Hello, Java!
mã băm của chuỗi: 1498789909
chuỗi myString có bằng chuổi 'Hello, World!': true
Chuỗi đại diện cho đối tượng: Hello, World!

Hy vọng rằng ví dụ này giúp bạn hiểu rõ hơn về việc sử dụng String như một đối tượng trong Java!

4. Tuần tự hóa (Serializable)

Lớp String có thể triển khai interface Serializable, nghĩa là các đối tượng chuỗi có thể được tuần tự hóa thành một luồng byte[] để lưu vào trữ tệp, cơ sở dữ liệu hoặc truyền qua mạng.

Dưới đây là một ví dụ về cách tuần tự hóa (Serializable) một đối tượng chuỗi (String) trong Java:

Ví dụ: Example.java

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Exemple {
    public static void main(String[] args) {
        String myString = "Hello, World!";
        
        try (FileOutputStream fileOut = new FileOutputStream("string.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            
            out.writeObject(myString);
            System.out.println("Chuỗi đã được tuần tự hóa");
            
        } catch (IOException i) {
            i.printStackTrace();
        }
    }
}

Kết quả của chương trình là:

Chuỗi đã được tuần tự hóa

Ví dụ trên minh họa cách sử dụng tuần tự hóa (Serializable) để lưu trữ và đọc đối tượng chuỗi (String) từ tệp. Điều này có thể hữu ích khi bạn cần lưu trữ trạng thái của chuỗi vào tệp hoặc truyền chuỗi qua mạng.

5. So sánh (Comparable)

So sánh chuỗi là một thao tác thường gặp khi làm việc với văn bản trong Java. Bạn có thể so sánh chuỗi dựa trên nội dung của chúng (ký tự) hoặc dựa trên tham chiếu đến cùng một đối tượng String. So sánh chuỗi được sử dụng trong nhiều tình huống như:

↳ Xác thực (bằng phương thức equals())

↳ Sắp xếp (bằng phương thức compareTo())

↳ Kiểm tra tham chiếu giống nhau (bằng toán tử ==)

Sử dụng phương thức equals()

Phương thức equals() của lớp String so sánh nội dung của hai chuỗi. Nó kiểm tra xem từng ký tự tương ứng có giống nhau hay không.

Lớp String cung cấp hai phương thức equals():

↳ public boolean equals(Object another): So sánh chuỗi này với một đối tượng khác (có thể là String).

↳ public boolean equalsIgnoreCase(String another): So sánh chuỗi này với một chuỗi khác, bỏ qua chữ hoa/thường.

Dưới đây là ví dụ minh họa cách sử dụng phương thức equals() và equalsIgnoreCase() của lớp String trong Java.:

Ví dụ: Example.java

public class Example {
    public static void main(String[] args) {
        // Khởi tạo các chuỗi
        String str1 = "Hello";
        String str2 = "Hello";
        String str3 = "HELLO";
        String str4 = "Java";

        // Sử dụng phương thức equals()
        boolean result1 = str1.equals(str2); // true, vì nội dung của str1 và str2 giống nhau
        boolean result2 = str1.equals(str3); // false, vì chữ hoa và chữ thường khác nhau
        boolean result3 = str1.equals(str4); // false, vì nội dung khác nhau

        // Sử dụng phương thức equalsIgnoreCase()
        boolean result4 = str1.equalsIgnoreCase(str3); // true, vì bỏ qua chữ hoa/thường, nội dung giống nhau

        // In kết quả
        System.out.println("str1 equals str2: " + result1);
        System.out.println("str1 equals str3: " + result2);
        System.out.println("str1 equals str4: " + result3);
        System.out.println("str1 equalsIgnoreCase str3: " + result4);
    }
}

Kết quả của chương trình là:

str1 equals str2: true
str1 equals str3: false
str1 equals str4: false
str1 equalsIgnoreCase str3: true

Ví dụ này minh họa cách sử dụng hai phương thức so sánh chuỗi của lớp String trong Java.

Sử dụng phương thức compareTo()

Phương thức compareTo() so sánh thứ tự từ điển của hai chuỗi. Nó trả về một số nguyên âm nếu chuỗi hiện tại đứng trước chuỗi khác trong bảng chữ cái, 0 nếu hai chuỗi bằng nhau, và một số nguyên dương nếu chuỗi hiện tại đứng sau chuỗi khác.

Phương thức này thường được sử dụng để sắp xếp các chuỗi.

Dưới đây là một ví dụ về việc sử dụng phương thức compareTo() của lớp String để so sánh thứ tự từ điển của hai chuỗi và sắp xếp các chuỗi.

Ví dụ: Example.java

import java.util.Arrays;

public class Example {
    public static void main(String[] args) {
        // Khởi tạo các chuỗi
        String str1 = "Apple";
        String str2 = "Banana";
        String str3 = "Apple";
        String str4 = "Cherry";
        
        // So sánh các chuỗi bằng phương thức compareTo()
        int result1 = str1.compareTo(str2); // Âm, vì "Apple" đứng trước "Banana"
        int result2 = str1.compareTo(str3); // 0, vì "Apple" bằng "Apple"
        int result3 = str2.compareTo(str4); // Âm, vì "Banana" đứng trước "Cherry"
        int result4 = str4.compareTo(str1); // Dương, vì "Cherry" đứng sau "Apple"

        // In kết quả
        System.out.println("str1 compareTo str2: " + result1);
        System.out.println("str1 compareTo str3: " + result2);
        System.out.println("str2 compareTo str4: " + result3);
        System.out.println("str4 compareTo str1: " + result4);

        // Sắp xếp một mảng chuỗi
        String[] fruits = {"Banana", "Apple", "Cherry", "Mango", "Orange"};
        Arrays.sort(fruits);

        // In mảng đã sắp xếp
        System.out.println("fruits đã được sắp xếp: " + Arrays.toString(fruits));
    }
}

Kết quả của chương trình là:

str1 compareTo str2: -1
str1 compareTo str3: 0
str2 compareTo str4: -1
str4 compareTo str1: 2 fruits đã được sắp xếp: [Apple, Banana, Cherry, Mango, Orange]

Ví dụ này minh họa cách sử dụng phương thức compareTo() để so sánh các chuỗi và sắp xếp các chuỗi trong Java.

Sử dụng toán tử ==

Toán tử == kiểm tra xem hai biến có tham chiếu đến cùng một đối tượng String trong bộ nhớ hay không.

Lưu ý: Cách này chỉ nên dùng khi bạn chắc chắn cả hai biến đều tham chiếu đến cùng một đối tượng được tạo ra từ cùng một chuỗi literal.

Dưới đây là một ví dụ về cách sử dụng toán tử == để kiểm tra xem hai biến có tham chiếu đến cùng một đối tượng String trong bộ nhớ hay không. Chúng ta cũng sẽ so sánh kết quả của toán tử == với phương thức equals() để minh họa sự khác biệt giữa hai cách so sánh này.

Ví dụ: Example.java

public class Example {
    public static void main(String[] args) {
        // Khởi tạo các chuỗi bằng cách sử dụng chuỗi literal
        String str1 = "Hello";
        String str2 = "Hello";

        // Khởi tạo các chuỗi bằng cách sử dụng từ khóa new
        String str3 = new String("Hello");
        String str4 = new String("Hello");

        // So sánh các chuỗi bằng toán tử ==
        boolean result1 = (str1 == str2); // true, vì str1 và str2 tham chiếu đến cùng một đối tượng trong bộ nhớ
        boolean result2 = (str1 == str3); // false, vì str1 và str3 tham chiếu đến các đối tượng khác nhau trong bộ nhớ
        boolean result3 = (str3 == str4); // false, vì str3 và str4 tham chiếu đến các đối tượng khác nhau trong bộ nhớ

        // So sánh các chuỗi bằng phương thức equals()
        boolean result4 = str1.equals(str2); // true, vì nội dung của str1 và str2 giống nhau
        boolean result5 = str1.equals(str3); // true, vì nội dung của str1 và str3 giống nhau
        boolean result6 = str3.equals(str4); // true, vì nội dung của str3 và str4 giống nhau

        // In kết quả
        System.out.println("str1 == str2: " + result1);
        System.out.println("str1 == str3: " + result2);
        System.out.println("str3 == str4: " + result3);
        System.out.println("str1 equals str2: " + result4);
        System.out.println("str1 equals str3: " + result5);
        System.out.println("str3 equals str4: " + result6);
    }
}

Kết quả của chương trình là:

str1 == str2: true
str1 == str3: false
str3 == str4: false
str1 equals str2: true
str1 equals str3: true
str3 equals str4: true

Ví dụ này minh họa sự khác biệt giữa việc sử dụng toán tử == và phương thức equals() để so sánh các chuỗi trong Java.

Trường hợp đặc biệt

↳ Chuỗi null: Khi so sánh một chuỗi với null, bạn nên kiểm tra null trước để tránh NullPointerException.

↳ Chuỗi rỗng: Chuỗi rỗng ("") là một chuỗi có độ dài bằng 0.

6. Interface CharSequence

Giao diện CharSequence (Interface CharSequence) là một giao diện đại diện cho một chuỗi các ký tự có thể đọc được. Nó cung cấp quyền truy cập thống nhất, chỉ đọc vào nhiều loại chuỗi ký tự khác nhau. Mỗi ký tự trong chuỗi được biểu diễn bởi một giá trị char. Một giá trị char đại diện cho một ký tự trong Basic Multilingual Plane (BMP) hoặc một ký tự thay thế. Tham khảo Unicode Character Representation để biết thêm chi tiết.

Các Lớp triển khai (implements) Interface CharSequence:

↳ compareTo(ChronoLocalDate other): CharBuffer là một lớp trong gói java.nio và là một phần của API NIO (New Input/Output). Nó cung cấp một bộ đệm các ký tự (char) mà có thể đọc và ghi.

↳ isBefore(ChronoLocalDate other): Segment là một lớp trong gói javax.swing.text. Nó đại diện cho một đoạn của một mảng ký tự và thường được sử dụng trong việc xử lý văn bản trong Swing.

↳ isAfter(ChronoLocalDate other): String là lớp đại diện cho một chuỗi ký tự bất biến (immutable). Nó lưu trữ các ký tự trong một mảng và cung cấp nhiều phương thức để thao tác với chuỗi.

↳ compareTo(ChronoLocalDate other): StringBuffer là một lớp đại diện cho một chuỗi ký tự có thể thay đổi (mutable) và được đồng bộ hóa (synchronized). Nó an toàn trong môi trường đa luồng.

↳ isBefore(ChronoLocalDate other): StringBuilder là một lớp đại diện cho một chuỗi ký tự có thể thay đổi (mutable) nhưng không được đồng bộ hóa (non-synchronized). Nó không an toàn trong môi trường đa luồng nhưng nhanh hơn StringBuffer.

Các lớp triển khai giao diện CharSequence - minh họa
Ảnh mô tả các lớp triển khai giao diện CharSequence.

Các phương thức của giao diện CharSequence

↳ char charAt(int index): Phương thức này trả về giá trị ký tự tại vị trí chỉ định trong chuỗi ký tự.

↳ default IntStream chars(): Phương thức này trả về một luồng (stream) các giá trị int, mỗi giá trị là mở rộng không dấu của các ký tự từ chuỗi này.

↳ default IntStream codePoints(): Phương thức này trả về một luồng (stream) các giá trị mã code point từ chuỗi này.

↳ int length(): Phương thức này trả về độ dài của chuỗi ký tự.

↳ CharSequence subSequence(int start, int end): Phương thức này trả về một CharSequence là một chuỗi con của chuỗi hiện tại, bắt đầu từ vị trí start và kết thúc tại vị trí end.

7. Hỗ trợ quốc tế hóa (Internationalization)

Để ứng dụng Java có thể hoạt động tốt với nhiều ngôn ngữ và vùng miền khác nhau, Java hỗ trợ mạnh mẽ quốc tế hóa (internationalization). Điều này có nghĩa là ứng dụng có thể tự động điều chỉnh giao diện, định dạng dữ liệu, đơn vị đo lường... cho phù hợp với ngôn ngữ và vùng miền của người dùng.

Khái niệm cơ bản

Quốc tế hóa (Internationalization) hay i18n là quá trình thiết kế một ứng dụng để có thể dễ dàng thích ứng với các ngôn ngữ và khu vực địa lý khác nhau mà không cần sửa đổi code.

Địa phương hóa (Localization) hay l10n là quá trình điều chỉnh ứng dụng cho một ngôn ngữ hoặc khu vực địa lý cụ thể.

Các yếu tố chính trong hỗ trợ quốc tế hóa của Java

Unicode:

Chuỗi trong Java sử dụng mã hóa Unicode, cho phép biểu diễn hầu hết các ký tự của mọi ngôn ngữ trên thế giới. Nhờ đó, bạn có thể lưu trữ và xử lý văn bản đa ngôn ngữ một cách dễ dàng.

Các lớp hỗ trợ:

↳ Locale: Đại diện cho một ngôn ngữ, vùng miền hoặc cả hai. Ví dụ: Locale.US, Locale.FRANCE.

↳ ResourceBundle: Dùng để quản lý các tài nguyên (như chuỗi, hình ảnh) cho các locale khác nhau.

↳ NumberFormat: Định dạng số theo quy ước của từng locale.

↳ DateFormat: Định dạng ngày tháng theo quy ước của từng locale.

↳ DecimalFormat: Định dạng số theo kiểu thập phân.

↳ MessageFormat: Định dạng các chuỗi phức tạp.

Chuỗi trong Java sử dụng mã hóa Unicode, cho phép biểu diễn hầu hết các ký tự của mọi ngôn ngữ trên thế giới. Nhờ đó, bạn có thể lưu trữ và xử lý văn bản đa ngôn ngữ một cách dễ dàng.

Ví dụ: Example.java

import java.util.Locale;
import java.util.ResourceBundle;

public class Example {
    public static void main(String[] args) {
        // Chọn locale (tiếng Anh hoặc tiếng Việt)
        Locale locale = Locale.getDefault(); // Hoặc new Locale("vi", "VN") để chọn tiếng Việt
        ResourceBundle bundle = ResourceBundle.getBundle("MessagesBundle", locale);
        
        // In thông điệp dựa trên locale
        System.out.println(bundle.getString("greeting"));  // In ra thông điệp chào
        System.out.println(bundle.getString("farewell"));  // In ra thông điệp tạm biệt
    }
}

Kết quả của chương trình là:

Xin chào
tạm biệt

Ⅲ. Tạo đối tượng String

Trong Java, bạn có thể tạo đối tượng String theo hai cách:

↳ Sử dụng string literal

↳ Sử dụng từ khóa new.

Dưới đây là giải thích chi tiết cùng với các ví dụ:

1. Sử dụng string literal

String Literal được tạo bằng cách sử dụng dấu nháy kép.

Ví dụ: Example.java

String str1 = "Xin chào";

Giải thích: Khi bạn đặt một chuỗi văn bản trong dấu ngoặc kép (""), Java sẽ tự động tạo ra một đối tượng String và gán nó cho biến str.

Các đối tượng String trong Java được lưu trữ trong một vùng nhớ đặc biệt gọi là "String Pool" hoặc "Pool hằng số chuỗi (String constant pool)". String Pool là một vùng nhớ đặc biệt trong heap của Java, được dùng để lưu trữ các đối tượng String. Mục đích chính của nó là tối ưu hóa việc sử dụng bộ nhớ và tăng hiệu suất khi làm việc với các chuỗi.

Khi bạn khai báo một String bằng cách sử dụng literal (ví dụ: String str1 = "Xin chào";), JVM sẽ trước tiên tìm kiếm trong String Pool xem đã có đối tượng String nào có giá trị giống như vậy chưa. Nếu tìm thấy, biến str sẽ tham chiếu đến đối tượng đó trong Pool. Nếu không tìm thấy, JVM sẽ tạo một đối tượng String mới trong Pool và cho biến str tham chiếu đến đối tượng này.

Ví dụ: Example.java

String str1 = "Xin chào";
String str2 = "Xin chào"; // Không tạo ra một thể hiện (instance) mới

Trong ví dụ trên, chỉ có một đối tượng được tạo ra. Đầu tiên, JVM sẽ không tìm thấy bất kỳ đối tượng chuỗi nào có giá trị "Xin chào" trong bộ nhớ đệm chuỗi, đó là lý do tại sao nó sẽ tạo ra một đối tượng mới. Sau đó, nó sẽ tìm thấy chuỗi có giá trị "Xin chào" trong bộ nhớ đệm, nó sẽ không tạo ra một đối tượng mới mà sẽ trả về tham chiếu đến cùng một thể hiện (instance).

JVM kiểm tra 'bộ nhớ đệm chuỗi' (string constant pool) - minh họa
Cách mô tả JVM kiểm tra "bộ nhớ đệm chuỗi" (string constant pool).

Tóm lại:

↳ Chuỗi literal là chuỗi được xác định trực tiếp trong mã nguồn bằng dấu nháy kép.

↳ JVM tối ưu hóa việc tạo chuỗi bằng cách sử dụng vùng nhớ đặc biệt gọi là "String Pool" hoặc "Pool hằng số chuỗi (String constant pool)".

↳ Nếu chuỗi đã tồn tại trong "String Pool" hoặc "Pool hằng số chuỗi (String constant pool)", sẽ không tạo đối tượng mới mà chỉ trả về tham chiếu đến đối tượng cũ.

↳ Điều này giúp tiết kiệm bộ nhớ và tăng hiệu suất.

2. Sử dụng từ khóa new.

Khi bạn sử dụng từ khóa new để tạo một đối tượng String. Một đối tượng chuỗi mới sẽ được tạo trong heap memory, cho dù chuỗi đó có tồn tại trong pool chuỗi hay không. Điều này có nghĩa là bạn đang tạo một đối tượng String hoàn toàn mới, không liên quan đến String Pool.

Cú pháp

String str3 = new String("Xin chào");

Đặc điểm:

↳ Không nhất thiết phải intern: Không giống như String literal, các đối tượng String được tạo bằng new không nhất thiết phải được đưa vào String Pool. Việc đưa vào Pool phụ thuộc vào cài đặt của JVM và nội dung của chuỗi.

↳ Ít hiệu quả: Việc tạo đối tượng bằng new thường chậm hơn so với sử dụng literal và có thể tiêu tốn nhiều bộ nhớ hơn, đặc biệt khi tạo nhiều đối tượng String tương tự nhau.

Ví dụ

// Ví dụ sử dụng String Literal và new String
String str1 = "Xin chào";
String str2 = "Xin chào";
String str3 = new String("Xin chào");

// Kiểm tra các chuỗi trong String Pool và bộ nhớ heap
System.out.println(str1 == str2); // Kết quả: true (cùng trong String Pool)
System.out.println(str1 == str3); // Kết quả: false (khác vùng nhớ)

Trong ví dụ trên, str1 và str2 trỏ đến cùng một đối tượng trong String Pool, trong khi str3 trỏ đến một đối tượng mới trong bộ nhớ heap.

Tóm tắt

↳ Lớp String cung cấp nhiều phương thức để thao tác với chuỗi ký tự và thực hiện các hoạt động như so sánh, nối, và thay thế.

↳ Giao diện CharSequence cho phép bạn đọc và làm việc với chuỗi ký tự mà không cần biết loại cụ thể của lớp thực hiện (như String, StringBuffer, hay StringBuilder).

↳ Tính Bất Biến của String nghĩa là khi bạn thực hiện thay đổi, một chuỗi mới sẽ được tạo ra.

"Trong các chương tiếp theo, chúng ta sẽ khám phá sâu hơn về các lớp trong thư viện java.lang như String, StringBuffer, và StringBuilder—những công cụ mạnh mẽ để làm việc với chuỗi trong Java. Bạn sẽ được làm quen với cách sử dụng các lớp này để tạo, sửa đổi và xử lý chuỗi một cách hiệu quả."

Câu Nói Truyền Cảm Hứng

“Bắt đầu ở đâu không quan trọng, quan trọng là bạn sẵn sàng bắt đầu.” – W. Clement Stone

Không Gian Tích Cực

“Chúc bạn luôn giữ vững niềm tin và sức mạnh để vượt qua mọi thử thách trong cuộc sống.”