Wstęp
W świecie programowania obiektowego
, gdzie obiekty
stanowią podstawowe jednostki, umiejętność porównywania ich ze sobą jest kluczowym elementem, wpływającym istotnie na efektywność tworzenia kodu. Porównywanie obiektów to jedno z fundamentalnych zagadnień w programowaniu obiektowym OOP
. W tym artykule przyjrzymy się bliżej temu mechanizmowi.
Dlaczego dwie zmienne mogą wydawać się identyczne, ale porównanie ich za pomocą operatora ==
może zaskakująco zwrócić wartość false
? To właśnie jedno z wielu pytań, które postaramy się dziś rozwikłać.
Równość równości nierówna 🤯
W kontekście programowania obiektowego, w tym również w języku Java, dwa obiekty są uważane za równe
, gdy spełniają określone przez programistę kryteria równości.
Równość referencyjna
oraz równość logiczna
to dwa odrębne sposoby porównywania obiektów w Javie. W tym wpisie skupimy się głównie na porównywaniu referencji.
Jeśli potrzebujesz przypomnieć sobie podstawowe informacje na temat tego, czym tak właściwie są obiekty, odsyłam Cię do mojego wpisu:
javamPokaze || Podstawy programowania obiektowego
Czym jest referencja?
String text = new String("Hello");
Powyżej mamy prosty fragment kodu. Generalnie w Javie obiekty typu String
tworzymy w prostszy sposob: String text = "Hello";
My jednak użyjemy pierwszej deklaracji, aby w jasny sposób wytłumaczyć pewne sprawy. Nasza zmienna o nazwie text
,to właśnie nasza referencja
, czyli swego rodzaju etykieta wskazująca na miejsce w pamieci komputera, gdzie przechowywany jest stworzony obiekt
. To pozwala programowi korzystać z danych wewnątrz tego obiektu
poprzez wspomnianą etykietę.
Kiedy używasz słowa kluczowego new
, tworzysz nowy obiekt w pamięci, a referencja text
wskazuje na to miejsce. To umożliwia przechowywanie danych, w tym przypadku tekstu Hello
, i operowanie nimi w programie. Referencje
są istotne, ponieważ pozwalają nam dynamicznie alokować i używać danych w trakcie działania programu.
W skrócie, referencje w Javie
pełnią rolę adresu w pamięci
do konkretnego obiektu, umożliwiając Ci dostęp i manipulację danymi przechowywanymi wewnątrz tego obiektu.
Równość referencyjna ==
Dwa obiekty są uważane za równe referencyjnie
, gdy posiadają te same adresy w pamięci komputera. Miejsce, gdzie przechowywane są obiekty, to sterta (heap)
w przypadku języków programowania obsługujących alokację dynamiczną pamięci, takich jak Java
, Python
, C#
.
Równość referencyjna
oznacza, że obiekt A
i obiekt B
wskazują na dokładnie ten sam obszar w pamięci, informując nas jednocześnie, że są to te same obiekty w sensie fizycznym.
⚠️ Operator ==
porównuje referencje do obiektów, czyli sprawdza, czy dwie zmienne wskazują na ten sam obszar pamięci, reprezentując ten sam obiekt
⚠️ W przypadku obiektów porównywanych za pomocą ==
, zwróci true
wtedy i tylko wtedy, gdy obie referencje wskazują na dokładnie ten sam obiekt
Domyślnie, jeśli nie nadpiszesz w swojej klasie metody equals()
(o tej metodzie opowiemy w kolejnym wpisie), to porównanie ==
będzie sprawdzać równość referencyjną
.
💥Przykład 1 - obiekty typu String
String str1 = "cat"; String str2 = "cat"; if (str1 == str2) System.out.println("str1 i str2 są równe referencyjnie"); else System.out.println("str1 i str2 nie są równe referencyjnie");
W Javie ciągi znaków są traktowane jako obiekty, więc operator ==
porównuje referencje obiektów, nie ich zawartość. Zatem w powyższym przykładzie, gdzie tworzymy 2 zupełnie niezależne obiekty, mogłoby się wydawać, że wynikiem porównania będzie false
.
Jadnak Java wykorzystuje pewien mechanizm zwany String pooling
, czyli pulę ciągów znaków. W tej puli ciągi znaków, identyczne treści
są przechowywane jako jedna instancja, co oznacza, że dwie zmienne zawierające identyczne literały będą wskazywać na ten sam obiekt w pamięci.
W związku z tym porównanie str1 == str2
zwróci wartość true
, ponieważ obie zmienne wskazują na ten sam obiekt w puli ciągów znaków. Oznacza to, że str1
i str2
są równe referencyjnie
.
💥Przykład 2 - kolejne obiekty typu String
String str1 = new String("dog"); String str2 = new String("dog"); if (str1 == str2) System.out.println("str1 i str2 są równe referencyjnie"); else System.out.println("str1 i str2 nie są równe referencyjnie");
W tym przypadku, nawet jeśli zawartość obu ciągów znaków jest taka sama (dog
), obiekty są tworzone za pomocą operatora new
, co oznacza, że każdy z nich będzie miał własną referencję w pamięci (będą funkcjonować jako 2 niezależne obiekty). W rezultacie str1 == str2
zwróci wartość false
, ponieważ porównuje referencje, a nie zawartość ciągów znaków. Zatem str1
oraz str2
nie są równe referencyjnie.
💥 Przykład 3 - Obiekty tej samej klasy
Poniżej mamy porównanie referencyjne
dla obiektów tej samej klasy Cat
Cat cat1 = new Cat("Miau", 2); // Głos oraz wiek kota Cat cat2 = new Cat("Miau", 2); if (point1 == point2) System.out.println("point1 i point2 są równe referencyjnie"); else System.out.println("point1 i point2 nie są równe referencyjnie");
W tym przykładzie tworzymy dwie instancje klasy Cat
o identycznych wartościach pól “Miau”
oraz 2
. Jednak obie te instancje są osobnymi obiektami w pamięci, ponieważ zostały utworzone za pomocą operatora new
. Dlatego cat1 == cat2
zwróci wartość false
, ponieważ porównujemy referencje do dwóch różnych obiektów. Wynikiem wypisanym na ekranie będzie : cat1
i
cat2
nie są równe referencyjnie
.
cat2
💥 Przykład 4 - Obiekty różnych klas
Tworzymy 2 osobne klasy: Dog
oraz Cat
.
W tym przypadku nie wykorzystujemy mechanizmu dziedziczenia, więc klasy Dog
i Cat
nie mają wspólnego przodka (klasy bazowej), np. Animal
. W naszym przykładzie obiekt klasy Dog
nie ma dostępu do pól ani metod klasy Cat
, i odwrotnie.
Cat cat = new Cat("Jan", 21); Dog dog = new Dog("Michał", 34); Dog dog1 = new Dog("Paweł", 21); System.out.println(dog == dog1); // false, różne miejsca w pamięci dog1 = dog; System.out.println(dog == dog1); // true, są równe referencyjnie System.out.println(dog == cat); // false, różne typy cat = dog; // kod nie skompiluje się
Obiekty klasy Dog
i Cat
są traktowane jako niezwiązane ze sobą, ponieważ nie mają wspólnej klasy bazowej, np. Animal
. Niezwykle istotne są dwie kwestie:
⚠️ Porównanie referencyjne
między obiektami różnych typów zwraca false
⚠️ Próba przypisania cat = dog;
spowoduje błąd kompilacji z powodu niezgodności typów (kot nigdy nie zostanie psem…)
💥 Przykład 5 - obiekty w hierarchi dziedziczenia
Tym razem klasy Cat
oraz Dog
dziedziczą po klasie Animal
, zatem obie są spokrewnione przez wspólnego przodka. W tym przypadku obie klasy mogą dziedziczyć pola i metody klasy Animal
.
public class Animal { String name; int age; public Animal(String name, int age) { this.name = name; this.age = age; } } public class Cat extends Animal { public Cat(String name, int age) { super(name, age); } } public class Dog extends Animal { public Dog(String name, int age) { super(name, age); } } public class Test { public static void main(String[] args) { Cat cat = new Cat("Mruczek", 21); Dog dog = new Dog("Rex", 34); Dog dog1 = new Dog("Azor", 21); System.out.println(dog == dog1); // false, różne miejsca w pamięci // System.out.println(dog.equals(dog1)); // false, różne miejsca w pamięci dog1 = dog; System.out.println(dog == dog1); // true, są równe referencyjnie System.out.println(dog == cat); // false, różne typy cat = dog; // tym razem kod się skompiluje System.out.println(dog == cat); // true, wspólny przodek (Animal) // Obiekty mają odmienne imię oraz wiek, ale mają wspólnego przodka (Animal) } }
Mamy tutaj następującą sytuację:
⚠️ Oba obiekty to pochodne klasy Object
(każdy obiekt utworzony w Javie jest pochodną klasy Object
Porównanie referencyjne
między obiektami klasy Dog
i Cat
zwróci false
, ponieważ to różne typy. Jednak po przypisaniu referencji cat = dog
, porównanie referencyjne między dog
a cat
zwróci true
, ponieważ obie te zmienne wskazują na ten sam obiekt klasy Dog
, dziedziczącej po klasie Animal
.
Podsumowanie
Równość referencyjna
w Javie, przy użyciu operatora ==
, skupia się na porównywaniu referencji dwóch obiektów. Istotą tego podejścia jest sprawdzenie, czy dwie referencje wskazują dokładnie na ten sam obszar pamięci.
W kolejnym wpisie skupimy się drugim sposobie porównywania obiektów w Javie, a mianowicie równości logicznej
, która koncentruje się na porównywaniu zawartości obiektów, niezależnie od tego, czy są to te same obiekty w pamięci. To podejście pozwala na bardziej złożone i elastyczne porównania.
Śledź mnie na LinkedIn:
Newsletter
Jeśli masz jakieś sugestie lub pytania, proszę napisz do mnie wiadomość: kuba@javampokaze.pl