Thành thạo hàm 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 Quan trọng trong Java

Thao tác với chuỗi là một trong những hoạt động được sử dụng thường xuyên nhất khi lập trình trong Java.
Dù là kiểm tra đầu vào của người dùng, phân tích nội dung tệp, hay tìm kiếm các từ khóa cụ thể, bạn thường cần xác định xem một từ cụ thể có được chứa trong một chuỗi cho trước hay không.

Để đáp ứng những nhu cầu này, Java cung cấp một phương thức tiện lợi gọi là 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 xem một thông báo lỗi có chứa một từ khóa cụ thể hay không, contains() cho phép bạn thực hiện điều đó chỉ trong một dòng mã.

Đặc biệt trong các tình huống 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 nhật ký—phương thức contains() cải thiện đáng kể khả năng đọc và khả năng bảo trì mã.
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 xảy ra null.

Bài viết này 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 phổ biến đến sự khác biệt với các phương thức khác và các ứng dụng thực tế.
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 lập trình viên đang sử dụng Java tích cực 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 rất thực tế 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 thuộc lớp String và chấp 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 được chỉ định, và false nếu không.

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" tồn tại trong chuỗi mục tiêu, vì vậy contains() trả về true.

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

  • Chỉ kiểm tra khớp một phần: Nếu bạn cần khớp chính xác, hãy sử dụng equals() thay thế.
  • Nhạy cảm với 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ỉ đơn giản kiểm tra sự hiện diện của một chuỗi, việc khớp mẫu yêu cầu matches() hoặc lớp Pattern.

Hành vi Khi Truyền null

Việc truyền null cho contains() sẽ kích hoạt NullPointerException.
Ví dụ, mã sau sẽ ném ngoại lệ:

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

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

3. Các Ví dụ Sử dụng Thực tế 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 sai có thể dẫn đến các lỗi bất ngờ hoặc mã không hiệu quả.
Phần này giải thích cách sử dụng cơ bản của contains() cùng với các điểm chính bạn nên lưu ý.

3-1. Ví dụ Sử dụng Cơ bản

Mã sau minh họa một ví dụ đơn giản về việc 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ư đã hiển thị, contains() thường được kết hợp với câu lệnh if để thực hiện phân nhánh điều kiện.

3-2. Độ nhạy cảm với Chữ hoa/thường

Phương thức contains() nhạy cảm với chữ hoa/thường.
Ví dụ, mã sau 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 thành 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ề kiểu chữ 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 cân nhắc quan trọng nhất khi sử dụng contains() là việc xử lý null.
Nếu chuỗi mục tiêu hoặc đối số là null, sẽ xảy ra NullPointerException.

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

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

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

Cũng lưu ý:
Việc 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ể vô tình gây ra lỗi nếu chuỗi rỗng được truyền một cách không cố ý.

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, cho 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 dùng cho “khớp một phần”, nhưng các phương thức khác cũng có mục đích tương tự.
Phần này giải thích các đặc điểm và sự khác nhau của các phương thức đó để giúp bạn sử dụng chúng một cách phù hợp.

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

equals() xác định liệu hai chuỗi có hoàn toàn giống nhau hay không.
Ngược lại, contains() kiểm tra các 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 hoàn toàn (ví dụ: xác minh ID).
Sử dụng contains() khi chấp nhận khớp một phần (ví dụ: tìm kiếm từ khóa).

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

Phương thức indexOf() cũng có thể được dùng để kiểm tra xem một chuỗi con có tồn tại trong một 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ể dùng indexOf() để mô phỏng 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. 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 hoàn toàn khớp với một biểu thức chính quy cho trước hay không.
Ngược lại, contains() chỉ kiểm tra khớp chuỗi con nguyên văn 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, hãy sử dụng lớp Pattern:

4-4. Tổng Kế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 Thông Thường và Mã Mẫu

.

Java’s contains() method is simple yet widely used in real development scenarios.
Typical use cases include user input validation, log analysis, and filtering operations.
This section covers practical examples with corresponding code.

