Теорія та практика Java Змінювати чи не змінювати

Незмінні об'єкти можуть значно полегшити ваше життя

java

Серія контенту:

Цей контент є частиною # із серії # статей: Теорія та практика Java

Цей контент є частиною серії: Теорія та практика Java

Слідкуйте за виходом нових статей цієї серії.

Незмінний об'єкт - це такий об'єкт, чий зовнішній видимий стан не може змінитися після його створення. Класи String , Integer і BigDecimal у бібліотеці класів Java є прикладами незмінних об'єктів - вони представляють окреме значення, яке може змінитися протягом життєвого циклу об'єкта.

Переваги незмінності

Незмінні класи при правильному використанні можуть спростити програмування. Вони можуть бути тільки в одному стані, тому якщо вони правильно сконструйовані, вони ніяк не можуть бути в неузгодженому стані. Ви можете вільно робити спільними та кешувати посилання на незмінні об'єкти без необхідності копіювати або клонувати їх; Ви можете кешувати їхні поля або результати їх методів, не турбуючись про те, що значення застаріють або стануть неузгодженими з іншими станами об'єкта. З незмінних класів зазвичай виходять найкращі map key. По суті, вони потоково-орієнтовані, тому вам не потрібно синхронізувати доступ до них через потоки.

Можливість кешування

Якщо об'єкт змінюється, вам потрібно бути обережними, зберігаючи посилання на нього. Розглянемо код у Лістингу 1, де у чергу виконання планувальником поставлено два завдання . Мета в тому, щоб перше завдання почало виконуватися зараз, а друге – через один день.

Лістинг 1. Потенційна проблема зі змінним об'єктом Date (дата)

Оскільки Date -Об'єкт, що змінюється, метод scheduleTask повинен про всяк випадок скопіювати параметр дати (можливо через clone() ) у внутрішню структуру даних. Інакше як task1, так і task2 можуть виконатися завтра, що не відповідає нашій меті. Найгірше, внутрішня структура даних, що використовується планувальником завдань, може бути пошкоджена. Дуже легко забути скопіювати параметр дати під час написання методу типу scheduleTask() . Якщо ви про це все ж таки забудете, то створите невловиму помилку, яка деякий час не виявлятиметься, а коли виявиться, знадобиться довгий час, щоб її відстежити. Незмінний клас Date зробив би помилку такого роду неможливою.

Внутрішня безпека потоків

Можливість робити загальними посилання на незмінні об'єкти в потоках без синхронізації може значно полегшити процес синхронізації написання паралельних програм і знижує кількість потенційних помилок у них.

Безпека під час роботи з "поганим" кодом

Методи, які приймають об'єкти як аргументи, не повинні змінювати стан цих об'єктів, крім тих випадків, коли їм це документально вказано або вони стають власниками цього об'єкта. Коли ми передаємо об'єкт звичайному методу, ми зазвичай не очікуємо його повернення у незміненому вигляді. Однак по відношенню до об'єктів, що змінюються, така впевненість просто нерозумна. Якщо ми передаємо java.awt.Point таким методом, як Component.setLocation() , ніщо не заважає setLocation модифікувати місцезнаходження переданого Point або зберегти посилання на цей point і змінити його пізніше в іншому методі. (Звичайно, Component цього не робить, тому, що це було б "грубо", але не всі класи такі "ввічливі".) Тепер стан нашого об'єкта Point змінився без нашого відома, і результати потенційнонебезпечні - ми все ще думаємо, що пункт в одному місці, а він фактично знаходиться в іншому. Однак, якби об'єкт Point був незмінним, такий "ворожий" код не зміг би модифікувати стан нашої програми таким, що збиває з пантелику і небезпечним способом.

Хороші ключі

З незмінних об'єктів виходять найкращі ключі HashMap або HashSet. Деякі об'єкти, що змінюються, змінюють значення hashcode() залежно від свого стану (як, наприклад, клас StringHolder в Лістингу 2). Якщо ви використовуєте такий об'єкт, що змінюється, як ключ HashSet , а потім об'єкт змінює свій стан, при реалізації HashSet може виникнути плутанина - об'єкт все ще буде присутній, якщо пронумерувати набір (set), але може виявитися, що його немає, якщо звернутися з запитом до набору за допомогою contains(). Немає необхідності говорити, що це могло б викликати непередбачувану поведінку. Код Лістингу 2, де це демонструється, надрукує "false", "1," та "moo."

