CHÀO! Hôm nay chúng ta sẽ nói về một chủ đề rất quan trọng và thú vị, đó là so sánh đối tượng với đối tượng (So sánh Strings và Equals). Vì vậy, trong Java, chính xác khi nào đối tượng A sẽ bằng đối tượng B ? Hãy thử viết một ví dụ:
Toán tử == sử dụng xấp xỉ logic này khi chúng ta sử dụng nó để so sánh hai đối tượng. Nhưng nếu bạn cần chương trình của mình sử dụng logic khác thì sao? Ví dụ: giả sử chương trình của bạn thực hiện phân tích DNA. Nó so sánh mã di truyền của hai người và xác định xem họ có phải là anh em sinh đôi hay không.
Nhóm chuỗi là một khu vực để lưu trữ tất cả các giá trị chuỗi mà bạn tạo trong chương trình của mình. Tại sao nó được tạo ra? Như chúng tôi đã nói trước đây, các chuỗi đại diện cho một tỷ lệ phần trăm lớn của tất cả các đối tượng. Bất kỳ chương trình lớn nào cũng tạo ra rất nhiều chuỗi. Nhóm chuỗi được tạo để tiết kiệm bộ nhớ: các chuỗi được đặt ở đó và sau đó các chuỗi được tạo sau đó tham chiếu đến cùng một vùng bộ nhớ—không cần cấp phát bộ nhớ bổ sung mỗi lần. Mỗi khi bạn viết String = "........" chương trình sẽ kiểm tra xem có một chuỗi giống hệt nhau trong nhóm chuỗi hay không. Nếu có, thì một chuỗi mới sẽ không được tạo. Và tham chiếu mới sẽ trỏ đến cùng một địa chỉ trong nhóm chuỗi (nơi đặt chuỗi giống hệt nhau). Vì vậy, khi chúng tôi đã viết
Và mỗi khi bạn tạo một đối tượng mới bằng cách sử dụng new , một vùng bộ nhớ mới sẽ được cấp phát, ngay cả khi văn bản bên trong chuỗi mới giống nhau! Có vẻ như chúng ta đã tìm ra toán tử == . Nhưng còn người mới quen của chúng ta, phương thức equals() thì sao ?
public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car car1 = new Car();
car1.model = "Ferrari";
car1.maxSpeed = 300;
Car car2 = new Car();
car2.model = "Ferrari";
car2.maxSpeed = 300;
System.out.println(car1 == car2);
}
}
Đầu ra bảng điều khiển: sai Đợi, dừng lại. Tại sao 2 xe này không bằng nhau? Chúng tôi đã gán cho chúng các thuộc tính giống nhau, nhưng kết quả so sánh là sai. Đáp án đơn giản. Toán tử == so sánh các tham chiếu đối tượng, không phải thuộc tính đối tượng. Hai đối tượng thậm chí có thể có 500 trường với các giá trị giống hệt nhau, nhưng so sánh chúng vẫn cho kết quả sai. Rốt cuộc, tham chiếu car1 và car2trỏ đến hai đối tượng khác nhau, nghĩa là đến hai địa chỉ khác nhau. Hãy tưởng tượng một tình huống mà bạn đang so sánh mọi người. Chắc chắn, ở một nơi nào đó trên thế giới có một người giống bạn về tên, màu mắt, tuổi, chiều cao, màu tóc, v.v. cùng một người.
public class Man {
int geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = 1111222233;
Man man2 = new Man();
man2.geneticCode = 1111222233;
System.out.println(man1 == man2);
}
}
Đầu ra của bảng điều khiển: sai Chúng tôi nhận được cùng một kết quả logic (vì chúng tôi đã không thay đổi nhiều), nhưng bây giờ logic đó không còn tốt nữa! Rốt cuộc, trong cuộc sống thực, phân tích DNA sẽ đảm bảo 100% rằng chúng ta có cặp song sinh đang đứng trước mặt. Nhưng chương trình của chúng ta và toán tử == lại cho chúng ta biết điều ngược lại. Làm cách nào để chúng tôi thay đổi hành vi này và đảm bảo chương trình đưa ra kết quả chính xác khi DNA trùng khớp? Java có một phương thức đặc biệt cho việc này: equals() . Giống như phương thức toString() mà chúng ta đã thảo luận trước đó, equals() thuộc về lớp Đối tượng — lớp quan trọng nhất trong Java, lớp mà tất cả các lớp khác bắt nguồn từ đó. Nhưng bằng()không thay đổi tất cả hành vi của chương trình của chúng tôi:
public class Man {
String geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = "111122223333";
Man man2 = new Man();
man2.geneticCode = "111122223333";
System.out.println(man1.equals(man2));
}
}
Đầu ra của bảng điều khiển: sai Chính xác là cùng một kết quả, vậy chúng ta cần phương thức này để làm gì? :/ Tất cả đều đơn giản. Vấn đề ở đây là chúng ta hiện đang sử dụng phương thức này vì nó được triển khai trong lớp Đối tượng . Và nếu chúng ta đi sâu vào mã của lớp Object và xem xét cách triển khai phương thức, đây là những gì chúng ta sẽ thấy:
public boolean equals(Object obj) {
return (this == obj);
}
Đó là lý do tại sao hành vi của chương trình không thay đổi! Toán tử == rất giống nhau (so sánh các tham chiếu) được sử dụng bên trong phương thức equals() của lớp Object . Nhưng mẹo với phương pháp này là chúng ta có thể ghi đè lên nó. Để ghi đè có nghĩa là viết phương thức equals() của riêng bạn trong lớp Man của chúng ta , cung cấp cho nó hành vi mà chúng ta cần! Hiện tại, chúng tôi không thích thực tế là man1.equals(man2) về cơ bản tương đương với man1 == man2 . Đây là những gì chúng ta sẽ làm trong tình huống này:
public class Man {
int dnaCode;
public boolean equals(Man man) {
return this.dnaCode == man.dnaCode;
}
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = 1111222233;
Man man2 = new Man();
man2.dnaCode = 1111222233;
System.out.println(man1.equals(man2));
}
}
Đầu ra của bảng điều khiển: true Bây giờ chúng tôi nhận được một kết quả hoàn toàn khác! Bằng cách viết phương thức equals() của riêng mình và sử dụng nó thay vì phương thức tiêu chuẩn, chúng ta đã tạo ra hành vi chính xác: Bây giờ nếu hai người có cùng DNA, chương trình sẽ báo cáo "Phân tích DNA đã chứng minh họ là cặp song sinh" và trả về giá trị true! Bằng cách ghi đè phương thức equals() trong các lớp của mình, bạn có thể dễ dàng tạo bất kỳ logic so sánh đối tượng nào mà bạn cần. Trên thực tế, chúng ta mới chỉ đề cập đến so sánh đối tượng. Trước mắt chúng ta, vẫn còn một bài học độc lập lớn về chủ đề này (bạn có thể lướt qua nó ngay bây giờ nếu bạn quan tâm).
So sánh chuỗi trong Java
Tại sao chúng ta xem xét so sánh chuỗi tách biệt với mọi thứ khác? Thực tế là các chuỗi là một chủ đề theo đúng nghĩa của chúng trong lập trình. Đầu tiên, nếu bạn lấy tất cả các chương trình Java đã từng viết, bạn sẽ thấy rằng khoảng 25% đối tượng trong đó là các chuỗi. Vì vậy, chủ đề này là rất quan trọng. Thứ hai, quá trình so sánh các chuỗi thực sự rất khác so với các đối tượng khác. Hãy xem xét một ví dụ đơn giản:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}
Đầu ra của bàn điều khiển: sai Nhưng tại sao chúng tôi lại sai? Xét cho cùng, các chuỗi hoàn toàn giống nhau, từng từ một:/ Bạn có thể đoán được lý do: đó là vì toán tử == so sánh các tham chiếu ! Rõ ràng, s1 và s2 có các địa chỉ khác nhau trong bộ nhớ. Nếu bạn nghĩ về điều đó, thì hãy làm lại ví dụ của chúng tôi:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
System.out.println(s1 == s2);
}
}
Bây giờ chúng ta lại có hai tham chiếu, nhưng kết quả hoàn toàn ngược lại: Đầu ra bảng điều khiển: đúng Bối rối một cách bất lực? Hãy tìm hiểu những gì đang xảy ra. Toán tử == thực sự so sánh các địa chỉ bộ nhớ. Điều này luôn đúng và bạn không cần phải nghi ngờ về điều đó. Điều đó có nghĩa là nếu s1 == s2 trả về true, thì hai chuỗi này có cùng địa chỉ. Và thực sự điều này là đúng! Đã đến lúc giới thiệu với bạn một vùng bộ nhớ đặc biệt để lưu trữ các chuỗi: nhóm chuỗi
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
s2 trỏ đến cùng một vị trí với s1 . Câu lệnh đầu tiên tạo một chuỗi mới trong nhóm chuỗi. Câu lệnh thứ hai chỉ đơn giản đề cập đến cùng một vùng bộ nhớ như s1 . Bạn có thể tạo thêm 500 chuỗi giống hệt nhau và kết quả sẽ không thay đổi. Đợi tí. Nếu đó là sự thật, thì tại sao ví dụ này không hoạt động trước đây?
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}
Tôi nghĩ rằng trực giác của bạn đã cho bạn biết lý do =) Hãy thử đoán trước khi đọc tiếp. Bạn có thể thấy rằng hai chuỗi này được khai báo theo những cách khác nhau. Một cái có toán tử new và cái còn lại không có nó. Đây là lý do. Khi toán tử mới được sử dụng để tạo một đối tượng, nó sẽ phân bổ mạnh mẽ một vùng bộ nhớ mới cho đối tượng. Và một chuỗi được tạo bằng new không kết thúc trong nhóm chuỗi — nó trở thành một đối tượng riêng biệt, ngay cả khi văn bản của nó hoàn toàn khớp với một chuỗi trong nhóm chuỗi. Đó là, nếu chúng ta viết đoạn mã sau:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
String s3 = new String("CodeGym is the best website for learning Java!");
}
}
Trong bộ nhớ, nó trông như thế này:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1.equals(s2));
}
}
Đầu ra bảng điều khiển: đúng Thú vị. Chúng tôi chắc chắn rằng s1 và s2 trỏ đến các vùng khác nhau trong bộ nhớ. Nhưng phương thức equals() vẫn cho chúng ta biết chúng bằng nhau. Tại sao? Hãy nhớ rằng trước đây chúng ta đã nói rằng phương thức equals() có thể bị ghi đè để so sánh các đối tượng theo cách chúng ta muốn? Đó chỉ là những gì họ đã làm với lớp String . Nó ghi đè bằng()phương pháp. Và thay vì so sánh các tham chiếu, nó so sánh chuỗi ký tự trong chuỗi. Nếu văn bản giống nhau, thì việc chúng được tạo ra như thế nào hoặc chúng được lưu trữ ở đâu không quan trọng: cho dù trong nhóm chuỗi hay một vùng bộ nhớ riêng biệt. Kết quả so sánh sẽ đúng. Nhân tiện, Java cho phép bạn thực hiện so sánh chuỗi không phân biệt chữ hoa chữ thường. Thông thường, nếu một trong các chuỗi có tất cả các chữ cái viết hoa, thì kết quả so sánh sẽ là sai:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CODEGYM IS THE BEST WEBSITE FOR LEARNING JAVA!");
System.out.println(s1.equals(s2));
}
}
Đầu ra của bàn điều khiển: sai Để so sánh không phân biệt chữ hoa chữ thường, lớp Chuỗi có phương thức equalsIgnoreCase() . Bạn có thể sử dụng nó nếu bạn chỉ quan tâm đến việc so sánh chuỗi ký tự cụ thể hơn là trường hợp chữ cái. Ví dụ: điều này có thể hữu ích khi so sánh hai địa chỉ:
public class Main {
public static void main(String[] args) {
String address1 = "2311 Broadway Street, San Francisco";
String address2 = new String("2311 BROADWAY STREET, SAN FRANCISCO");
System.out.println(address1.equalsIgnoreCase(address2));
}
}
Trong trường hợp này, rõ ràng là chúng ta đang nói về cùng một địa chỉ, vì vậy sẽ hợp lý khi sử dụng phương thức equalsIgnoreCase() .
Phương thức String.intern()
Lớp String có một phương thức phức tạp hơn: intern() ; Phương thức intern() hoạt động trực tiếp với nhóm chuỗi. Nếu bạn gọi phương thức intern() trên một số chuỗi:- Nó kiểm tra xem có một chuỗi phù hợp trong nhóm chuỗi
- Nếu có, nó trả về tham chiếu đến chuỗi trong nhóm
- Nếu không, nó sẽ thêm chuỗi vào nhóm chuỗi và trả về một tham chiếu đến chuỗi đó.
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2.intern());
}
}
Đầu ra của bảng điều khiển: true Khi chúng tôi so sánh các chuỗi này trước đây mà không có intern() , kết quả là sai. Bây giờ, phương thức intern() kiểm tra xem chuỗi "CodeGym có phải là trang web tốt nhất để học Java hay không!" nằm trong nhóm chuỗi. Tất nhiên, đó là: chúng tôi đã tạo ra nó với
String s1 = "CodeGym is the best website for learning Java!";
Chúng tôi kiểm tra xem s1 và tham chiếu được trả về bởi s2.intern() có trỏ đến cùng một vùng bộ nhớ hay không. Và tất nhiên là có :) Tóm lại, hãy ghi nhớ và áp dụng quy tắc quan trọng này: LUÔN LUÔN sử dụng phương thức equals() để so sánh các chuỗi! Khi so sánh các chuỗi, hầu như chúng ta luôn muốn so sánh các ký tự của chúng hơn là so sánh các tham chiếu, vùng bộ nhớ hoặc bất kỳ thứ gì khác. Phương thức equals() thực hiện chính xác những gì bạn cần. Để củng cố những gì bạn đã học, chúng tôi khuyên bạn nên xem một video bài học từ Khóa học Java của chúng tôi
Đọc thêm:
GO TO FULL VERSION