(+84) 236.3827111 ex. 402

Substring Java: Cách dùng từ A-Z và lỗi thường gặp


Cú pháp và cách sử dụng substring() trong Java kèm ví dụ

Phương thức substring() là công cụ cốt lõi của lớp String trong Java, được thiết kế để trích xuất (cắt) một phần của chuỗi gốc thành một chuỗi con (sub-string) mới. Đây là giải pháp hiệu quả và chính xác nhất giúp lập trình viên thao tác và xử lý các phân đoạn dữ liệu văn bản, phục vụ các tác vụ phổ biến như rút gọn nội dung, trích xuất mã định danh, hay lấy tên miền từ email.

Trong Java, lớp String cung cấp hai biến thể (overloading) của phương thức substring():

substring(int beginIndex)
substring(int beginIndex, int endIndex)

Copy

Tùy vào mục đích cần cắt chuỗi từ một vị trí cố định hay trích xuất một đoạn nằm giữa, bạn có thể lựa chọn cú pháp phù hợp.

substring(int beginIndex)

Phương thức này được dùng khi bạn muốn lấy toàn bộ phần còn lại của chuỗi kể từ một vị trí xác định.

Cơ chế: Trích xuất các ký tự bắt đầu từ chỉ số beginIndex cho đến ký tự cuối cùng của chuỗi.

Ví dụ: 

