— Привіт, Аміго!
— Привіт, Ріша!
— Ну як день минув?
— Чудово! Мені сьогодні Білаабо розповів про рекурсію, а Еллі — про слабкі та м'які посилання.
— А про примарні посилання розповідала?
— Ти про PhantomReference? Згадувала, але не докладно розповідала.
— Відмінно, тоді сподіваюся, ти не будеш проти, якщо я заповню цю прогалину.
— Звичайно, я із задоволенням тебе послухаю, Ріша!
— Чудово. Тоді я почну.
Примарні посилання - це найслабші посилання з усіх. Тільки якщо на об'єкт не залишається жодних посилань взагалі, крім примарних, їхній механізм набуває чинності.
PhantomReference використовується для складної процедури видалення об'єкта. Це може бути необхідно, коли об'єкт щось робить за межами Java-машини, наприклад викликає низькорівневі функції ОС або пише своє стан у файл або ще щось дуже важливе.
Приклад використання:
//спеціальна черга для примарних об'єктів
ReferenceQueue<Integer> queue = new ReferenceQueue<Integer>();
//список примарних посилань
ArrayList<PhantomReference<Integer> > list = новий ArrayList<PhantomReference< ;Integer>>();
//створюємо 10 об'єктів і додаємо їх у список через примарні посилання
for ( int i = 0; i < 10; i++)
{
Integer x = new Integer(i);
list.add(new PhantomReference<Integer>(x, queue));
}
Ще раз звертаю увагу на останній рядок. У PhantomReference передається не тільки об'єкт x, а й спеціальна черга примарних посилань.
— А навіщо потрібна ця черга?
— Ось зараз і розповім.
При знищенні об'єкта, який утримується примарним посиланням, він знищується, але не видаляється з пам'яті! Ось така загогулина, розумієш.
— А це як?
— Тут досить багато нюансів, тож почну з найпростішого.
Якщо на об'єкт залишаються лише примарні посилання, то ось що на нього чекає:
Крок 1. Під час найближчого складання сміття об'єкт буде викликаний метод finalize(). Але якщо метод finalize() не був перевизначений, цей крок пропускається, а виконається відразу крок 2.
Крок 2. Під час наступного складання сміття, об'єкт буде поміщений у спеціальну чергу примарних об'єктів, з якої буде видалено, коли PhantomReference викличуть метод clear().
— А хто його викличе? Адже об'єкт як би видалений, хіба ні?
— Тут справа в тому, що фактично об'єкт помер у нашому (Java) світі, але не зник, а залишився в ньому примарою – на нього зберігається посилання у черзі примарних об'єктів. Те саме ReferenceQueue, посилання на яке ми так дбайливо передаємо в конструктор PhantomReference.
— Тобто. ця ReferenceQueue — це як би потойбічний світ?
— Скоріше, як світ привидів.
І щоб видалити об'єкт-примару, треба викликати clear() у його примарного посилання.
Ось як можна продовжити попередній приклад:
//спеціальна черга для примарних об'єктів
ReferenceQueue<Integer> queue = new ReferenceQueue<Integer>();
//список примарних посилань
ArrayList<PhantomReference<Integer> ;> list = новий ArrayList<PhantomReference< ;Integer>>();
//створюємо 10 об'єктів і додаємо їх у список через примарні посилання
for ( int i = 0; i < 10; i++)
{
Integer x = new Integer(i);
list.add(new PhantomReference<Integer>(x, queue));
}
//викликаємо збирач сміття, сподіваємося, що він нас послухається :)
//він повинен убити всі «примарно досяжні» об'єкти і помістити їх у чергу
//Привидів
System.gc();
//Дістаємо з черги всі об'єкти
Reference<? extends Integer>referenceFromQueue;
while ((referenceFromQueue = queue.poll()) != null)
{
//виводимо об'єкт на екран
System.out.println(referenceFromQueue.get());
//очищаємо посилання
referenceFromQueue.clear();
}
— Щось тут відбувається – це зрозуміло. Навіть майже зрозуміло, що саме відбувається.
Але як це використовувати на практиці?
— Ось тобі більш адекватний приклад:
//спеціальна черга для примарних об'єктів
ReferenceQueue<Integer> queue = new ReferenceQueue<Integer>();
//список примарних посилань
ArrayList<PhantomInteger> list = new ArrayList<PhantomInteger>();
//створюємо 10 об'єктів і додаємо їх у список через примарні посилання
for ( int i = 0; i < 10; i++)
{
Integer x = new Integer(i);
list.add(new PhantomInteger (x, queue));
}
Thread referenceThread = new Thread()
{
public void run()
{
while (true)
{
try
{
//отримуємо новий об'єкт із черги, якщо об'єкта немає - чекаємо!
PhantomInteger ref=(PhantomInteger)queue.remove();
//викликаємо у нього метод close
ref.close();
ref.clear();
}
catch (Exception ex)
{
// пишемо в лог помилки
}
}
}
};
//запускаємо потік у службовому режимі.
referenceThread.setDaemon(true);
referenceThread.start();
static class PhantomInteger extends PhantomReference<Integer>
{
PhantomInteger(Integer referent, ReferenceQueue< ? super Integer> queue)
{
super(referent, queue);
}
private void close()
{
System.out.println("Bad Integer повністю відхилений!");
}
}
Ми тут зробили три речі.
По-перше, ми створили клас PhantomInteger, який успадкували від PhantomReference<Integer>.
По-друге, цей клас має спеціальний метод - close(), заради виклику якого як би все це й починається.
По-третє, ми оголосили спеціальну нитку — referenceThread. Вона у циклі чекає, поки в черзі примар не з'явиться ще один об'єкт. Як тільки він з'являється, вона видаляє його з черги привидів, а потім викликає в нього метод close (span). Потім метод clear(). І все – привид може переходити до наступного найкращого світу. У нашому він нас більше не потурбує.
— Як цікаво, проте все вийшло.
— Ми фактично відстежуємо чергу вмираючих об'єктів, і потім для кожного можемо викликати спеціальний метод.
Але, зваж, ти не можеш викликати метод самого об'єкта. Посилання на нього отримати не можна! Метод get() у PhantomReference завжди повертає null.
— Але ж ми успадковуємося від PhantomReference!
— Навіть усередині спадкоємця PhantomReference, метод get() повертає null.
— Тоді я просто збережу посилання на об'єкт у конструкторі
— Ага. Але тоді це посилання буде StrongReference, і об'єкт ніколи не потрапить у чергу примар!
— Млинець. Гаразд, здаюся. Не можна так не можна.
— От і відмінно. Сподіваюся, ти винесеш для себе щось цінне із сьогоднішнього уроку.
— Так стільки нового матеріалу. А я гадав, що вже все знаю. Дякую тобі за урок, Ріша.
— Будь ласка. Все, йди відпочивай. Але не забудь, у нас увечері ще урок.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