Java for Beginner
677 subscribers
546 photos
155 videos
12 files
836 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Неизменяемые коллекции, их внутреннее устройство и особенности

Неизменяемые коллекции в Java представляют собой структуры данных, которые нельзя модифицировать после их создания. Это означает, что после того как вы создали такую коллекцию, вы не сможете добавить, удалить или изменить элементы внутри нее. Неизменяемые коллекции становятся все более популярными благодаря их безопасности, простоте использования и эффективности в определенных контекстах, таких как многопоточность.

Неизменяемые коллекции предлагают ряд преимуществ, которые делают их полезными в различных сценариях:


Безопасность и предсказуемость:
Поскольку неизменяемые коллекции нельзя изменить после создания, это делает их идеальными для использования в многопоточных средах. Отсутствие изменений гарантирует, что коллекция останется в консистентном состоянии во время выполнения программы.

Простота и минимизация ошибок:
Использование неизменяемых коллекций снижает вероятность ошибок, связанных с непреднамеренными изменениями данных. Это делает код более предсказуемым и легким для понимания.

Производительность:
Неизменяемые коллекции могут быть более эффективными с точки зрения производительности, так как отпадает необходимость в синхронизации или копировании данных при передаче коллекции между потоками.

Кэширование и мемоизация:
Из-за их неизменности, неизменяемые объекты могут быть безопасно закэшированы и повторно использованы, что снижает нагрузку на систему и уменьшает потребность в выделении новой памяти.

Виды неизменяемых коллекций

В Java неизменяемые коллекции можно создать несколькими способами. Наиболее известные из них включают использование утилитарных методов, представленных в JDK 9 и более поздних версиях, а также использование классов-оболочек.
Начиная с Java 9, были добавлены статические фабричные методы для создания неизменяемых коллекций, такие как List.of(), Set.of() и Map.of().
List<String> immutableList = List.of("apple", "banana", "cherry");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<Integer, String> immutableMap = Map.of(1, "one", 2, "two", 3, "three");
Эти коллекции обеспечивают высокую производительность и компактность, так как они реализованы с использованием специализированных внутренних структур данных.


Collections.unmodifiableXXX():
До Java 9 неизменяемые коллекции можно было создавать с помощью методов из класса Collections. Эти методы возвращают "обернутые" коллекции, которые блокируют модификации.
List<String> mutableList = new ArrayList<>(Arrays.asList("apple", "banana", "cherry"));
List<String> immutableList = Collections.unmodifiableList(mutableList);
Однако важно помнить, что хотя сам объект коллекции становится неизменяемым, если исходная коллекция, переданная в метод, изменится, это отразится и на неизменяемой коллекции.


Guava Immutable Collections:
Библиотека Google Guava предоставляет собственные реализации неизменяемых коллекций, такие как ImmutableList, ImmutableSet, и ImmutableMap.
List<String> guavaImmutableList = ImmutableList.of("apple", "banana", "cherry");
Set<Integer> guavaImmutableSet = ImmutableSet.of(1, 2, 3);
Коллекции Guava полностью независимы от оригинальной коллекции и не изменяются, даже если исходная коллекция изменяется.


#Java #Training #Medium #ImmutableCollections
Внутреннее устройство неизменяемых коллекций

Неизменяемые коллекции реализуются по-разному в зависимости от их конкретной реализации, но основные принципы остаются схожими:

Отсутствие изменяемых ссылок:
Неизменяемые коллекции не предоставляют методов для изменения своего содержимого (таких как add(), remove(), и т. д.). Даже если такие методы существуют (например, в случае с Collections.unmodifiableList()), они выбрасывают исключения UnsupportedOperationException.

Минимизация памяти:
В реализации неизменяемых коллекций часто применяются специализированные структуры данных, которые минимизируют потребление памяти. Например, для хранения маленьких списков List.of() использует компактные структуры.

Предсказуемое поведение:
Поскольку неизменяемые коллекции не могут быть изменены, они обеспечивают предсказуемое поведение и стабильность. Это делает их идеальными кандидатами для кэширования и использования в хэш-структурах.

Копирование данных:
Некоторые неизменяемые коллекции создают свои копии данных, чтобы гарантировать, что оригинальные данные не изменятся (например, коллекции Guava). Это может увеличить время создания коллекции, но делает ее безопасной для использования.

Особенности неизменяемых коллекций

Поддержка null:
Важно помнить, что коллекции, созданные с помощью List.of(), Set.of(), или Map.of() в Java 9 и выше, не поддерживают null в качестве элемента. Попытка добавить null приведет к выбросу NullPointerException.
List<String> listWithNull = List.of("apple", null, "cherry"); // Throws NullPointerException


Порядок элементов:
В случае Set.of() элементы не сохраняют порядок, но List.of() и Map.of() сохраняют порядок добавления элементов.

Производительность:
Неизменяемые коллекции могут быть быстрее, чем изменяемые, благодаря отсутствию необходимости обрабатывать изменения данных. Однако стоит учитывать время, необходимое для их создания, особенно если коллекция очень большая.

