CodeGym /Blog Java /Random-PL /Porównaj porównania ciągów i równań w Javie
Autor
Milan Vucic
Programming Tutor at Codementor.io

Porównaj porównania ciągów i równań w Javie

Opublikowano w grupie Random-PL
Cześć! Dzisiaj porozmawiamy o bardzo ważnym i ciekawym temacie, a mianowicie o porównywaniu obiektów z obiektami (Porównaj ciągi i równania). Więc w Javie, kiedy dokładnie obiekt A byłby równy obiektowi B ? Spróbujmy napisać przykład:

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);
   }
}
Wyjście konsoli: false Zaczekaj, zatrzymaj się. Dlaczego te dwa samochody nie są sobie równe? Przypisaliśmy im te same właściwości, ale wynik porównania jest fałszywy. Odpowiedź jest prosta. Operator == porównuje odwołania do obiektów, a nie właściwości obiektów. Dwa obiekty mogą mieć nawet 500 pól o identycznych wartościach, ale ich porównanie nadal dawałoby fałsz. Przecież odniesienia car1 i car2wskazują na dwa różne obiekty, czyli na dwa różne adresy. Wyobraź sobie sytuację, w której porównujesz ludzi. Z pewnością gdzieś na świecie jest osoba, która ma takie samo imię, kolor oczu, wiek, wzrost, kolor włosów itp. To sprawia, że ​​jesteście do siebie podobni pod wieloma względami, ale nadal nie jesteście bliźniakami — i oczywiście nie jesteście ta sama osoba.
Równa się i porównania ciągów - 2
Operator == używa mniej więcej tej samej logiki, gdy używamy go do porównania dwóch obiektów. Ale co, jeśli potrzebujesz, aby Twój program używał innej logiki? Załóżmy na przykład, że twój program przeprowadza analizę DNA. Porównuje kod genetyczny dwóch osób i określa, czy są bliźniakami.

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);
   }
}
Wyjście konsoli: false Otrzymujemy ten sam wynik logiczny (ponieważ niewiele zmieniliśmy), ale teraz ta logika nie jest dobra! W końcu w prawdziwym życiu analiza DNA powinna dać nam 100% gwarancję, że mamy przed sobą bliźniaki. Ale nasz program i operator == mówią nam coś przeciwnego. Jak zmienić to zachowanie i upewnić się, że program wyświetli prawidłowy wynik, gdy DNA się zgadza? Java ma do tego specjalną metodę: equals() . Podobnie jak metoda toString() , o której mówiliśmy wcześniej, equals() należy do klasy Object — najważniejszej klasy w Javie, od której wywodzą się wszystkie inne klasy. Ale równa się()samo z siebie nie zmienia zachowania naszego programu:

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));
   }
}
Wyjście konsoli: false Dokładnie ten sam wynik, więc po co nam ta metoda? :/ To wszystko jest proste. Problem polega na tym, że obecnie używamy tej metody, ponieważ jest ona zaimplementowana w klasie Object . A jeśli przejdziemy do kodu klasy Object i przyjrzymy się implementacji metody, zobaczymy to:

public boolean equals(Object obj) {
   return (this == obj);
}
To jest powód, dla którego zachowanie programu się nie zmieniło! Ten sam operator == (który porównuje referencje) jest używany wewnątrz metody equals() klasy Object . Ale sztuczka z tą metodą polega na tym, że możemy ją zastąpić. Przesłonięcie oznacza napisanie własnej metody equals() w naszej klasie Man , nadając jej zachowanie, którego potrzebujemy! Obecnie nie podoba nam się fakt, że man1.equals(man2) jest zasadniczo odpowiednikiem man1 == man2 . Oto, co zrobimy w tej sytuacji:

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)); 

   } 
} 
Wyjście konsoli: true Teraz otrzymujemy zupełnie inny wynik! Pisząc własną metodę equals() i używając jej zamiast standardowej, uzyskaliśmy prawidłowe zachowanie: Teraz, jeśli dwie osoby mają to samo DNA, program zgłasza „Analiza DNA wykazała, że ​​są bliźniakami” i zwraca wartość true! Zastępując metodę equals() w swoich klasach, możesz łatwo stworzyć dowolną logikę porównywania obiektów, jakiej potrzebujesz. Właściwie dopiero co dotknęliśmy porównania obiektów. Przed nami jeszcze duża samodzielna lekcja na ten temat (przejrzyj ją teraz, jeśli jesteś zainteresowany).