Лістинг 2. Змінюваний клас StringHolder, непридатний для використання як ключ

Коли використовувати незмінні класи

Незмінні класи ідеальні для представлення значення абстрактних типів даних, таких як числа, типи або кольори, що перераховуються. Основні числові класи в бібліотеці класів Java, такі як Integer, Long і Float - незмінні, як і стандартні числові типи, такі як BigInteger і BigDecimal. Класам для представлення складних чисел або раціональних чисел довільної точності краще мати незмінність. Залежно від вашої програми навіть абстрактні типи, які містять багато дискретних значень, таких як вектори або матриці, могли б застосовуватися як класи, що не змінюються.

Flyweight шаблон

Незмінність - це те, що дозволяєшаблону Flyweight, який використовує спільний доступ для полегшення використання об'єктів, фактично представлятиме велику кількість дрібноструктурних об'єктів. Наприклад, вам може знадобитися представити кожен символ текстового документа або кожен піксел зображення за допомогою об'єкта, але "наївна" реалізація такої стратегії зажадала б надмірно великих витрат пам'яті і могло б викликати проблеми з керуванням нею. Шаблон Flyweight застосовує фабричний метод (factory method) для розподілу посилань на незмінні дрібноструктурні об'єкти, а також задіює спільне використання, щоб вести рахунок об'єктів, маючи лише один екземпляр об'єкта, що відповідає літері "a." Додаткову інформацію про шаблон Flyweight можна знайти в класичній книзі Конструктивні шаблони (Gamma та ін; див. Ресурси).

Чи слід представляти об'єкти, які є контейнерами для множинних елементарних значень, таких як точки, вектори, матриці або кольори RGB, за допомогою об'єктів, що змінюються або незмінюються? Відповідь – коли як. Як вони будуть використані? Чи використовуються вони головним чином для представлення багатовимірних значень (наприклад, кольору пікселя), або просто як контейнери для сукупності пов'язаних один з одним властивостей іншого об'єкта (наприклад, висоти і ширини вікна)? Як часто ці властивості змінюватимуться? Якщо вони змінюються, чи значення індивідуального компонента мають власний сенс у додатку?

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

Рекомендації щодо написання незмінних класів

Написати незмінні класи нескладно. Клас буде незмінним, якщо таке:

Остання група вимог здається складною, але це головним чином означає, що якщо ви збираєтеся зберігати посилання на масив або інші об'єкти, що змінюються, ви повинні забезпечити своєму класу монопольний доступ до цього змінного об'єкта (бо в іншому випадку хтось інший зможе змінити його стан ) і переконатися, що ви не модифікували його стан після конструювання. Це ускладнення необхідно, щоб дозволити незмінним об'єктам зберігати посилання на масиви, оскільки мова Java не має можливості забезпечити умови, коли елементи результуючих масивів не модифікуватимуться. Пам'ятайте, що якщо посилання на масив або інші поля, що змінюються, ініціалізуються аргументами, переданими конструктору, вам необхідно не всякий випадок скопіювати аргументи зухвалої сторони, інакше ви не можете бути впевнені, що у вас монопольний доступ до масиву. В іншому випадку, сторона, що викликає, зможе модифікувати стан масиву після виклику конструктора. Лістинг 3 показує правильний і неправильний способи написання конструктора для незмінного об'єкта, який зберігає масив сторони, що викликає.

Лістинг 3. Правильний та неправильний способи кодування незмінних об'єктів

Рідко змінюються дані

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

З незмінними об'єктами набагато легше працювати, ніж зі змінними. Вони можуть бути лише в одному стані і тому завжди узгоджені, в силу своєї природи мають підтримку безпеки потоків, і до них легко організувати спільний доступ. Є безліч помилок програмування, які легко зробити і важко виявити, і які повністю усуваються використанням незмінних об'єктів, наприклад, відсутність синхронізації доступу між потоками або клонування масиву або об'єкта перед збереженням посилання на нього. При написанні класу завжди варто запитати себе, чи можна ефективно застосовувати цей клас як незмінний. Можливо, ви здивуєтеся, як часто відповідь буває ствердною.

Ресурси для скачування

Схожі теми

Коментарі