Совместимость:
Неизменяемые коллекции полностью совместимы с остальными частями Java Collection Framework. Они могут быть использованы в любом месте, где требуется коллекция, но вы должны помнить, что они не поддерживают операции изменения.

#Java #Training #Medium #ImmutableCollections
Примеры использования неизменяемых коллекций в реальных задачах

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

Задача: Представим ситуацию, в которой несколько потоков читают список конфигураций приложения. Эти конфигурации не должны изменяться во время выполнения программы.

import java.util.List;

public class ConfigurationManager {
private final List<String> configurations;

public ConfigurationManager(List<String> configurations) {
this.configurations = List.copyOf(configurations);
}

public List<String> getConfigurations() {
return configurations;
}
}

// Пример использования
List<String> initialConfigs = Arrays.asList("config1", "config2", "config3");
ConfigurationManager manager = new ConfigurationManager(initialConfigs);

List<String> configs = manager.getConfigurations();
configs.forEach(System.out::println);
В этом примере, использование List.copyOf() в конструкторе ConfigurationManager гарантирует, что коллекция configurations будет неизменяемой, и любые попытки изменить ее извне будут заблокированы.


Пример 2: Защита от несанкционированных изменений
Иногда необходимо защитить коллекцию от изменений, чтобы избежать непреднамеренных или злонамеренных изменений данных.

Задача: Допустим, у нас есть список сотрудников компании, и мы хотим предоставить доступ к этому списку в разных частях программы, но при этом гарантировать, что список не будет изменен.
import java.util.Collections;
import java.util.List;

public class Company {
private final List<String> employees;

public Company(List<String> employees) {
this.employees = Collections.unmodifiableList(employees);
}

public List<String> getEmployees() {
return employees;
}
}

// Пример использования
List<String> employeeList = Arrays.asList("Alice", "Bob", "Charlie");
Company company = new Company(employeeList);

List<String> employees = company.getEmployees();
employees.forEach(System.out::println);

// Попытка изменения списка приведет к исключению UnsupportedOperationException
employees.add("David"); // This line will throw an exception
Здесь мы используем Collections.unmodifiableList(), чтобы создать неизменяемый список сотрудников. Это предотвращает любые попытки изменить список и гарантирует его неизменность в течение всего жизненного цикла программы.


#Java #Training #Medium #ImmutableCollections
Пример 3: Использование неизменяемых коллекций в качестве ключей в Map
Еще одна распространенная задача — использование сложных объектов, таких как списки или множества, в качестве ключей в Map. Для этого неизменяемые коллекции подходят идеально, так как они гарантируют, что значения ключей не изменятся после добавления их в Map.

Задача: Допустим, у нас есть Map, где ключами являются списки, представляющие комбинации параметров, а значениями — результаты расчетов. Мы хотим, чтобы ключи были неизменяемыми, чтобы избежать проблем с изменением комбинаций после добавления в Map.
import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class CalculationManager {
private final Map<List<Integer>, Integer> calculationResults = new HashMap<>();

public void addCalculationResult(List<Integer> params, int result) {
List<Integer> immutableParams = List.copyOf(params);
calculationResults.put(immutableParams, result);
}

public Integer getResult(List<Integer> params) {
return calculationResults.get(params);
}
}

// Пример использования
CalculationManager manager = new CalculationManager();

List<Integer> params = Arrays.asList(1, 2, 3);
manager.addCalculationResult(params, 10);

params.set(0, 5); // Изменение оригинального списка не повлияет на ключ в Map
System.out.println(manager.getResult(Arrays.asList(1, 2, 3))); // Output: 10
В этом примере мы используем List.copyOf() для создания неизменяемой версии списка параметров перед добавлением его в Map. Это гарантирует, что изменения оригинального списка params не повлияют на ключ в Map, сохраняя целостность данных.


Пример 4: Кэширование результатов
Неизменяемые коллекции идеально подходят для кэширования, так как их неизменность гарантирует, что закэшированные данные останутся консистентными на протяжении всего времени использования кэша.

Задача: Представим, что у нас есть метод, который выполняет сложные вычисления, и мы хотим кэшировать результаты этих вычислений для последующего использования.
import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class CacheManager {
private final Map<List<Integer>, Integer> cache = new HashMap<>();

public Integer computeIfAbsent(List<Integer> params, Calculation calculation) {
List<Integer> immutableParams = List.copyOf(params);
return cache.computeIfAbsent(immutableParams, calculation::calculate);
}

public interface Calculation {
Integer calculate(List<Integer> params);
}
}

// Пример использования
CacheManager cacheManager = new CacheManager();

List<Integer> params = Arrays.asList(2, 3, 4);
int result = cacheManager.computeIfAbsent(params, (p) -> p.stream().reduce(1, (a, b) -> a * b));

System.out.println(result); // Output: 24

// Повторный вызов с теми же параметрами вернет кэшированный результат
result = cacheManager.computeIfAbsent(params, (p) -> p.stream().reduce(1, (a, b) -> a * b));
System.out.println(result); // Output: 24
Здесь неизменяемая коллекция используется в качестве ключа для кэширования результата вычислений. Это позволяет избежать проблем с изменением параметров, которые могут привести к некорректному поведению кэша.


#Java #Training #Medium #ImmutableCollections