Porównywanie ciągów znaków w Javie

Dlaczego rozważamy porównania łańcuchów oddzielnie od wszystkiego innego? Rzeczywistość jest taka, że ​​łańcuchy są odrębnym tematem w programowaniu. Po pierwsze, jeśli weźmiemy pod uwagę wszystkie programy Java, jakie kiedykolwiek napisano, okaże się, że około 25% zawartych w nich obiektów to łańcuchy znaków. Więc ten temat jest bardzo ważny. Po drugie, proces porównywania ciągów naprawdę bardzo różni się od innych obiektów. Rozważ prosty przykład:

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);
   }
}
Wyjście konsoli: false Ale dlaczego otrzymaliśmy false? W końcu łańcuchy są dokładnie takie same, słowo w słowo :/ Być może odgadłeś przyczynę: to dlatego, że operator == porównuje referencje ! Oczywiście s1 i s2 mają różne adresy w pamięci. Jeśli o tym pomyślałeś, przeróbmy nasz przykład:

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);
   }
}
Teraz znowu mamy dwa odniesienia, ale wynik jest dokładnie odwrotny: Dane wyjściowe konsoli: prawda Bezradnie zdezorientowany? Dowiedzmy się, co się dzieje. Operator == naprawdę porównuje adresy pamięci. To jest zawsze prawda i nie musisz w to wątpić. Oznacza to, że jeśli s1 == s2 zwraca wartość true, to te dwa łańcuchy mają ten sam adres. I rzeczywiście, to prawda! Nadszedł czas, aby przedstawić ci specjalny obszar pamięci do przechowywania łańcuchów: pulę ciągów
Równa się i porównania ciągów - 3
Pula łańcuchów to obszar do przechowywania wszystkich wartości łańcuchów, które tworzysz w swoim programie. Dlaczego powstał? Jak powiedzieliśmy wcześniej, łańcuchy stanowią ogromny procent wszystkich obiektów. Każdy duży program tworzy wiele łańcuchów. Pula ciągów została utworzona w celu oszczędzania pamięci: umieszczane są tam ciągi, a następnie tworzone kolejno ciągi odnoszą się do tego samego obszaru pamięci – nie ma potrzeby każdorazowego przydzielania dodatkowej pamięci. Za każdym razem, gdy zapiszesz String = "........." program sprawdza, czy w puli ciągów znajduje się identyczny ciąg. Jeśli tak, nowy ciąg nie zostanie utworzony. A nowe odwołanie będzie wskazywało na ten sam adres w puli ciągów (gdzie znajduje się identyczny ciąg). Więc kiedy pisaliśmy

