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

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

Відкрита

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, 12:37
Цікаво, все зрозуміло! Залишилось тільки запам'ятати🙂
Valentyna
Рівень 30
23 грудня 2023, 18:23
Класно описано! Дякую!
Illia_Losiei
Рівень 16
Expert
28 вересня 2023, 07:34
Чому після компіляції прикладу
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, 11:34
Был импорт сканера? import java.util.Scanner;
Illia_Losiei
Рівень 16
Expert
7 жовтня 2023, 06:30
Можливо, в цьому і була проблема, дякую
tterribaess
Рівень 6
31 січня 2024, 12:34
import java.util.Scanner;
30 серпня 2023, 11:06
Це 5*, просто і доступно👍
Vlad
Рівень 17
7 серпня 2023, 10:04
Змінні message і text зберігають адресу (посилання) одного й того самого об'єкта. А от змінні s1 і s2 зберігають посилання на дуже схожі між собою об'єкти, але все ж таки не на один і той самий об'єкт. Тому що це різні змінні ?
Юрій
Рівень 16
27 вересня 2023, 08:17
Метод toUpperCase() щоразу створює новий рядок. Даний метод був викликаний двічі для різних змінних, отже вони містять посилання на різні об'єкти.
Ivanka K
Рівень 3
24 вересня 2022, 20:24
дуже цікаво описано
Pan Vitali Moroz
Рівень 51
6 вересня 2022, 12:00
Для початку інфи достатньо, дякую
hidden #2768909
Рівень 1
5 серпня 2022, 17:24
🤔
Dushan
Рівень 13
7 березня 2022, 18:59
Чому s1 і s2 зберігають адреси РІЗНИХ об'єктів? Хіба Java-компілятор не об'єднує однакові об'єкти для зберігання пам'яті?
Shutstoboy
Рівень 11
31 травня 2022, 08:24
Тому що s1 і s2 - це дві різні змінні, які зберігають два різних об'єкта. Тільки здається, що вони однакові, бо в них однакові методи toUpperCase.
Roma Chernesh
Рівень 16
21 листопада 2022, 18:37
мож колись це стане зрозуміло. Але поки треба просто прийняти як правило "треба рахувати не з 1, а з 0". Чому, чи як - вже піздніше дійнаймося))
De Stroy
Рівень 10
4 квітня 2024, 22:32
Те, що Ви маєте на увазі, називається 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, 17:29
гарно описано, дякую!
illjadmytruk23
Рівень 6
23 грудня 2021, 07:46
Так, цілком доступно.