5-1. Kiểm tra đầu vào người dùng (Phát hiện từ cấm)

Trong các biểu mẫu hoặc ứng dụng chat, bạn có thể cần phát hiện xem có từ cấm nào được bao gồm hay 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 log (Phát hiện tin nhắn cụ thể)

Khi phân tích log 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 đến hiệu năng 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ội bộ, 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ả các chuỗi sang 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() cho xử lý song song

Sử 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ể biểu diễn bằng một regex, Pattern có thể hoạt động tốt hơn:

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

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

Các hoạt động 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 các instance chuỗi không cần thiết.
  • Đối với các tập dữ liệu lớn, xem xét buffering hoặc xử lý theo chunk.
  • Lưu cache kết quả contains() lặp lại có thể cải thiện hiệu suất 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 việc khớp chuỗi con đơn giản, đáng tin cậy, nhưng các ngôn ngữ khác cung cấp các tính năng tương tự với đặc điểm riêng của chúng.
Phần này so sánh việc kiểm tra chuỗi con trong Python, JavaScript và C# để làm nổi bật sự khác biệt và tương đồng.

7-1. Python: Khớp Phần Đơn giản với Toán tử in

Trong Python, bạn có thể kiểm tra sự 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 học tập tối thiểu.

Sự khác biệt và Ghi chú:

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

7-2. JavaScript: Khớp 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 di chuyển về mặt tinh thần.

Sự khác biệt và Ghi 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 linh hoạt của nó.

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

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

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

Sự khác biệt và Ghi chú:

  • Contains() của C# phân biệt chữ hoa chữ thường theo mặc định, nhưng bạn có thể bỏ qua chữ hoa chữ thường bằng StringComparison.OrdinalIgnoreCase .
  • Truyền null kích hoạt 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à yêu cầu phổ biến qua các ngôn ngữ, mỗi ngôn ngữ cung cấp phương thức hoặc cú pháp riêng của mình.
contains() của Java mang lại sự ổn định và rõ ràng, làm cho nó phù hợp tốt cho các hệ thống doanh nghiệp và ứ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 nguyên 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 từng ngôn ngữ, bạn sẽ có thể viết mã an toàn và hiệu quả hơn qua các ngôn ngữ khác nhau.

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

Dưới đây là các câu hỏi thường gặp liên quan đến phương thức contains() của Java—giúp bạn hiểu các điểm khó và tránh các lỗi 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 phần bằng biểu thức chính quy?

contains() không hỗ trợ biểu thức chính quy.
Sử dụng matches() hoặc lớp Pattern thay thế.

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 ra.

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 thông số kỹ thuậ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 lúc không?

Không. Mỗi cuộc 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 tôi 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 liệu chuỗi con có tồn tại hay không.
  • Sử dụng indexOf() khi bạn cũng cần vị trí.
    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 liệu 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 các tình huống khác nhau như xác thực đầu vào người dùng, phân tích nhật ký, 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ề
  • Nhạy cảm với chữ hoa/thường và cách xử lý nó
  • Xử lý null và chuỗi rỗng
  • Sự khác biệt so với các phương thức so sánh chuỗi khác
  • Các trường hợp sử dụng thực tế: xác thực, tìm kiếm nhật ký, xử lý Stream
  • Các cân nhắc về hiệu suất và kỹ thuật tối ưu hóa
  • 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ó nê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, các cuộc gọi thường xuyên, hoặc các điều kiện tìm kiếm phức tạp.
Bằng cách kết hợp chuẩn hóa, xử lý song song, regex, và các chiến lược lưu trữ đệm, bạn có thể duy trì cả hiệu suất và khả năng đọc.

contains() là cơ bản để làm việc với chuỗi trong Java, chúng tôi hy vọng bài viết này giúp bạn sử dụng nó an toàn và hiệu quả hơn trong các dự án phát triển của bạn.