javamPokaze || Równość referencyjna w Javie
🛠️

javamPokaze || Równość referencyjna w Javie

notion image

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ć.

 
notion image
 

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

 
notion image
 

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.

notion image

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ą.

 
notion image
 
 

💥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 str2ró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.

 
notion image
 

💥 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.

 

💥 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…)

 
notion image
 

💥 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.

 
notion image
 

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