String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
s2 wskazuje to samo miejsce co s1 . Pierwsza instrukcja tworzy nowy ciąg w puli ciągów. Druga instrukcja po prostu odnosi się do tego samego obszaru pamięci co s1 . Możesz zrobić kolejne 500 identycznych ciągów, a wynik się nie zmieni. Poczekaj minutę. Jeśli to prawda, to dlaczego ten przykład nie działał wcześniej?

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);
   }
}
Myślę, że twoja intuicja już ci powiedziała powód =) Spróbuj zgadnąć, zanim zaczniesz czytać dalej. Widać, że te dwa łańcuchy zostały zadeklarowane na różne sposoby. Jeden z nowym operatorem, a drugi bez niego. W tym leży przyczyna. Gdy operator new jest używany do tworzenia obiektu, wymusza on alokację nowego obszaru pamięci dla obiektu. Ciąg utworzony przy użyciu polecenia new nie trafia do puli ciągów — staje się osobnym obiektem, nawet jeśli jego tekst idealnie pasuje do ciągu w puli ciągów. To znaczy, jeśli napiszemy następujący kod:

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!");
   }
}
W pamięci wygląda to tak:
Równa się i porównania ciągów - 4
I za każdym razem, gdy tworzysz nowy obiekt przy użyciu new , przydzielany jest nowy obszar pamięci, nawet jeśli tekst wewnątrz nowego ciągu jest taki sam! Wygląda na to, że odkryliśmy operatora == . Ale co z naszym nowym znajomym, metodą equals() ?

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));
   }
}
Wyjście konsoli: true Ciekawe. Jesteśmy pewni, że s1 i s2 wskazują różne obszary pamięci. Ale metoda equals() nadal mówi nam, że są równe. Dlaczego? Pamiętasz, jak powiedzieliśmy wcześniej, że metodę equals() można przesłonić, aby porównywać obiekty w dowolny sposób? Właśnie to zrobili z klasą String . Zastępuje równanie ()metoda. I zamiast porównywać referencje, porównuje sekwencję znaków w łańcuchach. Jeśli tekst jest taki sam, nie ma znaczenia, w jaki sposób zostały utworzone ani gdzie są przechowywane: czy w puli ciągów znaków, czy w oddzielnym obszarze pamięci. Wynik porównania będzie prawdziwy. Nawiasem mówiąc, Java umożliwia porównywanie ciągów bez uwzględniania wielkości liter. Normalnie, jeśli jeden z ciągów ma wszystkie wielkie litery, wynik porównania będzie fałszywy:

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));
   }
}
Dane wyjściowe konsoli: false W przypadku porównań bez uwzględniania wielkości liter klasa String ma metodę equalsIgnoreCase() . Możesz go użyć, jeśli zależy Ci tylko na porównaniu sekwencji określonych znaków, a nie wielkości liter. Na przykład może to być pomocne przy porównywaniu dwóch adresów:

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));
   }
}
W tym przypadku mówimy oczywiście o tym samym adresie, więc sensowne jest użycie metody equalsIgnoreCase() .

Metoda String.intern().

Klasa String ma jeszcze jedną trudną metodę: intern() ; Metoda intern() działa bezpośrednio z pulą ciągów znaków. Jeśli wywołasz metodę intern() na jakimś łańcuchu:
  • Sprawdza, czy w puli ciągów znajduje się pasujący ciąg
  • Jeśli tak, zwraca odwołanie do łańcucha w puli
  • Jeśli nie, dodaje ciąg do puli ciągów i zwraca do niego odwołanie.
Po użyciu metody intern() na odwołaniu do łańcucha otrzymanym za pomocą new możemy użyć operatora == do porównania go z odwołaniem do ciągu z puli ciągów.

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());
   }
}
Dane wyjściowe konsoli: prawda Kiedy porównaliśmy te łańcuchy wcześniej bez intern() , wynik był fałszywy. Teraz metoda intern() sprawdza, czy napis „CodeGym to najlepsza strona do nauki języka Java!” jest w puli łańcuchów. Oczywiście, że jest: stworzyliśmy go z

String s1 = "CodeGym is the best website for learning Java!";
Sprawdzamy, czy s1 i referencja zwrócona przez s2.intern() wskazują na ten sam obszar pamięci. I oczywiście, że tak :) Podsumowując, zapamiętaj i zastosuj tę ważną zasadę: ZAWSZE używaj metody equals() do porównywania ciągów znaków! Porównując ciągi, prawie zawsze mamy na myśli porównanie ich znaków, a nie odniesień, obszarów pamięci lub czegokolwiek innego. Metoda equals() robi dokładnie to, czego potrzebujesz. Aby utrwalić to, czego się nauczyłeś, zalecamy obejrzenie lekcji wideo z naszego kursu języka Java

Więcej czytania:

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION