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 // адреси різні