```java
String str = "Hello";
String result = str.substring(2); 
// Kết quả: "llo"

Copy

Giải thích: Java bắt đầu đếm từ 0 (H=0, e=1, l=2). Tại vị trí index 2 là chữ ‘l’, phương thức sẽ lấy từ đó đến hết.

substring(int beginIndex, int endIndex)

Đây là biến thể linh hoạt hơn, cho phép bạn cắt chính xác một đoạn văn bản nằm giữa chuỗi.

Cơ chế: Trích xuất từ vị trí beginIndex đến trước vị trí endIndex.

Lưu ý quan trọng: Phương thức này bao gồm ký tự tại beginIndex nhưng không bao gồm ký tự tại endIndex (tương đương với nửa khoảng $[beginIndex, endIndex)$).

Công thức tính độ dài chuỗi con:

$$\text{Length} = \text{endIndex} - \text{beginIndex}$$

Copy

Ví dụ:

String text = "Hello World";
String sub = text.substring(0, 5);
// Kết quả: "Hello"

Copy

Giải thích: Lấy từ index 0 đến index 4. Ký tự tại index 5 (khoảng trắng) sẽ bị loại bỏ.

Cơ chế hoạt động của substring() trong Java

Để sử dụng substring() một cách tối ưu, lập trình viên cần hiểu rõ những gì thực sự xảy ra bên trong bộ nhớ (Heap Memory).

Tính bất biến (Immutability)

Một đặc điểm cốt lõi của lớp String trong Java là tính bất biến. Điều này có nghĩa là khi bạn gọi phương thức substring(), chuỗi gốc hoàn toàn không bị thay đổi hay bị “cắt” đi.

Thay vào đó, Java sẽ khởi tạo một đối tượng String hoàn toàn mới trong Memory để chứa nội dung vừa trích xuất. Vì vậy, nếu bạn muốn sử dụng chuỗi con này, bạn phải gán nó cho một biến mới:

String original = "Java Programming";
String sub = original.substring(0, 4); 
System.out.println(original); // Vẫn là "Java Programming"
System.out.println(sub);      // "Java"

Copy

Sự thay đổi cơ chế qua các phiên bản Java

Cơ chế quản lý bộ nhớ của substring() đã có sự thay đổi đáng chú ý qua các phiên bản Java:

  • Trước Java 7u6: Phương thức substring() tạo ra một String mới nhưng vẫn chia sẻ chung mảng ký tự (char[] value) với chuỗi gốc. Điều này giúp tiết kiệm bộ nhớ và thời gian nhưng lại gây ra lỗi Memory Leak (Rò rỉ bộ nhớ). 

Ví dụ: Bạn chỉ lấy một chuỗi con rất nhỏ từ một chuỗi khổng lồ, nhưng chuỗi khổng lồ đó vẫn không được bộ thu gom rác (GC) giải phóng vì chuỗi con vẫn đang giữ tham chiếu đến mảng ký tự của nó.

  • Từ Java 7u6 trở đi: Để khắc phục vấn đề trên, Java đã thay đổi cơ chế. 

Mỗi khi gọi substring(), một mảng ký tự mới được copy hoàn toàn. Điều này giúp chuỗi con độc lập hoàn toàn với chuỗi gốc, cho phép chuỗi gốc được giải phóng khỏi bộ nhớ nếu không còn được sử dụng, giúp ứng dụng hoạt động ổn định hơn.

Các lỗi thường gặp với substring() và cách xử lý

Dù là một phương thức cơ bản, substring() vẫn thường xuyên gây ra những lỗi khiến ứng dụng bị dừng đột ngột (crash). Dưới đây là những lỗi phổ biến nhất và cách xử lý chuyên nghiệp:

Ngoại lệ StringIndexOutOfBoundsException

Đây là lỗi thường gặp phổ biến nhất. Lỗi này xảy ra khi các tham số bạn truyền vào phương thức không nằm trong phạm vi hợp lệ của chuỗi.

Nguyên nhân:

  • beginIndex nhỏ hơn 0.
  • endIndex lớn hơn độ dài thực tế của chuỗi (str.length()).
  • beginIndex lại lớn hơn endIndex.

Ứng dụng của substring() trong Java trong thực tế

Không chỉ đơn thuần là cắt chuỗi, phương thức substring() là công cụ mạnh mẽ, kết hợp với các phương thức khác (như indexOf() hay length()), để giải quyết nhiều bài toán xử lý dữ liệu chuỗi (String) thực tế trong ứng dụng:

Trích xuất Tên miền từ Địa chỉ Email

Thường được dùng để lấy phần tên miền của một địa chỉ email. Bạn sẽ dùng indexOf() để tìm vị trí ký tự @ và bắt đầu cắt chuỗi từ vị trí tiếp theo.

String email = "laptrinhvien@domain.com";
int atIndex = email.indexOf('@'); // Vị trí của ký tự '@'

if (atIndex != -1 && atIndex < email.length() - 1) {
    String domain = email.substring(atIndex + 1);
    // Kết quả: "domain.com"
} else {
    // Xử lý trường hợp email không hợp lệ (không có '@' hoặc '@' ở cuối)
}

Copy

Chuẩn hóa hoặc Trích xuất Mã định danh (Format User ID/Code)

Trong các hệ thống quản lý, mã định danh (ID) thường có cấu trúc cố định. Substring giúp bạn trích xuất phần thông tin mong muốn, ví dụ như số thứ tự, mã loại, hoặc ngày tháng:

String fullCode = "INV-2024-01-04567"; // Mã hóa đơn: Loại - Năm - Tháng - STT

// Trích xuất 5 ký tự cuối (Số thứ tự):
String sequenceNumber = fullCode.substring(fullCode.length() - 5);
// Kết quả: "04567"

// Trích xuất Mã loại (Phần đầu tiên trước dấu gạch ngang đầu tiên):
String typeCode = fullCode.substring(0, fullCode.indexOf('-'));
// Kết quả: "INV"

Copy

Rút gọn nội dung để hiểnthị

Đây là trường hợp phổ biến khi bạn cần hiển thị bản xem trước (preview) hoặc tóm tắt một đoạn văn bản quá dài trên giao diện người dùng (ví dụ: hiển thị mô tả sản phẩm trên trang kết quả tìm kiếm).

String longText = "Đây là một đoạn văn bản rất dài cần được rút gọn để hiển thị trên giao diện người dùng...";
int maxLength = 100;

if (longText.length() > maxLength) {
    // Cắt chuỗi từ 0 đến vị trí maxLength
    String shortText = longText.substring(0, maxLength) + "...";
    // Kết quả: 100 ký tự đầu tiên + "..."
} else {
    String shortText = longText;
}

Copy

Cách phòng tránh: Luôn thực hiện kiểm tra độ dài chuỗi hoặc sử dụng phương thức Math.min() để đảm bảo chỉ số luôn nằm trong vùng an toàn.

Ví dụ:

String data = "Java";
// Lỗi: data.substring(0, 10); -> Quá độ dài chuỗi

// Cách xử lý an toàn:
int end = 10;
if (end <= data.length()) {
    String sub = data.substring(0, end);
}

Copy

Nhầm lẫn về nguyên tắc “Inclusive – Exclusive”

Nhiều lập trình viên mới thường nhầm rằng endIndex là vị trí của ký tự cuối cùng họ muốn lấy. Tuy nhiên, như đã đề cập, trong phương thức substring(beginIndex, endIndex), Java áp dụng quy tắc inclusive – exclusive (bao gồm chỉ số bắt đầu nhưng loại trừ chỉ số kết thúc).

  • Inclusive (Bắt đầu): Ký tự tại vị trí beginIndex được lấy.
  • Exclusive (Kết thúc): Ký tự tại vị trí endIndex bị bỏ qua.

Ví dụ: Nếu bạn muốn lấy ký tự tại vị trí số 5, bạn phải truyền vào endIndex là 6.

Mẹo ghi nhớ: Hãy luôn nhớ công thức $\text{độ dài chuỗi con} = \text{endIndex} – \text{beginIndex}$. 

Ví dụ: Nếu bạn muốn lấy 3 ký tự tính từ vị trí 0, chỉ số kết thúc phải là $0 + 3 = 3$ (str.substring(0, 3)).

Xử lý chuỗi null hoặc chuỗi rỗng

Gọi .substring() trên một biến String có giá trị null sẽ ngay lập tức ném ra NullPointerException gây lỗi dừng app đột ngột.

Giải pháp: Luôn sử dụng kỹ thuật “Defensive Programming” bằng cách kiểm tra str != null hoặc sử dụng các thư viện hỗ trợ như StringUtils.substring() của Apache Commons để code an toàn hơn.

So sánh substring() trong String, StringBuilder và StringBuffer

Dù cả ba lớp này đều cung cấp phương thức substring(), nhưng cách chúng xử lý dữ liệu và tác động đến hiệu năng lại có sự khác biệt rõ rệt.

Bảng so sánh nhanh:

Đặc điểm String StringBuilder StringBuffer
Tính bất biến Immutable (Bất biến) Mutable (Thay đổi được) Mutable (Thay đổi được)
An toàn luồng Có (Thread-safe) Không (Not Thread-safe) Có (Thread-safe)
Hiệu năng Trung bình Cao nhất Trung bình
Kết quả trả về String mới String mới String mới

Một lưu ý quan trọng mà nhiều lập trình viên hay nhầm lẫn: Mặc dù StringBuilder và StringBuffer dùng để chỉnh sửa chuỗi hiện có, nhưng phương thức substring() của chúng vẫn trả về một đối tượng Stringmới. Nó không cắt trực tiếp trên vùng nhớ đệm (buffer) hiện tại mà tạo ra một bản sao định dạng String.

Các câu hỏi thường gặp về substring() Java

Khi nào nên dùng substring() của StringBuilder?

Việc sử dụng substring() của StringBuilder giúp tối ưu hóa bộ nhớ trong các trường hợp sau:

  • Xử lý chuỗi phức tạp: Khi bạn thực hiện nhiều thao tác như append, insert, delete, thay vì chuyển đổi ngược lại thành String (toString()) rồi mới cắt, hãy gọi trực tiếp substring() từ StringBuilder. Việc này giúp tránh tạo ra các đối tượng trung gian không cần thiết.
  • Xây dựng nội dung động: Nếu bạn đang xây dựng một câu truy vấn SQL hoặc một file JSON lớn và chỉ cần lấy một phần kết quả cuối cùng, StringBuilder.substring() sẽ thực hiện việc trích xuất nhanh chóng hơn từ vùng đệm có sẵn.

Tóm lại: Trong hầu hết các ứng dụng đơn luồng (Single-threaded), hãy ưu tiên sử dụng StringBuilder.substring() để đạt tốc độ xử lý tối ưu nhất.

substring() có làm tốn bộ nhớ (Memory Leak) không?

  • Với Java hiện đại (từ 7u6 trở đi): Câu trả lời là KHÔNG. Mỗi chuỗi con là một đối tượng độc lập với mảng ký tự riêng.
  • Với Java cũ: CÓ. Chuỗi con vẫn giữ tham chiếu đến mảng ký tự khổng lồ của chuỗi gốc, khiến bộ nhớ không được giải phóng.

Có cách nào cắt chuỗi mà không lo bị NullPointerException không?

Nếu bạn lo lắng biến String có thể bị null, hãy sử dụng thư viện Apache Commons Lang.

  • Cách dùng: StringUtils.substring(str, start, end);
  • Lợi ích: Nếu str là null, nó sẽ trả về null thay vì làm chương trình bị văng lỗi.

Tại sao str.substring(0, str.length()) không gây lỗi?

Theo lý thuyết, str.length() là chỉ số nằm ngoài phạm vi ký tự (vị trí cuối cùng là length – 1). Tuy nhiên, vì nguyên tắc exclusive (Loại trừ), Java cho phép endIndex bằng đúng độ dài chuỗi. Kết quả trả về một bản sao của toàn bộ chuỗi gốc.

Ví dụ: Với chuỗi “Hi”, length là 2.

str.substring(0, 2) -> Lấy ký tự tại index 0 và 1. Hợp lệ.

str.charAt(2) -> Lỗi IndexOutOfBounds vì không có ký tự nào tại index 2.

Làm sao để cắt lấy X ký tự cuối cùng của một chuỗi trong Java?

Để lấy X ký tự cuối của một chuỗi trong Java, bạn có thể kết hợp phương thức substring() với length().

Ví dụ (Lấy 3 ký tự cuối): 

```java String str = "JavaProgramming"; String lastThree = str.substring(str.length() - 3); 
// Kết quả: "ing"

Copy

Giải thích: Đầu tiên, ta lấy tổng chiều dài của mảng String kể trên(15), sau đó str.length() – 3 để lấy ra (substring) mảng mới bắt đầu bằng 12 tức kết quả “ing”.

Tổng kết

Phương thức substring() là công cụ cốt lõi và không thể thiếu trong xử lý chuỗi của Java, giúp trích xuất dữ liệu một cách hiệu quả. Để làm chủ phương thức này, cần hiểu rõ tính bất biến (Immutability) của lớp String, tức là mỗi lần gọi substring() sẽ tạo ra một đối tượng String mới. Đối với các ứng dụng đơn luồng phức tạp, ưu tiên sử dụng StringBuilder.substring() để tối ưu hóa hiệu năng, và áp dụng các kỹ thuật được đề cập trong bài viết để xử lý chuỗi null an toàn.