JavaRush /Курси /Java Syntax Zero /Порівняння посилань

Порівняння посилань

Java Syntax Zero
Рівень 3 , Лекція 8
Відкрита

1. Порівняння

Дуже часто програмісту потрібно порівнювати різні змінні між собою. І як ви вже встигли переконатися, не все настільки однозначно.

Цілі числа порівнювати дуже легко — просто використовуйте ==, та й усе. Для порівняння дійсних чисел порівнювати їх різницю (точніше, модуль різниці) з яким-небудь дуже маленьким числом.

Порівнювати рядки ще складніше. А все через те, що, по-перше, рядки — це об'єкти, а по-друге, залежно від ситуації програмісту часто хочеться, щоб порівняння рядків відбувалося трішки по-іншому (з урахуванням або без урахування певних факторів).


2. Розташування рядків у пам'яті

Як ви вже встигли побачити, рядки в пам'яті зберігаються не так, як цілі та дійсні числа:

Розташування рядків у пам'яті

Для зберігання рядків використовується два блоки пам'яті: один блок зберігає власне текст (його розмір залежить від розміру тексту), а другий (розміром 4 байти) зберігає адресу першого блоку.

Хоча досвідчений програміст в такій ситуації скаже щось на кшталт: «Змінна str типу String зберігає посилання на об'єкт типу String».


3. Присвоювання посилань на рядки

Перевага такого підходу очевидна, коли вам потрібно присвоїти одній рядковій змінній значення іншої рядкової змінної. Приклад:

String text = "Це дуже важливе повідомлення";
String message = text;

От що в підсумку буде в пам'яті:

У результаті цієї операції присвоювання об'єкт String залишиться на своєму місці, а в змінну message скопіюється тільки його адреса (посилання на об'єкт).


4. Робота з посиланнями та об'єктами

Якщо ви вирішите перетворити рядок у верхній регістр (великі літери), Java-машина зробить все правильно: у вас будуть два об'єкти типу String, і змінні text і message зберігатимуть посилання: кожна на свій об'єкт.

Приклад:

String text = "Це дуже важливе повідомлення";
String message = text.toUpperCase(); 

От що в підсумку буде в пам'яті:

Зверніть увагу на те, що метод toUpperCase() не змінює той рядок, звідки його було викликано. Натомість він створює новий рядок (новий об'єкт) і повертає посилання на нього.

Розгляньмо ще цікавіший приклад. Скажімо, ви вирішили передати рядок в об'єкт типу Scanner (щоб він читав значення з цього рядка).

Приклад:

String text = "10 20 40 80";
Scanner console = new Scanner(text);
int a = console.nextInt();
int b = console.nextInt();

Докладніше про роботу класу Scanner можна дізнатися за посиланням.

От як це все зберігатиметься в пам'яті:

Робота з посиланнями та об'єктами. Scanner

Водночас об'єкт типу String як був у пам'яті в одному екземплярі, так там і зберігається — всюди передаються та зберігаються лише посилання на нього.


5. Порівняння посилань на об'єкти типу String

Ну й нарешті ми дійшли до найцікавішого — порівняння рядків.

Для порівняння рядкових змінних можна використовувати два оператори: == (дорівнює) і != (не дорівнює). Оператори «більше», «менше», «більше або дорівнює» використовувати не можна — компілятор не дозволить.

Однак є цікавий нюанс: що у нас зберігається в рядкових змінних? Правильно: адреси (посилання) на об'єкти. Отже, саме ці адреси й будуть порівнюватися:

String text = "Привіт";
String message = text;
String s1 = text.toUpperCase();
String s2 = text.toUpperCase(); 

От що буде в пам'яті:

Змінні message і text зберігають адресу (посилання) одного й того самого об'єкта. А от змінні s1 і s2 зберігають посилання на дуже схожі між собою об'єкти, але все ж таки не на один і той самий об'єкт.

І якщо ви порівняєте в коді ці 4 змінні, то отримаєте такий результат:

Код Виведення на екран
String text = "Привіт";
String message = text;
String s1 = text.toUpperCase();
String s2 = text.toUpperCase();
System.out.println(text == message);
System.out.println(text == s1);
System.out.println(s1 == s2); 




true  // адреси однакові
false // адреси різні
false // адреси різні
Коментарі (18)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Іван Рівень 8
7 листопада 2024
Цікаво, все зрозуміло! Залишилось тільки запам'ятати🙂
Valentyna Рівень 30
23 грудня 2023
Класно описано! Дякую!
Illia_Losiei Рівень 16 Expert
28 вересня 2023
Чому після компіляції прикладу

String text = "10 20 40 80";
Scanner console = new Scanner(text);
int a = console.nextInt();
int b = console.nextInt();
у мене видає помилку Main.java:13: error: cannot find symbol Scanner console = new Scanner(text); ^ symbol: class Scanner location: class Main Main.java:13: error: cannot find symbol Scanner console = new Scanner(text); ^ symbol: class Scanner location: class Main 2 errors
Сергій Горох Рівень 89
6 жовтня 2023
Был импорт сканера? import java.util.Scanner;
Illia_Losiei Рівень 16 Expert
7 жовтня 2023
Можливо, в цьому і була проблема, дякую
tterribaess Рівень 6
31 січня 2024
import java.util.Scanner;
30 серпня 2023
Це 5*, просто і доступно👍
Vlad Рівень 17
7 серпня 2023
Змінні message і text зберігають адресу (посилання) одного й того самого об'єкта. А от змінні s1 і s2 зберігають посилання на дуже схожі між собою об'єкти, але все ж таки не на один і той самий об'єкт. Тому що це різні змінні ?
Юрій Рівень 16
27 вересня 2023
Метод toUpperCase() щоразу створює новий рядок. Даний метод був викликаний двічі для різних змінних, отже вони містять посилання на різні об'єкти.
Ivanka K Рівень 3
24 вересня 2022
дуже цікаво описано
Pan Vitali Moroz Рівень 51
6 вересня 2022
Для початку інфи достатньо, дякую
Oleh Polishchuk Рівень 6
5 серпня 2022
🤔
Dushan Рівень 13
7 березня 2022
Чому s1 і s2 зберігають адреси РІЗНИХ об'єктів? Хіба Java-компілятор не об'єднує однакові об'єкти для зберігання пам'яті?
Shutstoboy Рівень 11
31 травня 2022
Тому що s1 і s2 - це дві різні змінні, які зберігають два різних об'єкта. Тільки здається, що вони однакові, бо в них однакові методи toUpperCase.
Roma Chernesh Рівень 16
21 листопада 2022
мож колись це стане зрозуміло. Але поки треба просто прийняти як правило "треба рахувати не з 1, а з 0". Чому, чи як - вже піздніше дійнаймося))
De Stroy Рівень 10
4 квітня 2024
Те, що Ви маєте на увазі, називається String pool або String interning. Коли створюється рядковий літерал, то однакові літерали переходять у string pool для того, щоб зберегти пам'ять.

String a = "Hello";
String b = "Hello";
// a and b point to the same object in the string pool.
Однак, якщо Ви використовуєте конструктор або методи маніпуляції рядками (як у нашому прикладі, .toUpperCase() або substring() або інші), то об'єкти автоматично не поміщаються до string pool, це будуть різні об'єкти, які розміщуються на купі (heap), хоча їх вміст однаковий.

String s1 = "Привіт".toUpperCase();
String s2 = "Привіт".toUpperCase();
// s1 and s2 are different objects in the heap, s1 != s2
Однак Ви можете примусово їх покласти до string pool, застосувавши intern():

String s1 = "Привіт".toUpperCase().intern();
String s2 = "Привіт".toUpperCase().intern();
// now s1 = s2
Aleksandr Rozhko Рівень 6
14 вересня 2021
гарно описано, дякую!
illjadmytruk23 Рівень 6
23 грудня 2021
Так, цілком доступно.