1. Змінні-посилання

У мові Java є змінні двох видів: змінні примітивного типу і всі інші. От про «всі інші» ми зараз і поговоримо.

Насправді точнішим буде таке визначення: є змінні примітивного типу (primitive type variables), і є вказівні змінні (reference variables). То ж це таке — вказівні змінні?

На відміну від примітивних типів, які дозволяють зберігати значення безпосередньо всередині змінних, змінні вказівних типів зберігають посилання на об'єкти. Тобто десь у пам'яті є певний об'єкт, а у вказівній змінній (синонім: змінна-посилання) просто зберігається адреса цього об'єкта в пам'яті (посилання).

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

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

Змінні-посилання у Java

Змінні int a, int b і double d є примітивними і зберігають у собі значення.

Змінна String str — вказівна і зберігає адресу (посилання) об'єкта типу String у пам'яті.

Під час присвоювання значення змінній примітивного типу це значення копіюється (дублюється). А коли відбувається присвоювання значення вказівній змінній, копіюється тільки адреса об'єкта, а власне об'єкт не копіюється.


2. Сутність посилань

У чому принципова відмінність вказівних змінних від примітивних?

Примітивна змінна схожа на коробку, де можна зберігати якесь значення. Вказівна змінна більше схожа на аркуш паперу із записаним на ньому номером телефону.

Автомобіль і ключі від автомобіля

Уявіть, що ви вирішили подарувати своєму другові на день народження автомобіль. Ви ж не запакуєте його в коробку і не понесете із собою: автомобіль для цього надто великий.

Значно зручніше взяти з собою тільки ключі від автомобіля і покласти їх в коробку відповідного розміру. А ваш друг і так усе зрозуміє, коли дістане ключі з коробки. Немає потреби приносити автомобіль, коли можна просто передати ключі.

Людина і номер її телефону

Або ще один варіант: людина і номер її телефону. Номер телефону — це не сама людина, але за цим номером можна їй зателефонувати, щось запитати, дати вказівки.

Так само й посилання використовуються для взаємодії з об'єктом. Усі об'єкти взаємодіють один з одним за допомогою посилань. Замість того, щоб «обмінюватися людьми», досить обмінятися номерами телефонів.

Коли присвоюється значення змінній примітивного типу, це значення копіюється (дублюється). А коли присвоюється значення вказівній змінній, копіюється тільки адреса об'єкта («номер телефону»), а власне об'єкт не копіюється.

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


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

Для вказівних змінних операція присвоювання полягає в тому, що змінній просто присвоюється адреса об'єкта в пам'яті. А самі об'єкти в результаті присвоєння не з'являються й не зникають.

Такий підхід дає змогу уникнути копіювання великих обсягів пам'яті. Якщо потрібно кудись передати дуже великий об'єкт, ми просто передамо в цей метод посилання на об'єкт, і все. Посилання займає значно менше місця.

Розмір усіх вказівних змінних (незалежно від типу) однаковий і становить 4 байти (як тип int). Але! Якщо ваша програма працює в 64-розрядній Java-машині, розмір усіх посилань буде 8 байтів (64 біти).

Посилання можна присвоїти лише посиланню. Не можна змінювати посилання чи присвоювати їм довільні значення.

Код Опис
String hello = "Привіт";
String s = hello;
Так можна
String hello = "Привіт";
hello++;
А так не можна
String hello = 0x1234;
І так не можна

4. Порожнє посилання — null

А що зберігає вказівна змінна, коли їй ще нічого не присвоєно?

Вона зберігає порожнє посилання — null. null — це спеціальне ключове слово в Java, яке позначає відсутність посилання (порожнє посилання). Значення null можна присвоїти будь-якій вказівній змінній.

Усі вказівні змінні, доки їм не присвоєно певне посилання, мають значення null.

Приклади:

Код Опис
class Person
{
   public static String name;
   public static int age;
}


Змінна String name має значення за замовчуванням: null.
Змінна int age має значення за замовчуванням: 0.

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

Якщо змінна зберігає посилання на певний об'єкт, а ви хочете видалити значення цієї змінної, просто присвойте їй посилання null.

Код Опис
String s = null;
s = "Привіт";
s = null;
s зберігає посилання null.
s зберігає посилання на об'єкт-рядок
s зберігає посилання null

5. Передавання посилань у методи

Якщо параметрами певного методу є вказівні змінні, то значення передаються в такі змінні так само, як і у звичайні. Змінній-параметру просто присвоюється значення іншої змінної.

Приклад:

Код Опис
class Solution
{
   public static void fill(String[] array, String value)
   {
      for (int i = 0; i < array.length; i++)
        array[i] = value;
   }

   public static void main(String[] args)
   {
     String[] data = new String[10];
     fill(data, "Hello");
   }
}


Метод fill заповнює переданий масив array переданим значенням value.

Коли викликається метод fill, змінній array присвоюється посилання на масив data. Змінній value присвоюється посилання на об'єкт-рядок «Hello».

Отака ситуація буде в пам'яті перед викликом методу fill:

Отака ситуація буде в пам'яті під час виконання методу fill:

Змінні data і array посилаються (зберігають посилання) на один і той самий масив-контейнер у пам'яті.

Змінна value зберігає посилання на об'єкт-рядок Hello.

У комірках масиву також зберігаються просто посилання на об'єкт Hello.

Фактично об'єкти не дублюються — відбувається тільки копіювання посилань.



6. Порівняння з мовою С/С++

Іноді Java-програмістів на співбесідах запитують: як у Java передаються дані в методи? Часом ще й уточнюють: за посиланням чи за значенням?

Це питання походить з мови С++ — у мові Java воно не має сенсу. У мові Java змінним-параметрам завжди просто присвоюються значення змінних-аргументів. Отже, правильна відповідь — за значенням.

Але будьте готові до того, щоб пояснити свою думку, тому що вам можуть заперечити, що «примітивні типи передаються за значенням, а вказівні — за посиланням».

Ця проблема пов'язана з тим, що багато Java-програмістів були в минулому програмістами С++. А там питання «як передаються параметри в методи» відігравало дуже важливу роль.

У Java все однозначно: примітивні типи зберігають значення, вказівні теж зберігають значення — посилання. Річ у тім, що саме вважати значенням змінної.

У мові C++ змінні можуть зберігати як посилання на об'єкт, так і сам об'єкт. Це також стосується примітивних типів: у змінній можна зберігати значення або оголосити змінну посиланням на тип int. Тому, щоб не заплутатися, програмісти С++ завжди називають посилання на об'єкт посиланням, а власне об'єкт — значенням.

У С++ легко може скластися ситуація, коли одна змінна містить об'єкт, а інша — посилання на цей самий об'єкт. Тому питання, що зберігає в собі змінна — сам об'єкт чи тільки посилання на нього, — дуже важливе. Під час передавання об'єкта в метод цей об'єкт копіюється (якщо передається за значенням) або не копіюється (якщо передається за посиланням).

У Java цієї подвійності немає, і правильна відповідь буде така: у Java параметри передаються в методи за значенням. Просто у випадку із вказівними змінними це значення — посилання.