24 kwietnia 2015

equals() vs '=='

Częstą praktyką wśród młodych programistów jest korzystanie z operatora '==' do porównywania zmiennych typu String oraz chociażby Integer. Po pewnym czasie użytkowania tego mechanizmu często dochodzi do sytuacji, w której młody programista wpada w zakłopotanie przy porównywaniu dwóch identycznych wartości tekstowych. Dlaczego? Ponieważ spodziewając się wyniku true otrzymują oni niezrozumiałą dla nich wartość false. Dlaczego tak się dzieje?

Content:
  • Czym różni się operator '==' od metody equals()
  • Przykłady implementacji metody equals()

Czym różni się operator '==' od metody equals()


Najważniejszą różnicą jest porównywana informacja. W przypadku operatora '==' porównywana jest przestrzeń pamięci naszej aplikacji. A dokładniej sprawdzane jest czy wartość a i wartość b zapisane są w tym samym miejscu w pamięci naszej wirtualnej maszyny. Jeśli obie zmienne odwołują się do tego samego miejsca, wtedy wartość otrzymana będzie równa true. Jeśli natomiast wartości zmiennych będą identyczne, jednak zapisane w innych miejscach w pamięci - wtedy naszym wynikiem będzie false.

W przypadku metody equals()(przyjmijmy przykład dla zmiennej typu String) porównywana jest wartość (znak po znaku) zmiennej a i b. Niezależnie czy odwołują się one do tego samego miejsca w pamięci czy też nie (oraz przy założeniu że ich wartość jest identyczna) wynik wywołania metody equals() będzie równy true.

Może czas na przykład:

        String a = "test"; // tworzy obiekt typu String
        String b = "test"; // tworzy referencje do obiektu a
        
        String c = new String("test"); // tworzy nowy obiekt typu String 
        
        // ten if zwróci true
        if(a == b){

            System.out.println("True!");
        }
        else System.out.println("False!");
        
        // ten if zwróci false
        if(a == c){

            System.out.println("True!");
        }
        else System.out.println("False!");

Konsola:
run:
True!
False!
BUILD SUCCESSFUL (total time: 0 seconds)

W powyższym przykładzie jasno widać, iż operator '==' porównuje czy obie wartości zapisane są w tym samym miejscu w pamięci. Na wyjaśnienie zasługuje fakt, iż konstrukcja String c = new String("test") nie sprawdza w String pool istnienia obiektu typu String o wartości test. (Jeśli chcesz wiedzieć dlaczego tak się dzieje i o co chodzi z String pool zajrzyj tutaj).

Teraz sprawdźmy jaki wynik przyniesie metoda equals():
        String a = "test"; // tworzy obiekt typu String
        String b = "test"; // tworzy referencje do obiektu a
        
        String c = new String("test"); // tworzy nowy obiekt typu String 
        
        // ten if zwróci true
        if(a.equals(b)){

            System.out.println("True!");
        }
        else System.out.println("False!");
        
        // ten if również zwróci true
        if(a.equals(c)){

            System.out.println("True!");
        }
        else System.out.println("False!");

Konsola:
run:
True!
True!
BUILD SUCCESSFUL (total time: 0 seconds)

Jak widać metoda equals() porównuje bezpośrednio wartość podanych zmiennych, mimo iż wartości zmiennej a i c znajdują się w innej przestrzeni pamięci naszej aplikacji, wartość metody w obu przypadkach pozostaje równa true!
Ważna konkluzja: Do porównywania Stringów używamy tylko i wyłącznie metody equals(). Wyjątkiem od tej reguły może być bezpośrednie powiązanie wartości z przestrzenią w pamięci.


Przykłady implementacji metody equals()


Na początek przyjrzyjmy się implementacji metody equals() wewnątrz klasy string:
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

Jest to metoda zwracająca boolean, więc jej wynikiem zawsze będzie true albo false. Co dzieje się po kolei:

1. Metoda przyjmuje Object jako anObject (w naszym przypadku String) oraz "wrzuca" go do pierwszego bloku if. Tu bardzo ważna kwestia (potwierdzająca to co omawialiśmy w poście Niezmienność typu String - metoda sprawdza czy anObject nie odwołuje się do tego samego miejsca w pamięci naszej aplikacji co podany przez nas do porównania String. Jeśli tak, to automatycznie zostaje zwrócona wartość true. Jest to wyraźny przykład zwiększania wydajności metody - zdecydowanie warto stosować takie rozwiązania!

2. Jeśli anObject nie odwołuje się do tego samego miejsca w pamięci wtedy następuje kolejny blok if który to kolejno:
a) poprzez wywołanie operatora instanceof sprawdza czy anObject jest na pewno obiektem klasy String (wszak ciężko jest porównywać coś co nie jest tej samej klasy...)
b) następnie tworzy kolejną instancję klasy String - anotherString
c) tworzy zmienną typu int, która przyjmuje wartość równą ilości znaków zawartych w podanym przez nas Stringu (value - zmienna tablicowa typu char służąca do przechowywania poszczególnych znaków naszego Stringa; .length - zwraca ilość znaków zawartą w zmiennej value)
d) kolejny blok if sprawdzający czy długość obu podanych Stringów jest jednakowa - jeśli tak to przechodzimy dalej, jeśli nie zwracamy false (kolejny element poprawiający wydajność!)
e) następnie metoda tworzy dwie tablice typu char (v1 i v2) przyjmujące kolejno znaki obiektu, który podaliśmy oraz obiektu porównywanego anObject
f) tworzy int i jako licznik inkrementacji
g) blok while, który od wartości n(długość naszego Stringa - ilość char'ów) odejmuje przy każdym przebiegu 1 (dopóki n jest różne od 0) i sprawdza każdą literę obu Stringów (nasz String oraz anObject)po kolei pod względem równości - jeśli któraś z liter będzie odbiegać od litery o tym samym indeksie w drugim słowie, wtedy automatycznie zostanie zwrócona wartość false

To by było na tyle jeśli chodzi o metodę equals() wewnątrz klasy String. Mam nadzieję, że zauważyliście również zalety płynące z czytania kodu źródłowego Javy :)

Może dla porównania i potwierdzenia różnorodności implementacji metody equals() w różnych klasach weźmy na warsztat tę metodę z klasy Integer:
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

W tym przypadku nie mamy za bardzo czego opisywać. Najprostsze w świecie porównanie dwóch wartości.

W tym miejscu moglibyśmy skończyć. Jak zwykle (od dwóch postów :) ) zachęcam do komentowania oraz uzupełniania zapisanych przeze mnie informacji. Mam nadzieję że wam się przydadzą.

1 komentarz:

  1. Best Roulette and Slots with Real Money Casinos
    Lucky Club has a huge range of slot and table games that cater to both high and low stakes players. luckyclub.live If you love to gamble online, you can also find the

    OdpowiedzUsuń