Thành thạo so sánh chuỗi trong Java: Sự khác biệt giữa “==”, equals(), compareTo() và các thực tiễn tốt nhất

目次

1. Giới thiệu

Tại sao việc so sánh chuỗi lại quan trọng trong Java?

Trong lập trình Java, chuỗi được sử dụng trong rất nhiều tình huống. Kiểm tra tên người dùng, xác thực dữ liệu nhập vào biểu mẫu, và xác minh phản hồi API đều đòi hỏi việc so sánh chuỗi.
Ở giai đoạn này, “cách so sánh chuỗi một cách đúng đắn” là một rào cản phổ biến đối với người mới bắt đầu. Đặc biệt, việc không hiểu sự khác nhau giữa toán tử == và phương thức equals() có thể dẫn đến các lỗi không mong muốn.

Nguy cơ khi không hiểu sự khác nhau giữa “==” và “equals”

Xem đoạn mã sau:

String a = "apple";
String b = new String("apple");

System.out.println(a == b);       // result: false
System.out.println(a.equals(b));  // result: true

Nhiều người ngạc nhiên trước kết quả này. Mặc dù các chuỗi giống hệt nhau, == trả về false trong khi equals() trả về true. Điều này xảy ra vì Java coi chuỗi là kiểu tham chiếu, và == so sánh địa chỉ tham chiếu.
Hiểu đúng cách so sánh chuỗi ảnh hưởng trực tiếp đến độ tin cậy và khả năng đọc của chương trình. Nếu bạn nắm vững, bạn có thể ngăn ngừa lỗi trước khi chúng xuất hiện.

Những gì bạn sẽ học trong bài viết này

Bài viết này giải thích việc so sánh chuỗi trong Java từ cơ bản đến ứng dụng thực tiễn. Nó trả lời các câu hỏi như:

  • Sự khác nhau giữa ==equals() là gì?
  • Làm sao để so sánh chuỗi mà không phân biệt chữ hoa/chữ thường?
  • Làm sao để so sánh chuỗi theo thứ tự từ điển?
  • Làm sao tránh ngoại lệ khi so sánh với null?

Thông qua các ví dụ thực tế, bạn sẽ nắm vững các thực hành so sánh chuỗi đúng.

2. Cơ bản về Chuỗi trong Java

Chuỗi là kiểu tham chiếu

Trong Java, kiểu String không phải là kiểu nguyên thủy (như int hay boolean) mà là kiểu tham chiếu. Biến String không lưu trữ các ký tự thực tế mà là một tham chiếu tới một đối tượng được lưu trong bộ nhớ heap.
Ví dụ:

String a = "hello";
String b = "hello";

Cả ab có thể tham chiếu tới cùng một literal "hello" nhờ cơ chế string interning của Java.

Sự khác nhau giữa literal và new String()

Java tối ưu các literal chuỗi lặp lại bằng cách tái sử dụng cùng một tham chiếu:

String s1 = "apple";
String s2 = "apple";
System.out.println(s1 == s2); // true (same literal, interned)

Tuy nhiên, việc dùng new luôn tạo ra một đối tượng mới:

String s3 = new String("apple");
System.out.println(s1 == s3); // false (different references)
System.out.println(s1.equals(s3)); // true (same content)

Do đó, == kiểm tra tham chiếuequals() kiểm tra nội dung.

Chuỗi là bất biến

Một đặc điểm quan trọng khác là Stringbất biến. Khi đã tạo, một chuỗi không thể thay đổi.

String original = "hello";
original = original + " world";

Điều này tạo ra một đối tượng String mới thay vì sửa đổi đối tượng ban đầu.

3. Các phương thức so sánh chuỗi

So sánh tham chiếu bằng ==

== so sánh tham chiếu đối tượng:

String a = "Java";
String b = new String("Java");

System.out.println(a == b); // false

không nên được dùng để so sánh nội dung.

So sánh nội dung bằng equals()

equals() là cách đúng để so sánh nội dung chuỗi:

String a = "Java";
String b = new String("Java");

System.out.println(a.equals(b)); // true

Tránh NullPointerException

String input = null;
System.out.println(input.equals("test")); // Exception!

Sử dụng kiểu “hằng số trước”:

System.out.println("test".equals(input)); // false, safe

Bỏ qua chữ hoa/chữ thường với equalsIgnoreCase()

String a = "Hello";
String b = "hello";

System.out.println(a.equalsIgnoreCase(b)); // true

So sánh theo thứ tự từ điển với compareTo()

  • 0 → bằng nhau
  • Âm → đối tượng gọi trước
  • Dương → đối tượng gọi sau
    String a = "apple";
    String b = "banana";
    
    System.out.println(a.compareTo(b)); // negative
    

4. Ví dụ thực tế

Kiểm tra đăng nhập người dùng

String inputUsername = "Naohiro";
String registeredUsername = "naohiro";

if (registeredUsername.equalsIgnoreCase(inputUsername)) {
    System.out.println("Login successful");
} else {
    System.out.println("Username does not match");
}

Mật khẩu luôn nên sử dụng equals() vì yêu cầu phân biệt chữ hoa chữ thường.

Xác thực đầu vào biểu mẫu

String selectedOption = request.getParameter("plan");

if ("premium".equals(selectedOption)) {
    System.out.println("Premium plan selected.");
} else {
    System.out.println("Other plan selected.");
}

Logic nhánh với nhiều kiểm tra chuỗi

String cmd = args[0];

if ("start".equals(cmd)) {
    startApp();
} else if ("stop".equals(cmd)) {
    stopApp();
} else {
    System.out.println("Invalid command");
}
switch (cmd) {
    case "start":
        startApp();
        break;
    case "stop":
        stopApp();
        break;
    default:
        System.out.println("Unknown command");
}

Xử lý null một cách an toàn

String keyword = null;

if ("search".equals(keyword)) {
    System.out.println("Searching...");
}

5. Hiệu năng và Tối ưu hoá

Chi phí của việc so sánh chuỗi

equals()compareTo() so sánh các ký tự bên trong. Đối với các chuỗi dài hoặc các lần so sánh lặp lại, điều này có thể ảnh hưởng đến hiệu năng.

Sử dụng String.intern() để cải thiện hiệu năng

String a = new String("hello").intern();
String b = "hello";

System.out.println(a == b); // true

Chỉ sử dụng khi cần thiết để tránh áp lực bộ nhớ.

Ảnh hưởng đến hiệu năng của equalsIgnoreCase()

String input = userInput.toLowerCase();
if ("admin".equals(input)) {
    // fast comparison
}

Sử dụng StringBuilder / StringBuffer

StringBuilder sb = new StringBuilder();
sb.append("user_");
sb.append("123");

String result = sb.toString();

if (result.equals("user_123")) {
    // comparison
}

Sử dụng bộ nhớ đệm hoặc bản đồ để giảm số lần so sánh

Map<String, Runnable> commandMap = new HashMap<>();
commandMap.put("start", () -> startApp());
commandMap.put("stop", () -> stopApp());

Runnable action = commandMap.get(inputCommand);
if (action != null) {
    action.run();
}

6. Câu hỏi thường gặp

Câu 1. Sự khác nhau giữa == và equals() là gì?

Đáp.

  • == so sánh tham chiếu
  • equals() so sánh nội dung chuỗi

Câu 2. Tại sao equals() gây lỗi khi biến là null?

String input = null;
input.equals("test"); // Exception

Sử dụng so sánh hằng trước:

"test".equals(input);

Câu 3. Làm sao so sánh chuỗi mà không phân biệt chữ hoa chữ thường?

stringA.equalsIgnoreCase(stringB);

Câu 4. Làm sao so sánh thứ tự bảng chữ cái?

a.compareTo(b);

7. Kết luận

Việc chọn đúng phương pháp so sánh là rất quan trọng

String là kiểu tham chiếu, việc so sánh không đúng thường dẫn đến hành vi bất ngờ. Hiểu rõ ==equals() là điều cần thiết.

Danh sách kiểm tra

  • == : so sánh tham chiếu
  • equals() : so sánh nội dung
  • equalsIgnoreCase() : không phân biệt chữ hoa chữ thường
  • compareTo() : thứ tự từ điển
  • "constant".equals(variable) : an toàn với null
  • Sử dụng intern() hoặc bộ nhớ đệm cho các so sánh nặng

Kiến thức thực tiễn và thiết yếu

Việc so sánh chuỗi xuất hiện trong kiểm tra đăng nhập, xác thực, thao tác cơ sở dữ liệu, logic nhánh và nhiều công việc hàng ngày. Nắm vững các kỹ thuật đúng giúp viết mã an toàn hơn, đáng tin cậy hơn.

Sử dụng hướng dẫn này làm tài liệu tham khảo khi làm việc với chuỗi trong Java.