Thành thạo contains() trong Java: Cách thực hiện tìm kiếm chuỗi con hiệu quả

目次

1. Giới thiệu: Tại sao Tìm kiếm Chuỗi lại quan trọng trong Java

Việc thao tác chuỗi từ cụ thể, bạn thường cần xác định xem một từ nào đó có xuất hiện trong một chuỗi cho trước hay không.
Để đáp ứng nhu cầu này, Java cung cấp một phương thức tiện lợi có tên contains().
Với phương thức này, bạn có thể dễ dàng xác định xem một chuỗi có chứa một phần của chuỗi khác hay không.
Ví dụ, nếu bạn muốn kiểm tra một thông báo lỗi có chứa một từ khóa nhất định, contains() cho phép bạn thực hiện việc này chỉ trong một dòng lệnh.
Đặc biệt trong các kịch bản liên quan đến khối lượng văn bản lớn—như ứng dụng web, xử lý API, hoặc phân tích log—phương thức contains() giúp cải thiện đáng kể tính đọc được và khả năng bảo trì của mã nguồn.
Tuy nhiên, cũng có những lưu ý quan trọng như độ nhạy cảm với chữ hoa/thường và khả năng nhận null.
Bài viết này sẽ giải thích chi tiết về phương thức contains() của Java—t cách sử dụng cơ bản và các lỗi thường gặp đến sự khác biệt so với các phương thức khác và các ứng dụng thực tiễn.
Mục tiêu của chúng tôi là cung cấp thông tin hữu ích không chỉ cho người mới bắt đầu mà còn cho các nhà phát triển đang sử dụng Java trong các dự án thực tế.

2. Cú pháp cơ bản và Đặc điểm của phương thức contains()

Phương thức contains() của Java xác định xem một chuỗi có chứa một phần của chuỗi khác hay không.
Cú pháp của nó rất đơn giản, nhưng lại vô cùng thực tiễn và được sử dụng thường xuyên trong các nhiệm vụ lập trình hàng ngày.

Cú pháp cơ bản

boolean result = targetString.contains(searchString);

Phương thức này thuộc lớp String và nhận một CharSequence (thường là một String) làm đối số.
Giá trị trả về là boolean: true nếu chuỗi mục tiêu chứa chuỗi con đã cho, và false trong các trường hợp còn lại.

Mã mẫu

String message = "Java programming is fun!";
boolean hasKeyword = message.contains("programming");

System.out.println(hasKeyword); // Output: true

Trong ví dụ trên, chuỗi con "programming" xuất hiện trong chuỗi mục tiêu, vì vậy contains() trả về true.

Đặc điểm của phương thức

  • Kiểm tra phần khớp: Nếu bạn cần kiểm tra khớp hoàn toàn, hãy dùng equals() thay thế.
  • Phân biệt chữ hoa/thường: Ví dụ, "Java""java" được coi là khác nhau (chi tiết sẽ được giải thích sau).
  • Không hỗ trợ biểu thức chính quy: Vì nó chỉ kiểm tra sự tồn tại của một chuỗi, việc khớp mẫu cần dùng matches() hoặc lớp Pattern.

Hành vi khi truyền null

Việc truyền null vào contains() sẽ gây ra NullPointerException.
Ví dụ, đoạn mã sau sẽ ném ra ngoại lệ:

String text = null;
System.out.println(text.contains("test")); // Exception occurs

Tương tự, nếu chuỗi mục tiêu tự nó là null, cùng một ngoại lệ sẽ được ném.
Do đó, nên thực hiện kiểm tra null trước khi gọi contains().

3. Các ví dụ thực tiễn và Những lưu ý quan trọng

Phương thức contains() của Java rất trực quan và tiện lợi, nhưng việc sử dụng không đúng### 3-1. Ví dụ sử dụng cơ bản

Đoạn mã dưới đây minh họa một ví dụ đơn giản để kiểm tra xem chuỗi mục tiêu có chứa một từ khóa cụ thể hay không:

