Równość i Identyczność Integerów

Dowiedz się czym jest równość i identyczność Integerów w Javie. Co lepiej używać do ich tworzenia: konstruktora czy statycznej metody fabrycznej valueOf?

Równość i Identyczność Integerów

Równość a Identyczność wokół nas

Równość (ang. Equality) sprawdzamy poprzez porównanie stanów dwóch obiektów. Przeprowadźmy eksperyment myślowy mający na celu porównanie dwóch butelek z cieczą z założeniem, że objętość cieczy w butelkach będzie taka sama. W jednej butelce jest woda, w drugiej sok (patrz obrazek poniżej). Czy te butelki z cieczą są sobie równe? Nie są: pomimo, że obydwa znajdują się w butelce, ciecz w nich umieszczona nie jest tego samego rodzaju (stan się różni). Natomiast gdy porównamy ze sobą dwie butelki z wodą, możemy stwierdzić, że są one sobie równe: obie znajdują się w butelce oraz obie są cieczą tego samego rodzaju (stan jest taki sam).

Przykład równości butelek z cieczą

Identyczność (ang. Identity) sprawdzamy poprzez porównanie dwóch obiektów, z czego drugi obiekt może być tym samym obiektem co pierwszy. Dwie różne butelki z wodą są równe, ale nie są identyczne: są to dwa odrębne obiekty. Wyobraź sobie, że położyłeś butelkę wody na stole, a podczas Twojej nieuwagi inny domownik przełożył tę butelkę na półkę w innym pokoju. Zaczniesz szukać butelki z wodą: identycznej, czyli tej która stała na stole. Nie interesują Cię inne butelki (nie są identyczne) tylko ta jedna, która stała wcześniej na stole. Gdy porównamy butelkę wody stojącą wcześniej na stole z tą stojącą teraz na półce w drugim pokoju po przełożeniu zauważymy, że są one równe (ta sama butelka i rodzaj cieczy: woda) i identyczne (to jeden i ten sam obiekt).

Przykład identyczności butelek z cieczą

Równość a Identyczność w Javie

W Javie sprawa wygląda podobnie. Równość sprawdzamy metodą equals klasy java.lang.Object natomiast identyczność operatorem ==.

Sprawdzanie równości i identyczności w Javie na obiektach a i b

Identyczność sprawdza czy dwa obiekty wskazują do tego samego miejsca w pamięci komputera, czyli czy jest to jeden i ten sam obiekt. Porównuje się w tym celu referencje do obiektów, czyli adresy obiektów w pamięci komputera. Gdy adres jest taki sam, porównywane obiekty są identyczne.

Równość sprawdza, czy dwa obiekty są sobie równe. Domyślnie jest sprawdzana po prostu identyczność jednak możemy nadpisać metodę equals i podać własne kryteria określające czy dany obiekt jest równy innemu (np. porównując jego pola z polami drugiego obiektu).

Metoda equals w klasie java.lang.Object
ZAPAMIĘTAJ!
Kiedy tworzymy obiekt, komputer przechowuje go w swojej pamięci, gdzie przypisywany jest mu adres. Dzięki temu komputer może go później odnaleźć.
UWAGA!
Równe obiekty muszą posiadać również równe wartości hash code zwracane przez metodę hashCode klasy java.lang.Object.
Nie działa to w drugą stronę: obiekty zwracające tę samą wartość hash code nie muszą być równe.

Czym jest Integer

Klasa java.lang.Integer reprezentuje liczby całkowite: opakowuje wartość prymitywnego typu int przez co jest nazywana klasą opakowującą (ang. wrapper class). Innymi słowy klasa Integer posiada jedno pole typu int.

Pole typu int w klasie java.lang.Integer

Czym jest IntegerCache

Jaka jest różnica między new Integer(5) a Integer.valueOf(5)?

Pierwsze to konstruktor, który zawsze tworzy obiekt. Został on zdeprecjonowany w Javie 9, czyli jest przestarzały i nie powinno się go używać. W Javie 16 został oznaczony do usunięcia, co oznacza, że w następnych wersjach Javy zostanie usunięty.

Konstruktor klasy java.lang.Integer przyjmujący parametr typu int

Drugie to statyczna metoda fabryczna (ang. static factory method) zwracająca instancję klasy. Metoda ta korzysta z IntegerCache, dzięki któremu obiekty Integer nie zawsze są tworzone. IntegerCache może zwrócić obiekt Integer, który został uprzednio zcache'owany (stworzony i umieszczony w cache do późniejszego użytku).

Statyczne metoda fabryczna valueOf klasy java.lang.Integer korzystająca z IntegerCache

