1. Методи copyOf: List.copyOf, Set.copyOf, Map.copyOf
Уявіть ситуацію: є метод, який має повернути список, множину або мапу, але важливо, щоб клієнтський код не міг жодним чином змінити вміст цієї колекції. Наприклад, «список підтримуваних валют» або «набір ролей користувача». Будь-яке довільне додавання чи видалення може порушити бізнес-логіку.
До Java 10 доводилося або повертати копію колекції вручну, або використовувати обгортки на кшталт Collections.unmodifiableList(list). Обгортка захищає лише від змін через неї, але не від модифікацій вихідної колекції.
Тепер маємо надійніший спосіб — методи copyOf.
Що це таке?
Методи List.copyOf(Collection), Set.copyOf(Collection), Map.copyOf(Map) (Java 10) створюють справжню незмінну копію переданої колекції або мапи.
- Копія не повʼязана з вихідною колекцією: зміни оригіналу не впливають на результат copyOf.
- Змінити копію неможливо: будь-яка модифікація призведе до UnsupportedOperationException.
Приклад створення незмінної копії
import java.util.*;
public class CopyOfDemo {
public static void main(String[] args) {
List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("Python");
// Створюємо незмінну копію
List<String> immutable = List.copyOf(modifiable);
// Спробуємо змінити копію
try {
immutable.add("C++"); // Викине UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("Не можна додати елемент до незмінної колекції!");
}
// Змінюємо вихідний список
modifiable.add("Kotlin");
// Копія залишилася незмінною!
System.out.println("Вихідний список: " + modifiable);
System.out.println("Незмінна копія: " + immutable);
}
}
Виведення програми:
Не можна додати елемент до незмінної колекції!
Вихідний список: [Java, Python, Kotlin]
Незмінна копія: [Java, Python]
Коротко про кожен метод
- List.copyOf(Collection) — повертає незмінний список з елементами з вихідної колекції.
- Set.copyOf(Collection) — повертає незмінну множину з елементами з колекції; дублікати буде усунено.
- Map.copyOf(Map) — повертає незмінну мапу з парами з вихідної мапи.
Приклад із Set і Map
import java.util.*;
public class CopyOfSetMapDemo {
public static void main(String[] args) {
Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
Set<String> immutableSet = Set.copyOf(modifiableSet);
// Спробуємо додати елемент
try {
immutableSet.add("D");
} catch (UnsupportedOperationException e) {
System.out.println("Не можна змінити Set!");
}
Map<String, Integer> modifiableMap = new HashMap<>();
modifiableMap.put("Alice", 30);
modifiableMap.put("Bob", 25);
Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);
try {
immutableMap.put("Charlie", 28);
} catch (UnsupportedOperationException e) {
System.out.println("Не можна змінити Map!");
}
}
}
2. Особливості та обмеження методів copyOf
Елементи null не допускаються
Як і фабричні методи List.of, Set.of, Map.of, методи copyOf забороняють null як елемент, ключ або значення. Якщо у вихідній колекції/мапі є null, під час копіювання буде викинуто NullPointerException.
List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // Викине NullPointerException!
Колекція справді незмінна
Будь-яка спроба add/remove/put/replace призводить до UnsupportedOperationException.
Оригінал і копія не повʼязані
Зміни вихідної колекції не впливають на копію, і навпаки.
Якщо колекція вже незмінна — повертається вона ж
Передали вже незмінну колекцію (наприклад, результат List.of(...)) — copyOf поверне той самий обʼєкт (економія памʼяті).
List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);
System.out.println(immutable == copy); // true
Конкретна реалізація не гарантується
Тип поверненої колекції — просто List, Set або Map. Це не обовʼязково ArrayList, HashSet або HashMap. Не покладайтеся на деталі реалізації (наприклад, внутрішній тип або оптимізації); контракт — незмінність і відповідний інтерфейс.
3. Відмінність copyOf від обгорток (unmodifiable wrappers)
Collections.unmodifiableList(list) створює обгортку над наявною колекцією. Якщо вихідна колекція змінюється, ці зміни буде видно і в обгортці.
List.copyOf(list) створює нову незмінну колекцію, не повʼязану з оригіналом.
Демонстрація різниці
import java.util.*;
public class WrapperVsCopyOf {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("A", "B"));
// Обгортка
List<String> wrapper = Collections.unmodifiableList(original);
// Копія
List<String> copy = List.copyOf(original);
// Змінимо оригінал
original.add("C");
System.out.println("Обгортка: " + wrapper); // [A, B, C]
System.out.println("Копія: " + copy); // [A, B]
}
}
Виведення:
Обгортка: [A, B, C]
Копія: [A, B]
Обгортка відображає зміни оригіналу, а копія залишається незмінною.
4. Практичні сценарії використання copyOf
Захист даних під час повернення з методу
public class CurrencyService {
private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));
public List<String> getSupportedCurrencies() {
// Ніхто не зможе змінити результат
return List.copyOf(currencies);
}
}
Тепер клієнтський код не зможе додати або видалити валюту з поверненого списку.
Передача між шарами застосунку
Коли дані передаються між шарами (DAO → сервіс → контролер), використовуйте copyOf, щоб бути певними, що ніхто не змінить колекцію «в дорозі».
Безпечна публікація в багатопотокових програмах
Незмінні колекції — зручний спосіб безпечно поділитися даними між потоками без додаткової синхронізації.
5. Типові помилки під час використання copyOf і незмінних колекцій
Помилка № 1: спроба вставити null. Методи copyOf (як і of) не терплять null — ні як елемент списку/множини, ні як ключ/значення мапи. Під час копіювання отримаєте NullPointerException.
Помилка № 2: плутанина між обгорткою та копією. copyOf створює незалежну копію: зміни оригіналу не видно в копії. А от Collections.unmodifiableList(list) лише обгортає вихідну колекцію: усі зміни оригіналу будуть відображатися в обгортці.
Помилка № 3: спроба змінити незмінну колекцію. Виклики add/remove/put на колекціях із copyOf/of призводять до UnsupportedOperationException.
Помилка № 4: забули про обмеження фабрик мап. Наприклад, Map.of() підтримує до 10 пар ключ–значення. Якщо потрібно більше — використовуйте Map.ofEntries(...) або зберіть змінну мапу, а потім застосуйте Map.copyOf.
Помилка № 5: покладатися на конкретну реалізацію. Не очікуйте, що copyOf поверне саме ArrayList або HashSet. Гарантуються лише інтерфейс (List/Set/Map) і незмінність.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