String sentence = "今日はJavaの勉強をしています。";

if (sentence.contains("Java")) {
    System.out.println("Javaが含まれています。");
} else {
    System.out.println("Javaは含まれていません。");
}

Như thấy, contains() thường được kết hợp với câu lệnh if để thực hiện việc rẽ nhánh có điều kiện.

3-2. Phân biệt chữ hoa/thường

Phương thức contains() phân biệt chữ hoa/thường.
Ví dụ, đoạn mã sau sẽ trả về false:

String text = "Welcome to Java";
System.out.println(text.contains("java")); // false

Trong những trường hợp như vậy, thường sẽ chuyển các chuỗi sang chữ thường (hoặc chữ hoa) trước khi so sánh:

String text = "Welcome to Java";
System.out.println(text.toLowerCase().contains("java")); // true

Cách tiếp cận này giúp loại bỏ sự khác biệt về chữ hoa chữ thường trong đầu vào (ví dụ: đầu vào của người dùng).

3-3. Xử lý null và Chuỗi Rỗng

Một trong những lưu ý quan trọng nhất khi sử dụng contains() là xử lý null. Nếu chuỗi đích hoặc đối số là null, một ngoại lệ NullPointerException sẽ xảy ra.

String text = null;
System.out.println(text.contains("test")); // Runtime error

Để tránh vấn đề này, luôn thêm kiểm tra null:

if (text != null && text.contains("test")) {
    // Safe to process
}

Cũng lưu ý: Truyền một chuỗi rỗng ("") luôn trả về true.

String sample = "test";
System.out.println(sample.contains("")); // true

Tuy nhiên, hành vi này hiếm khi hữu ích trong thực tế và có thể dẫn đến lỗi không mong muốn nếu chuỗi rỗng được truyền một cách vô ý.

3-4. Không Hỗ Trợ Tìm Kiếm Nhiều Từ Khóa

contains() chỉ có thể kiểm tra một từ khóa tại một thời điểm. Để kiểm tra nhiều từ khóa, bạn phải gọi contains() nhiều lần hoặc sử dụng Stream API.

String target = "エラーコード123:アクセス拒否";
if (target.contains("エラー") || target.contains("拒否")) {
    System.out.println("問題のあるメッセージです。");
}

Hoặc, đối với các tập từ khóa động:

List<String> keywords = Arrays.asList("エラー", "障害", "失敗");
boolean found = keywords.stream().anyMatch(target::contains);

4. Các Phương Thức Thường Được So Sánh Với contains()

Java cung cấp một số phương thức để so sánh chuỗi hoặc kiểm tra xem một chuỗi con cụ thể có tồn tại hay không. Trong số đó, contains() được sử dụng cho “khớp một phần,” nhưng các phương thức khác cũng phục vụ các mục đích tương tự. Phần này giải thích các đặc điểm và sự khác biệt của những phương thức đó để giúp bạn sử dụng chúng một cách phù hợp.

4-1. Sự Khác Biệt Với equals(): Khớp Chính Xác vs. Khớp Một Phần

equals() xác định xem hai chuỗi có hoàn toàn giống nhau hay không. Ngược lại, contains() kiểm tra khớp một phần.

String a = "Java";
String b = "Java";

System.out.println(a.equals(b));      // true: Exact match
System.out.println(a.contains("av")); // true: Partial match

Sự khác biệt chính:

Comparisonequals()contains()
Match TypeExact matchPartial match
Case SensitivitySensitiveSensitive
Argument TypeObjectCharSequence

Hướng Dẫn Sử Dụng: Sử dụng equals() khi giá trị phải khớp chính xác (ví dụ: xác minh ID). Sử dụng contains() khi khớp một phần là chấp nhận được (ví dụ: tìm kiếm từ khóa).

4-2. Sự Khác Biệt Với indexOf(): Có Cần Vị Trí Hay Không

Phương thức indexOf() cũng có thể được sử dụng để kiểm tra xem một chuỗi con có tồn tại trong chuỗi hay không. Sự khác biệt là indexOf() trả về chỉ số bắt đầu của chuỗi con nếu tìm thấy. Nếu không tìm thấy chuỗi con, nó trả về -1.

String text = "Hello, Java World!";
System.out.println(text.indexOf("Java"));    // 7
System.out.println(text.indexOf("Python"));  // -1

Bạn cũng có thể sử dụng indexOf() để tái tạo hành vi của contains():

if (text.indexOf("Java") >= 0) {
    System.out.println("It is contained.");
}

Hướng dẫn sử dụng: Nếu bạn không cần chỉ số, contains() dễ đọc hơn và được ưu tiên.

4-3. Sự Khác Biệt Với matches(): Hỗ Trợ Biểu Thức Chính Quy

Phương thức matches() kiểm tra xem một chuỗi có khớp hoàn toàn với biểu thức chính quy đã cho hay không. Ngược lại, contains() chỉ kiểm tra khớp chuỗi con theo nghĩa đen và không hỗ trợ regex.

String text = "abc123";
System.out.println(text.matches(".*123")); // true
System.out.println(text.contains(".*123")); // false (not regex)

Nếu bạn muốn khớp một phần dựa trên regex, sử dụng lớp Pattern:

4-4. Tóm Tắt So Sánh Tính Năng

MethodPurposeReturn TypeRegex SupportUse Case
contains()Partial matchbooleanNoKeyword search
equals()Exact matchbooleanNoID/password checks
indexOf()Get match positionintNoIndex-based processing
matches()Regex matchbooleanYesFind pattern-based strings

5. Các Trường Hợp Sử Dụng Phổ Biến Và Mã Mẫu

Phương thức contains() của Java đơn giản nhưng được sử dụng rộng rãi trong các tình huống phát triển thực tế. Các trường hợp sử dụng điển hình bao gồm xác thực đầu vào người dùng, phân tích nhật ký, và các hoạt động lọc. Phần này bao gồm các ví dụ thực tế với mã tương ứng.

5-1. Xác Thực Đầu Vào Người Dùng (Phát Hiện Từ Bị Cấm)

Trong các biểu mẫu hoặc ứng dụng trò chuyện, bạn có thể cần phát hiện xem có chứa các từ bị cấm nào không.

String input = "このアプリは最悪だ";
String banned = "最悪";

if (input.contains(banned)) {
    System.out.println("不適切な言葉が含まれています。");
}

Xử lý nhiều từ NG:

List<String> bannedWords = Arrays.asList("最悪", "バカ", "死ね");
for (String word : bannedWords) {
    if (input.contains(word)) {
        System.out.println("不適切な言葉が含まれています: " + word);
        break;
    }
}

5-2. Phân tích tệp nhật ký (Phát hiện các tin nhắn cụ thể)

Khi phân tích nhật ký hệ thống hoặc ứng dụng, bạn có thể muốn trích xuất chỉ các dòng chứa các từ khóa cụ thể như ERROR hoặc WARN.

List<String> logs = Arrays.asList(
    "[INFO] サーバーが起動しました",
    "[ERROR] データベース接続失敗",
    "[WARN] メモリ使用率が高い"
);

for (String log : logs) {
    if (log.contains("ERROR")) {
        System.out.println("エラー発生ログ: " + log);
    }
}

5-3. Lọc chuỗi trong danh sách (Sử dụng Stream API)

Khi xử lý các bộ dữ liệu lớn, sử dụng Stream API để trích xuất chỉ các phần tử chứa một chuỗi con cụ thể:

List<String> users = Arrays.asList("tanaka@example.com", "sato@gmail.com", "yamada@yahoo.co.jp");

List<String> gmailUsers = users.stream()
    .filter(email -> email.contains("@gmail.com"))
    .collect(Collectors.toList());

System.out.println(gmailUsers); // [sato@gmail.com]

5-4. Phân tích tiêu đề yêu cầu HTTP hoặc URL

Trong phát triển web, việc định tuyến hoặc xử lý theo thiết bị có thể yêu cầu kiểm tra các chuỗi con trong User-Agent hoặc URL.

String userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)";
if (userAgent.contains("iPhone")) {
    System.out.println("スマートフォンからのアクセスです。");
}

5-5. Kiểm tra đường dẫn tệp hoặc phần mở rộng

Để xác định loại tệp dựa trên đường dẫn của nó:

String filePath = "/usr/local/data/sample.csv";
if (filePath.contains(".csv")) {
    System.out.println("CSVファイルです。");
}

Lưu ý: Đối với việc kiểm tra phần mở rộng tệp, endsWith(".csv") thường chính xác hơn.

Các cân nhắc thực tiễn

  • Áp dụng chuẩn hoá (ví dụ, toLowerCase(), trim()) khi cần độ chính xác.
  • Đối với dữ liệu quy mô lớn, cân nhắc sử dụng Stream API hoặc regex.
  • Nhớ rằng contains() là khớp một phần—kết hợp với các điều kiện khác để logic an toàn hơn.

6. Các cân nhắc về hiệu năng

Mặc dù phương thức contains() mang lại khả năng đọc tốt và đơn giản, bạn phải cân nhắc tác động về hiệu năng của nó khi xử lý các bộ dữ liệu lớn hoặc thực hiện các thao tác lặp lại.
Phần này giải thích chi phí xử lý của contains() và các phương pháp thay thế để cải thiện hiệu suất.

6-1. Hành vi nội bộ và độ phức tạp thời gian của contains()

Phương thức contains() tìm kiếm chuỗi mục tiêu theo thứ tự từ đầu để xác định chuỗi con.
Nó dựa vào phương thức indexOf() và độ phức tạp thời gian trong trường hợp xấu nhất là:
O(n * m)
– n = độ dài của chuỗi mục tiêu
– m = độ dài của chuỗi tìm kiếm

Ví dụ về xử lý nặng:

for (String line : hugeTextList) {
    if (line.contains("error")) {
        // processing
    }
}

Điều này có thể ảnh hưởng đáng kể đến hiệu năng khi lặp lại trong các vòng lặp lớn.

6-2. Kỹ thuật cải thiện hiệu năng khi tìm kiếm thường xuyên

Khi sử dụng contains() lặp lại trong các bộ dữ liệu lớn, các kỹ thuật sau có thể cải thiện tốc độ xử lý:

• Chuyển tất cả chuỗi về chữ thường trước

Thay vì gọi toLowerCase() trong mỗi lần so sánh, hãy chuẩn hoá các chuỗi trước:

List<String> normalizedList = originalList.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toList());
• Sử dụng Stream API với parallel() để xử lý song song

Tận dụng các lõi CPU để tăng tốc tìm kiếm:

List<String> result = hugeTextList.parallelStream()
    .filter(line -> line.contains("keyword"))
    .collect(Collectors.toList());
• Sử dụng biểu thức chính quy cho các mẫu tìm kiếm phức tạp

Nếu các điều kiện phức tạp và có thể diễn đạt bằng một biểu thức chính quy duy nhất, Pattern có thể hoạt động hiệu quả hơn:

Pattern pattern = Pattern.compile("error|fail|fatal");
for (String log : logs) {
    if (pattern.matcher(log).find()) {
        // matched
    }
}

6-3. Xem xét về Hiệu suất Bộ nhớ và Tính tái sử dụng

Các thao tác thường xuyên chuyển đổi chuỗi—như toLowerCase() hoặc substring()—có thể tạo ra nhiều đối tượng chuỗi không cần thiết, ảnh hưởng đến việc sử dụng bộ nhớ.
Điều này đặc biệt quan trọng đối với các ứng dụng chạy lâu dài hoặc xử lý phía máy chủ.

Các điểm chính:

  • Tránh tạo ra các thể hiện chuỗi không cần thiết.
  • Đối với bộ dữ liệu lớn, cân nhắc việc đệm hoặc xử lý theo khối.
  • Lưu trữ kết quả contains() lặp lại có thể cải thiện hiệu năng trong một số trường hợp.