IntegerCache (dodany w Javie 5) to prywatna, statyczna, wewnętrzna klasa znajdująca się w klasie Integer. Cache'uje obiekty od -128 do 127 włącznie co zwiększa wydajność i zmniejsza wykorzystanie pamięci komputera. Cache ten jest inicjalizowany przy pierwszym użyciu, zaś jego rozmiar można zmodyfikować poprzez użycie flagi: -XX:AutoBoxCacheMax=<size>. Parametr <size> to liczba maksymalna: domyślnie 127. Jeżeli określisz rozmiar cache na liczbę mniejszą niż 127, zostanie ona nadpisana liczbą 127.

Autoboxing, czyli automatyczna zamiana typu prymitywnego w odpowiadający mu obiekt korzysta z mechanizmu cache'owania.

// To jest autoboxing
Integer fiveAutoboxing = 5;

// Autoboxing w rzeczywistości wywołuje poniższą metodę
Integer fiveStaticFactoryMethod = Integer.valueOf(5);

IntegerCache w praktyce

Co wypisze w konsoli poniższy kod?

Integer a = 20;
Integer b = 20;
System.out.println("Czy a i b są równe: " + a.equals(b));
System.out.println("Czy a i b są identyczne: " + (a == b));

Integer c = 200;
Integer d = 200;
System.out.println("Czy c i d są równe: " + c.equals(d));
System.out.println("Czy c i d są identyczne: " + (c == d));

Używając autoboxingu tworzymy 4 zmienne całkowite: a i b o wartości 20 oraz c i d o wartości 200.

Jak już wspomniałem wcześniej, operator == w Javie sprawdza czy porównywane obiekty wskazują do tego samego miejsca w pamięci. Jeżeli będzie to ten sam obiekt w pamięci, otrzymamy true, w przeciwnym wypadku false.

Autoboxing używa mechanizmu cache'owania dla wartości od -128 do 127. W związku z tym a i b będzie jednym i tym samym obiektem (zwróconym z cache). Natomiast c i d będą różnymi obiektami, ponieważ każdy obiekt Integer większy od 127 i mniejszy od -128 będzie nowym obiektem w pamięci, gdyż statyczna metoda fabryczna tworzy nowy obiekt w tym zakresie!

Wynik wypisany w konsoli

Cache w innych obiektach opakowujących

Mechanizm cachowania w statycznej metodzie fabrycznej valueOf występuje nie tylko w Integerach, ale także w innych klasach opakowujących typy prymitywne:

  • Byte z mechanizmem ByteCache
  • Short z mechanizmem ShortCache
  • Long z mechanizmem LongCache
  • Character z mechanizmem CharacterCache

ByteCache, ShortCache i LongCache również cache'ują obiekty od -128 do 127 włącznie. Natomiast CharacterCache cache'uje znaki unicode od 0 do 127 włącznie, czyli od znaku \u0000 do znaku \u007F.

Tylko rozmiar IntegerCache może być modyfikowany przez flagę.

UWAGA!
Float i Double nie korzystują z mechanizmu cache'owania i zawsze tworzą nowy obiekt. Boolean nie posiada cache, ponieważ wystarczą mu tylko dwa stworzone wcześniej obiekty: TRUE i FALSE, które zwraca ze statycznej metody fabrycznej valueOf.

GitHub

Kod źródłowy jest udostępniony na GitHubie. Poniżej link 🙂

javowiec-pl-java/rownosc-i-identycznosc-integerow at main · JavowiecPL/javowiec-pl-java
Gotowe projekty z kategorii Java stworzone na potrzeby postów publikowanych na blogu Javowiec.pl - javowiec-pl-java/rownosc-i-identycznosc-integerow at main · JavowiecPL/javowiec-pl-java

Podsumowanie

Identyczne obiekty wskazują do tego samego miejsca w pamięci komputera, czyli ich referencje (adresy w pamięci) są takie same: jest to jeden i ten sam obiekt. Równe obiekty posiadają ten sam stan, czyli ich pola są równe. Identyczne obiekty zawsze są równe. Równe obiekty nie zawsze są identyczne. Odnosi się to zarówno do Integerów jak i innych obiektów.

Do tworzenia Integerów używaj statycznej metody fabrycznej valueOf np. Integer.valueOf(1) lub autoboxingu, które korzystają z IntegerCache: mechanizmu zwiększającego wydajność i zmniejszającego wykorzystanie pamięci komputera. Autoboxing tak naprawdę wywołuje statyczną metodę fabryczną valueOf. Konstruktor Integera jest zdeprecjonowany od Javy 9 i oznaczony do usunięcia od Javy 16. Nie powinien być używany i w kolejnych wersjach Javy zostanie usunięty.

Liczba komentarzy:

Zaloguj się lub dołącz do społeczności Javowców, aby móc uczestniczyć w dyskusji 🙂