7. So sánh với các Ngôn ngữ Lập trình Khác

Phương thức contains() của Java cung cấp cách khớp chuỗi con đơn giản, đáng tin cậy, nhưng các ngôn ngữ khác cũng có các tính năng tương tự với những đặc điểm riêng.
Phần này so sánh việc kiểm tra chuỗi con trong Python, JavaScript và C# để nêu bật các khác biệt và điểm tương đồng.

7-1. Python: Khớp một phần Đơn giản với toán tử in

Trong Python, bạn có thể kiểm tra việc bao gồm chuỗi con bằng toán tử in:

text = "Hello, Python!"
if "Python" in text:
    print("含まれています")

Cú pháp này cực kỳ dễ đọc—gần như ngôn ngữ tự nhiên—và yêu cầu ít thời gian học.

Khác biệt và Ghi chú:

  • in là một toán tử của ngôn ngữ, không phải một phương thức.
  • Python cũng phân biệt chữ hoa/chữ thường khi so sánh chuỗi.
  • None gây ra ngoại lệ; cần kiểm tra null.

7-2. JavaScript: Khớp một phần với includes()

Trong JavaScript (ES6+), bạn có thể sử dụng phương thức includes():

const text = "JavaScript is fun";
console.log(text.includes("fun")); // true

Phương thức này rất giống với contains() của Java và dễ dàng chuyển đổi trong tư duy.

Khác biệt và G chú:

  • Truyền undefined không gây ra ngoại lệ; nó chỉ trả về false.
  • includes() cũng hoạt động trên mảng, tăng tính đa dụng của nó.

7-3. C#: Contains() Tương tự Java

C# cũng cung cấp một phương thức Contains() với hành vi tương tự Java:

string text = "Welcome to C#";
bool result = text.Contains("C#");

Khác biệt và Ghi chú:

  • Contains() trong C# phân biệt chữ hoa/chữ thường theo mặc định, nhưng bạn có thể bỏ qua việc phân biệt bằng cách sử dụng StringComparison.OrdinalIgnoreCase.
  • Truyền giá trị null sẽ gây ra ArgumentNullException.

7-4. Bảng So sánh Giữa Các Ngôn ngữ

LanguageExample SyntaxCase SensitivityNotes
Java"abc".contains("a")SensitiveThrows exception on null
Python"a" in "abc"SensitiveMost intuitive syntax
JavaScript"abc".includes("a")SensitiveAlso works for arrays
C#"abc".Contains("a")Sensitive (configurable)Comparison mode can be chosen

Tóm tắt: Chọn Cú pháp Phù hợp cho Trường hợp Sử dụng của Bạn

Mặc dù việc kiểm tra chuỗi con là một yêu cầu phổ biến trong nhiều ngôn ngữ, mỗi ngôn ngữ đều cung cấp phương thức hoặc cú pháp riêng.
Phương thức contains() của Java mang lại sự ổn định và rõ ràng, phù hợp cho các hệ thống doanh nghiệp và các ứng dụng dễ bảo trì.
Các ngôn ngữ như Python và JavaScript cung cấp cú pháp đơn giản và ngắn gọn hơn, có thể lý tưởng cho các script nhẹ hoặc việc tạo mẫu nhanh.
Bằng cách hiểu cả các khái niệm chung và các tính năng đặc thù của mỗi ngôn ngữ, bạn sẽ có thể viết mã an toàn và hiệu quả hơn trên nhiều ngôn ngữ.

8. Câu hỏi Thường gặp (FAQ)

Dưới đây là các câu hỏi thường gặp về phương thức contains() của Java—giúp bạn hiểu các điểm khó và tránh những bẫy phổ biến.

Q1. contains() có phân biệt chữ hoa/chữ thường không?

Có, nó phân biệt chữ hoa/chữ thường.
Ví dụ, "Java".contains("java") trả về false.
Giải pháp:

String input = "Welcome to Java";
boolean result = input.toLowerCase().contains("java");

Q2. Làm thế nào để kiểm tra khớp một phần bằng biểu thức chính quy?

contains() không hỗ trợ biểu thức chính quy.
Thay vào đó, sử dụng matches() hoặc lớp Pattern.
Ví dụ (kiểm tra mẫu số):

import java.util.regex.Pattern;
import java.util.regex.Matcher;

String text = "注文番号: A123456";
Pattern pattern = Pattern.compile("A\d+");
Matcher matcher = pattern.matcher(text);

if (matcher.find()) {
    System.out.println("パターンに一致しました。");
}

Q3. Điều gì xảy ra nếu tôi gọi contains() trên null?

Một NullPointerException sẽ được ném.

String target = null;
System.out.println(target.contains("test")); // Error

Giải pháp:

if (target != null && target.contains("test")) {
    System.out.println("含まれています。");
}

Q4. Điều gì xảy ra nếu tôi truyền một chuỗi rỗng (“”) vào contains()?

Nó luôn trả về true.

String text = "Java";
System.out.println(text.contains("")); // true

Mặc dù là một phần của đặc tả chính thức, hành vi này hiếm khi hữu ích và có thể gây ra lỗi không mong muốn nếu chuỗi rỗng không được dự định.

Q5. contains() có thể tìm kiếm nhiều từ khóa cùng một lúc không?

Không. Mỗi lần gọi chỉ kiểm tra một từ khóa.

String text = "本日はシステムエラーが発生しました";
if (text.contains("エラー") || text.contains("障害") || text.contains("失敗")) {
    System.out.println("問題が検出されました。");
}

Cách tiếp cận động:

List<String> keywords = Arrays.asList("エラー", "障害", "失敗");
boolean found = keywords.stream().anyMatch(text::contains);

Q6. Khi nào nên sử dụng contains() so với indexOf()?

contains() trả về một boolean, trong khi indexOf() trả về một chỉ số số.

  • Sử dụng contains() khi bạn chỉ muốn biết chuỗi con có tồn tại hay không.
  • Sử dụng indexOf() khi bạn cũng cần vị trí của chuỗi con.
    String text = "Error: Disk full";
    if (text.contains("Error")) {
        int pos = text.indexOf("Error");
        System.out.println("Position: " + pos);
    }
    

9. Kết luận

Phương thức contains() của Java là một công cụ mạnh mẽ và tiện lợi để xác định xem một chuỗi con cụ thể có được chứa trong một chuỗi hay không.
Nó được sử dụng rộng rãi trong nhiều tình huống như kiểm tra đầu vào người dùng, phân tích log và lọc dữ liệu.
Trong bài viết này, chúng tôi đã đề cập đến:

  • Cú pháp cơ bản và giá trị trả về
  • Phân biệt chữ hoa/chữ thường và cách xử lý
  • Xử lý null và chuỗi rỗng
  • Sự khác biệt so với các phương pháp so sánh chuỗi khác
  • Các trường hợp sử dụng thực tế: kiểm tra, tìm kiếm log, xử lý Stream
  • Các cân nhắc về hiệu năng và kỹ thuật tối ưu
  • So sánh với Python, JavaScript và C#
  • Các câu hỏi thường gặp và mẹo khắc phục sự cố

Mặc dù contains() trực quan và đa năng, việc sử dụng nó cần được đánh giá cẩn thận trong các trường hợp liên quan đến tập dữ liệu lớn, gọi thường xuyên, hoặc điều kiện tìm kiếm phức tạp.
Bằng cách kết hợp chuẩn hoá, xử lý song song, regex và các chiến lược cache, bạn có thể duy trì cả hiệu năng và khả năng đọc mã.
contains() là nền tảng để làm việc với chuỗi trong Java, chúng tôi hy vọng bài viết này sẽ giúp bạn sử dụng nó một cách an toàn và hiệu quả hơn trong các dự án phát triển của mình.