Java for Beginner
780 subscribers
772 photos
220 videos
12 files
1.29K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Predicates (условия маршрутизации)

Predicates в Spring Cloud Gatewayэто функции, которые принимают ServerWebExchange и возвращают boolean, определяя, попадает ли входящий запрос под конкретный Route. Predicates — фундамент маршрутизации: маршрут считается подходящим только если все его предикаты возвращают true (логическое AND между перечисленными предикатами одного маршрута). Понимание механизмов работы предикатов критично для корректного и производительного построения маршрутов.


Ключевые принципы — механика и порядок


Маршрут выбран, если все его предикаты истинны.
В конфигурации YAML или Java DSL, когда у Route указано несколько предикатов, они объединяются логически через AND.

Порядок маршрутов важен.
Gateway перебирает доступные маршруты в порядке, определённом компонентом, который предоставляет Flux<Route> (обычно RouteDefinitionLocator → RouteDefinitionRouteLocator). Конкретный порядок определяется полем order (если задано) или порядком получения/создания маршрутов. Первый подходящий маршрут (в смысле совпадения предикатов) берётся в обработку. Следовательно, дешёвые «фильтрующие» предикаты (например, Host, Method) следует располагать раньше в конфигурации маршрутов по логике — не путать с порядком предикатов в одном маршруте: в одном маршруте порядок предикатов обычно не меняет семантику, но влияет на порядок выполнения (см. рекомендации по эффективности ниже).

Оценка предикатов — последовательная и краткая:
Для одного маршрута предикаты обычно вычисляются последовательно и, при первом false, дальнейшая проверка предикатов для этого маршрута останавливается (short-circuit). Тем не менее, поскольку маршрутов может быть много, Gateway продолжит проверять следующие маршруты до нахождения первого подходящего.

Рекомендации по производительности:
Помещайте дешёвые и часто отсекающие проверки (например, Host, Method, Path) до дорогих вычислений (например, проверки содержимого тела, обращения в внешние сервисы или сложных регулярных выражений).

Избегайте предикатов, которые делают блокирующие операции — predicated должны быть чисто вычислительными и быстрыми. При необходимости используйте Java DSL и инкапсулируйте асинхронную работу в фильтрах, а не в предикатах.

Если нужна сложная проверка, лучше вынести её в отдельный, оптимизированный RoutePredicateFactory (см. ниже).

Композиция логики (AND/OR):
AND — поведение по умолчанию: несколько предикатов в одном Route объединяются через AND.
OR — декларативно не представлен как оператор на уровне одной записи YAML; для выражения OR чаще используют либо несколько маршрутов с разными наборами предикатов, либо пишут кастомный предикат, который внутри реализует логическое OR. В Java DSL также проще объявить несколько маршрутов или написать композиционный предикат в коде.


Встроенные предикаты — семантика, YAML-примеры, Java DSL-примеры

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

1) Path
Проверяет соответствие URL-пути. Поддерживает шаблоны в стиле Ant (/users/**, /api/*/items) и также поддерживает регулярные группы при использовании Rewrite и фильтров.

YAML:
predicates:
- Path=/api/users/**


Java DSL:
.route("users", r -> r.path("/api/users/**")
.uri("lb://user-service"))


Замечание: Path — обычно самый дешёвый и самый часто используемый предикат; ставьте его в начале списка логических проверок маршрутов.

2) Host
Проверяет заголовок Host (например, api.example.com). Поддерживает подстановки (*.example.com).

YAML:
predicates:
- Host=api.example.com, *.internal.example.org


Java DSL:
.route("host-route", r -> r.host("api.example.com", "*.internal.example.org")
.uri("http://localhost:8080"))


Заметка: Host — особенно полезен при мульти-тенантной конфигурации.


#Java #middle #Spring_Cloud_Gateway
👍2
3) Method
Сравнивает HTTP-метод (GET/POST/PUT/...).

YAML:
predicates:
- Method=GET,POST


Java DSL:
.route("read-write", r -> r.method(HttpMethod.GET, HttpMethod.POST)
.uri("lb://some-service"))


4) Header

Проверяет наличие и (при задании) значение заголовка. Поддерживает регулярные выражения на значение.

YAML:
predicates:
- Header=X-Client, ^mobile-.*


Java DSL:
.route("header-route", r -> r.header("X-Client", "^mobile-.*")
.uri("http://mobile-backend"))


Если значение не указано — проверяется только наличие заголовка.

5) Query
Проверяет наличие параметра query и, при наличии второго аргумента, — значение (регулярное выражение).

YAML:
predicates:
- Query=version, ^v[0-9]+$


Java DSL:
.route("versioned", r -> r.query("version", "^v[0-9]+$")
.uri("lb://versioned-service"))


6) RemoteAddr
Проверяет IP-адрес клиента — поддерживает одиночные адреса и CIDR. Важно: в случае проксирования через балансировщики/Gateway заранее убедитесь, какие IP попадают в RemoteAddr (реальный клиент или IP reverse-proxy). Иногда требуется проверка X-Forwarded-For.

YAML:
predicates:
- RemoteAddr=192.168.1.0/24, 10.0.0.10


Java DSL:
.route("internal-only", r -> r.remoteAddr("192.168.0.0/16", "10.0.0.0/8")
.uri("lb://internal-service"))


7) Weight / Load Balancer predicates (логика распределения) — концепция и применение
Weight — предикат, встречающийся в экосистеме Spring Cloud Gateway в контексте динамических маршрутов и Discovery клиента. Его цель — поддержка взвешенной маршрутизации: когда один и тот же serviceId представлен несколькими маршрутами с разными весами (weight), Gateway на этапе выбора маршрута учитывает веса (для канареечных релизов, A/B, geo-aware routing и т.п.).

Практическая схема использования:
Discovery-driven маршрутизация (DiscoveryClientRouteDefinitionLocator) может генерировать маршруты для каждого экземпляра/группы с метаданными веса.
Weight-предикат в составе маршрута проверяет — подходит ли текущий запрос под данный «весовой» маршрут (обычно реализуется через случайное/round-robin решение, зависящее от weight).

Примечание по реализации и совместимости: синтаксис и встроенные реализации могут различаться между версиями Spring Cloud.

Если требуется взвешенная маршрутизация в production, часто применяют:
Spring Cloud LoadBalancer для распределения трафика по инстансам;
Weight-подход в комбинировании с DiscoveryClientRouteDefinitionLocator или кастомным RouteDefinitionRepository.
Если нужен пример конфигурации — лучше реализовать weight механизмы через Discovery + metadata на стороне сервисов или через кастомный RoutePredicateFactory (пример ниже покажет, как писать свой предикат).


Сложные комбинации — AND / OR / NOT

AND: стандартная комбинация — несколько предикатов в одном Route → все должны быть true. Шаблон конфигурирования одинаков как в YAML, так и в DSL.


OR: декларативного OR в одном Route нет.

Для выражения OR есть два варианта:
Несколько маршрутов: создайте два (или больше) маршрута, каждый с различным предикатом; порядок важен, если маршруты пересекаются.
Кастомный предикат (composite): напишите RoutePredicateFactory, который внутри реализует логику a || b, и используйте его как обычный предикат. Это предпочтительно, если хотите избежать дублирования конфигурации и централизовать логику.

NOT / Negation: также не представлен в YAML как отдельный оператор; реализуется кастомным предикатом или путём композиции (например, создание предиката NotHeader).


#Java #middle #Spring_Cloud_Gateway
👍2
Написание собственного Predicate — RoutePredicateFactory

Когда встроенных предикатов недостаточно по логике или производительности, пишут собственные RoutePredicateFactory. Ниже — полный пример: реализуем предикат, который проверяет наличие параметра X-Feature и сопоставляет его по списку допустимых значений, при этом конфигурируемый через YAML.


1) Структура — класс предиката

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.web.server.ServerWebExchange;
import java.util.function.Predicate;
import java.util.List;

public class XFeatureRoutePredicateFactory extends AbstractRoutePredicateFactory<XFeatureRoutePredicateFactory.Config> {

public XFeatureRoutePredicateFactory() {
super(Config.class);
}

@Override
public Predicate<ServerWebExchange> apply(Config config) {
List<String> allowed = config.getAllowedValues();
boolean ignoreCase = config.isIgnoreCase();

return exchange -> {
List<String> headerValues = exchange.getRequest().getHeaders().get("X-Feature");
if (headerValues == null || headerValues.isEmpty()) {
return false;
}
for (String hv : headerValues) {
for (String allowedVal : allowed) {
if (ignoreCase) {
if (hv.equalsIgnoreCase(allowedVal)) return true;
} else {
if (hv.equals(allowedVal)) return true;
}
}
}
return false;
};
}

public static class Config {
private List<String> allowedValues;
private boolean ignoreCase = true;

public List<String> getAllowedValues() { return allowedValues; }
public void setAllowedValues(List<String> allowedValues) { this.allowedValues = allowedValues; }
public boolean isIgnoreCase() { return ignoreCase; }
public void setIgnoreCase(boolean ignoreCase) { this.ignoreCase = ignoreCase; }
}
}


Ключевые моменты:
Наследуемся от AbstractRoutePredicateFactory<Config>. Это даёт поддержку автоконфигурируемой десериализации конфигурации из YAML в Config.
apply(Config) возвращает Predicate<ServerWebExchange>, который будет выполняться для каждого запроса.
Config — POJO с геттерами/сеттерами: Spring автоматически маппит значения из YAML (через Binder).

2) Регистрация (component или bean)
import org.springframework.stereotype.Component;

@Component
public class XFeatureRoutePredicateFactory extends AbstractRoutePredicateFactory<XFeatureRoutePredicateFactory.Config> {
// ... код как выше
}


Если не использовать @Component, можно зарегистрировать как bean через конфигурацию.

3) Использование в YAML
spring:
cloud:
gateway:
routes:
- id: feature-route
uri: http://feature-service
predicates:
- XFeature=allowed1,allowed2


В AbstractRoutePredicateFactory стандартная разбивка аргументов (если конфиг простой) позволит подставить список значений. Для более сложной настройки (ключ:значение) необходимо переопределить shortcutFieldOrder() или использовать вложенный Config с именованными полями.

4) Использование в Java DSL
Если хотите inline-предикат в коде (без фабрики), Java DSL позволяет:
.route("xfeature-inline", r -> r.p(exchange -> {
List<String> values = exchange.getRequest().getHeaders().get("X-Feature");
return values != null && values.stream().anyMatch(v -> v.equalsIgnoreCase("allowed1"));
}).uri("http://feature-service"))


Однако такой inline-предикат теряет преимущества конфигурируемости через YAML и переиспользуемости.


#Java #middle #Spring_Cloud_Gateway
👍2
Работа с конфигурационными объектами и AbstractRoutePredicateFactory — тонкости

Shortcut configuration vs. full Config binding

Если RoutePredicateFactory использует короткий синтаксис (например: MyPred=val1,val2), то AbstractRoutePredicateFactory поддерживает маппинг по shortcutFieldOrder() — список полей Config, которые будут заполнены по порядку.
Для явной структуры лучше использовать именованные свойства в YAML и обычный биндинг в Config.

Валидация конфигурации

Валидируйте конфиг в конструкторе предиката или в apply() — например, проверить, что список не пуст. Ошибки конфигурации лучше бросать на старте приложения, а не при первом запросе.
Сериализация/десериализация сложных типов
Для сложных типов (например, Duration, Pattern, InetAddress[]) используйте соответствующие конвертеры или храните строковые представления и парсите в Config.

Потокобезопасность

Predicate возвращаемый apply() должен быть потокобезопасным и не содержать mutable state, зависящего от запроса; храните precomputed структуры (например Pattern), а не парсьте каждый раз.

Логирование и мониторинг

Для сложных предикатов логируйте причины отказа (на низком уровне) либо метрики отказов, чтобы упростить отладку некорректного маршрутизации.


Примеры: несколько реальных сценариев и лучшие практики

Пример 1 — комбинация Host + Path + Method (YAML)
routes:
- id: users-route
uri: lb://user-service
predicates:
- Host=api.example.com
- Path=/users/**
- Method=GET

Пояснение: типичный маршрут, дешёвые проверки (Host/Method/Path) — быстрый short-circuit.


Пример 2 — OR-логика через два маршрута (YAML)
routes:
- id: mobile-route
uri: lb://mobile-backend
predicates:
- Header=X-Client, ^mobile-.*
- Path=/api/**
- id: fallback-route
uri: lb://web-backend
predicates:
- Path=/api/**

Пояснение: первый маршрут отведёт мобильный трафик на mobile-backend; второй поймает остальные запросы по тому же Path — это простой способ построить OR-поведение без кастомных предикатов.


Пример 3 — кастомный предикат с конфигом в YAML
predicates:
- XFeature=allowedA,allowedB

(см. реализацию XFeatureRoutePredicateFactory выше).


#Java #middle #Spring_Cloud_Gateway
👍2
Что выведет код?

public class Task111225 {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;

System.out.print((a == b) + " ");
System.out.print((c == d) + " ");

int e = 200;
Integer f = 200;

System.out.print((e == f) + " ");
System.out.print((c == e));
}
}


#Tasks
👍1
А вот и ответы по квизу!

👍 - если по нраву такой формат задач


#Quiz
👍2🔥1
Вопрос с собеседований

Что делает метод wait()? 🤓


Ответ:

wait()
переводит поток в состояние ожидания до вызова notify()/notifyAll().

Он освобождает монитор объекта, позволяя другим потокам войти в synchronized-блок. Используется при координации потоков.

Неправильное применение легко приводит к deadlock’ам и ошибкам синхронизации.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История IT-технологий сегодня — 12 декабря


ℹ️ Кто родился в этот день

Эд Катмулл (Эдвин Эрл Катмулл, англ. Edwin «Ed» Earl Catmull; род. 31 марта 1945) — один из основателей Pixar, пионер компьютерной графики; разработал Z-buffer и текстурное отображение, без которых невозможен современный 3D-рендеринг и игровая индустрия.

Хью Херр (родился 25 октября 1964 г.) — американский инженер-робототехник (MIT Media Lab); разрабатывает кибернетические протезы и интерфейсы «человек–компьютер» на стыке ИТ, ИИ и биомехатроники.


🌐 Знаковые события

1951 - страны-участницы подписали конвенцию о создании CERN — крупнейшего научно-технического центра Европы. Именно в CERN позже был создан World Wide Web (WWW), HTTP и HTML.


#Biography #Birth_Date #Events #12Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
🤓1
Глава 6. Итераторы

Интерфейс ListIterator — двунаправленный обход и расширенные возможности

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

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


Архитектура интерфейса ListIterator

ListIterator наследует от Iterator и добавляет значительное количество новых методов:
public interface ListIterator<E> extends Iterator<E> {
// Наследуемые методы
boolean hasNext();
E next();
void remove();

// Новые методы
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void set(E e);
void add(E e);
}


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


Фундаментальные методы двунаправленного обхода

hasPrevious() и previous(): Обратное движение

Концептуальное назначение

Методы hasPrevious() и previous() представляют собой зеркальное отражение hasNext() и next(), позволяющее двигаться по списку в обратном направлении. Эта пара методов реализует концепцию двунаправленного обхода, что является ключевым отличием ListIterator от базового Iterator.

Семантика и поведение

hasPrevious():
Возвращает true, если при движении в обратном направлении существуют элементы
Не изменяет состояние итератора
Может возвращать false в начальной позиции или после reset итератора


previous():
Возвращает предыдущий элемент и перемещает курсор назад
Выбрасывает NoSuchElementException, если предыдущих элементов нет
Устанавливает состояние для последующего вызова remove() или set()


Внутренние механизмы реализации

Позиционирование курсора

Ключевая концепция ListIterator — курсор, который находится между элементами списка.


Для списка из n элементов существует n+1 возможных позиций курсора:
Позиции курсора: 0   1   2   3   4
Элементы списка: [A] [B] [C] [D]


Семантика операций:
next() возвращает элемент после курсора и перемещает курсор вперед
previous() возвращает элемент перед курсором и перемещает курсор назад
remove() удаляет последний возвращенный элемент
set(E e) заменяет последний возвращенный элемент



Реализация для ArrayList
// Концептуальная реализация для ArrayList
public boolean hasPrevious() {
return cursor > 0;
}

public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0) {
throw new NoSuchElementException();
}
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
cursor = i;
return (E) elementData[lastReturned = i];
}


Особенности:
Использование индекса вместо ссылок на узлы
Проверка границ массива
Обновление lastReturned для поддержки remove() и set()


Реализация для LinkedList
// Концептуальная реализация для LinkedList
public boolean hasPrevious() {
return nextIndex > 0;
}

public E previous() {
checkForComodification();
if (!hasPrevious()) {
throw new NoSuchElementException();
}
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}


Особенности:
Навигация по ссылкам prev
Обработка граничных условий (первый/последний элемент)
Корректировка next и nextIndex


#Java #для_новичков #beginner #ListIterator
👍2
Особенности производительности

Временная сложность:

ArrayList: O(1) для обеих операций
LinkedList: O(1) для перехода между соседними узлами
Память: Не создает дополнительных объектов при нормальной работе.
Потокобезопасность: Как и другие методы итератора, не является потокобезопасным.



Паттерны использования

Полный двунаправленный обход
ListIterator<String> iterator = list.listIterator();

// Движение вперед
while (iterator.hasNext()) {
String element = iterator.next();
processForward(element);
}

// Движение назад
while (iterator.hasPrevious()) {
String element = iterator.previous();
processBackward(element);
}


Поиск с возвратом
public static <T> int findAndReturn(List<T> list, T target) {
ListIterator<T> iterator = list.listIterator();

// Поиск вперед
while (iterator.hasNext()) {
if (iterator.next().equals(target)) {
// Найдено - возвращаемся на две позиции назад
iterator.previous(); // Возвращаемся к найденному элементу
return iterator.previousIndex() + 1;
}
}

return -1; // Не найдено
}


Сравнение соседних элементов
public static void processAdjacentPairs(List<Integer> numbers) {
if (numbers.size() < 2) return;

ListIterator<Integer> iterator = numbers.listIterator(1); // Начинаем со второго элемента

while (iterator.hasNext()) {
Integer current = iterator.next();
Integer previous = iterator.previous(); // Переходим к предыдущему

processPair(previous, current);

iterator.next(); // Возвращаемся к текущему для продолжения
}
}



nextIndex() и previousIndex(): Информация о позиции

Концептуальное назначение

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

Семантика и поведение

nextIndex():
Возвращает индекс элемента, который будет возвращен следующим вызовом next()
Если итератор находится в конце списка, возвращает размер списка
Не изменяет состояние итератора


previousIndex():
Возвращает индекс элемента, который будет возвращен следующим вызовом previous()
Если итератор находится в начале списка, возвращает -1
Не изменяет состояние итератора


Математическая модель позиционирования

Для списка из n элементов с индексами от 0 до n-1:
Индексы элементов:   0     1     2     3
Позиции курсора: 0 1 2 3 4


Соотношения:
- nextIndex() возвращает индекс курсора
- previousIndex() возвращает (курсор - 1)
- hasNext() = (nextIndex() < n)
- hasPrevious() = (previousIndex() >= 0)


Внутренние механизмы реализации


Базовая реализация
// Концептуальная реализация
public int nextIndex() {
return cursor;
}

public int previousIndex() {
return cursor - 1;
}


Для ArrayList: Реализация тривиальна, так как позиция хранится как целочисленный индекс.
Для LinkedList: Требуется поддержка счетчика индекса или вычисление позиции через обход.


Поддержка индексации в LinkedList

В LinkedList итератору необходимо поддерживать информацию об индексе:
public int nextIndex() {
return nextIndex;
}

public int previousIndex() {
return nextIndex - 1;
}


Где nextIndex обновляется при каждом вызове next() или previous():
next(): увеличивает nextIndex на 1
previous(): уменьшает nextIndex на 1


Особенности производительности


Временная сложность: O(1) для обеих операций во всех реализациях.
Точность: Гарантированно точное значение индекса, соответствующее текущей позиции в списке.



#Java #для_новичков #beginner #ListIterator
👍2
Паттерны использования

Относительная навигация

public static <T> void processWithRelativeNavigation(List<T> list) {
ListIterator<T> iterator = list.listIterator();

while (iterator.hasNext()) {
int currentIndex = iterator.nextIndex();
T current = iterator.next();

// Обработка с учетом позиции
if (currentIndex % 2 == 0) {
// Четная позиция
processEvenPosition(current, currentIndex);
}

// Пропустить следующие 2 элемента, если возможно
if (iterator.hasNext()) {
iterator.next();
if (iterator.hasNext()) {
iterator.next();
}
}
}
}


Валидация границ

public static <T> void safeBidirectionalProcessing(List<T> list) {
ListIterator<T> iterator = list.listIterator();

// Движение вперед с проверкой
while (iterator.hasNext()) {
int nextIdx = iterator.nextIndex();
if (nextIdx < list.size() / 2) {
// Только первая половина списка
T element = iterator.next();
processElement(element);
} else {
break;
}
}

// Движение назад
while (iterator.hasPrevious()) {
int prevIdx = iterator.previousIndex();
if (prevIdx >= 0) {
T element = iterator.previous();
reverseProcess(element);
}
}
}


Создание подсписков по позициям

public static <T> List<T> extractSubList(List<T> list, int startIndex, int endIndex) {
List<T> result = new ArrayList<>();
ListIterator<T> iterator = list.listIterator(startIndex);

while (iterator.hasNext() && iterator.nextIndex() < endIndex) {
result.add(iterator.next());
}

return result;
}



Расширенные методы модификации

set(E e): Замена текущего элемента

Концептуальное назначение
Метод set(E e) позволяет заменить последний элемент, возвращенный вызовом next() или previous(). Это расширяет возможности итератора за пределы простого удаления, позволяя модифицировать содержимое списка во время итерации.

Семантика и поведение
Условия вызова: Может быть вызван только после успешного вызова next() или previous().
Исключения: Выбрасывает IllegalStateException, если не был вызван next() или previous(), или если после последнего вызова next()/previous() был вызван add() или remove().
Эффект: Заменяет элемент в списке без изменения размера коллекции или позиции итератора.

Внутренние механизмы реализации

Общий алгоритм
public void set(E e) {
if (lastReturned == null) {
throw new IllegalStateException();
}
checkForComodification();

try {
// Замена элемента в списке
list.set(lastReturnedIndex, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}


Реализация для ArrayList
public void set(E e) {
if (lastReturned < 0) {
throw new IllegalStateException();
}
checkForComodification();

try {
ArrayList.this.set(lastReturned, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}


Особенности:
Прямой доступ к массиву по индексу
Проверка границ массива
Сохранение позиции итератора


Реализация для LinkedList
public void set(E e) {
if (lastReturned == null) {
throw new IllegalStateException();
}
checkForComodification();
lastReturned.item = e;
}


Особенности:
Прямая модификация поля item узла
Не требует поиска узла
Высокая эффективность


Особенности производительности

Временная сложность:
ArrayList: O(1) — прямой доступ по индексу
LinkedList: O(1) — модификация существующего узла
Память: Может создавать новый объект, если заменяемый элемент становится недостижимым.
Потокобезопасность: Не является потокобезопасным.



#Java #для_новичков #beginner #ListIterator
👍2
Паттерны использования

Модификация элементов на лету

public static void transformStrings(List<String> strings) {
ListIterator<String> iterator = strings.listIterator();

while (iterator.hasNext()) {
String original = iterator.next();
String transformed = original.toUpperCase();
iterator.set(transformed);
}
}


Условная замена
public static void replaceIf(List<Integer> numbers, Predicate<Integer> condition, Integer replacement) {
ListIterator<Integer> iterator = numbers.listIterator();

while (iterator.hasNext()) {
Integer current = iterator.next();
if (condition.test(current)) {
iterator.set(replacement);
}
}
}


Коррекция данных
public static void correctDataSequence(List<DataPoint> data) {
if (data.isEmpty()) return;

ListIterator<DataPoint> iterator = data.listIterator();
DataPoint previous = iterator.next();

while (iterator.hasNext()) {
DataPoint current = iterator.next();

// Коррекция аномальных значений
if (isAnomaly(current, previous)) {
DataPoint corrected = interpolate(previous, current);
iterator.set(corrected);
}

previous = current;
}
}



add(E e): Вставка нового элемента

Концептуальное назначение
Метод add(E e) представляет собой наиболее мощную операцию ListIterator — он позволяет вставлять новые элементы в список непосредственно перед элементом, который будет возвращен следующим вызовом next(). Это уникальная возможность, недоступная в базовом Iterator.

Семантика и поведение


Позиция вставки: Элемент вставляется перед элементом, который будет возвращен next(), или в конец списка, если next() вернет NoSuchElementException.
Влияние на итератор: После вставки последующий вызов next() вернет новый элемент, а previous() вернет только что вставленный элемент.
Состояние: Сбрасывает состояние lastReturned, поэтому последующий вызов remove() или set() выбросит исключение до следующего вызова next() или previous().


Внутренние механизмы реализации

Общий алгоритм
public void add(E e) {
checkForComodification();

try {
int i = cursor;
// Вставка в список
list.add(i, e);

// Корректировка состояния итератора
cursor = i + 1;
lastReturned = null;
nextIndex++;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}


Реализация для ArrayList
public void add(E e) {
checkForComodification();

try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastReturned = null;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}


Особенности:
Вызов ArrayList.add(index, element) со сдвигом элементов
Корректировка курсора и счетчиков
Обработка возможного расширения массива


Реализация для LinkedList
public void add(E e) {
checkForComodification();
lastReturned = null;

if (next == null) {
// Вставка в конец
linkLast(e);
} else {
// Вставка перед next
linkBefore(e, next);
}

nextIndex++;
expectedModCount++;
}


Особенности:
Эффективная вставка за O(1) после нахождения позиции
Манипуляции со ссылками между узлами
Автоматическая обработка граничных условий


Особенности производительности

Временная сложность:
ArrayList: O(n) — требуется сдвиг элементов
LinkedList: O(1) — вставка в найденную позицию
Память: Создает новый объект узла (LinkedList) или может вызвать расширение массива (ArrayList).
Потокобезопасность: Не является потокобезопасным.



#Java #для_новичков #beginner #ListIterator
👍2
Паттерны использования

Динамическое пополнение списка

public static void expandListWithPrimes(List<Integer> numbers) {
ListIterator<Integer> iterator = numbers.listIterator();

while (iterator.hasNext()) {
Integer current = iterator.next();

if (isPrime(current)) {
// Вставляем квадрат простого числа перед ним
iterator.previous(); // Возвращаемся к простому числу
iterator.add(current * current);
iterator.next(); // Продолжаем с исходного элемента
}
}
}


Интерполяция пропущенных значений
public static void interpolateMissing(List<Double> values) {
if (values.size() < 2) return;

ListIterator<Double> iterator = values.listIterator();
Double prev = iterator.next();

while (iterator.hasNext()) {
Double current = iterator.next();

if (current == null && prev != null) {
// Нашли null после non-null значения
// Возвращаемся к null и заменяем его интерполированным значением
iterator.previous(); // Переходим к null
iterator.set((prev + getNextNonNull(iterator)) / 2);
iterator.next(); // Продолжаем
}

prev = current;
}
}


Построение упорядоченного списка
public static <T extends Comparable<T>> void insertSorted(List<T> sortedList, T newElement) {
ListIterator<T> iterator = sortedList.listIterator();

while (iterator.hasNext()) {
if (newElement.compareTo(iterator.next()) < 0) {
// Нашли позицию для вставки
iterator.previous(); // Возвращаемся к элементу, который больше нового
iterator.add(newElement);
return;
}
}

// Новый элемент больше всех существующих
iterator.add(newElement);
}



Интеграция всех методов: Сложные сценарии использования

Полный цикл двунаправленной обработки
public class AdvancedListProcessing {

public static <T> void bidirectionalTransform(List<T> list,
Function<T, T> forwardTransform,
Function<T, T> backwardTransform) {
ListIterator<T> iterator = list.listIterator();

// Фаза 1: Прямой обход с трансформацией
while (iterator.hasNext()) {
T original = iterator.next();
T transformed = forwardTransform.apply(original);
iterator.set(transformed);
}

// Фаза 2: Обратный обход с дополнительной трансформацией
while (iterator.hasPrevious()) {
T current = iterator.previous();
T doublyTransformed = backwardTransform.apply(current);

// Условная вставка нового элемента
if (shouldInsertAfter(current)) {
iterator.next(); // Переходим к следующей позиции для вставки
iterator.add(generateNewElement(current));
iterator.previous(); // Возвращаемся к текущему элементу
}

iterator.set(doublyTransformed);
}
}

public static <T> List<T> mergeAdjacentDuplicates(List<T> list) {
if (list.size() < 2) {
return new ArrayList<>(list);
}

ListIterator<T> iterator = list.listIterator();
List<T> result = new ArrayList<>();

T current = iterator.next();
result.add(current);

while (iterator.hasNext()) {
T next = iterator.next();

if (!current.equals(next)) {
// Разные элементы - добавляем в результат
result.add(next);
current = next;
} else {
// Дубликат - пропускаем
// При необходимости можно выполнить merge операцию
mergeLastTwo(result);
}
}

return result;
}

public static <T> void processWithLookaheadAndLookbehind(List<T> list,
👍2
                                                             BiFunction<T, T, T> lookaheadProcessor,
BiFunction<T, T, T> lookbehindProcessor) {
if (list.size() < 2) return;

ListIterator<T> iterator = list.listIterator();

// Обработка с lookahead
while (iterator.hasNext()) {
int currentIndex = iterator.nextIndex();
T current = iterator.next();

if (iterator.hasNext()) {
// Есть следующий элемент для lookahead
T lookahead = iterator.next();
T processed = lookaheadProcessor.apply(current, lookahead);

// Возвращаемся и заменяем текущий элемент
iterator.previous(); // К lookahead
iterator.previous(); // К current
iterator.set(processed);
iterator.next(); // К lookahead
iterator.next(); // К следующему элементу для продолжения
}
}

// Обработка с lookbehind
while (iterator.hasPrevious()) {
T current = iterator.previous();

if (iterator.hasPrevious()) {
T lookbehind = iterator.previous();
T processed = lookbehindProcessor.apply(current, lookbehind);

// Возвращаемся и заменяем
iterator.next(); // К lookbehind
iterator.next(); // К current
iterator.set(processed);
iterator.previous(); // К lookbehind для продолжения
}
}
}
}

Управление состоянием и переходы

Диаграмма состояний ListIterator

Состояния:
1. Инициализация: lastReturned = null, cursor = 0
2. После next(): lastReturned = элемент, cursor = nextIndex
3. После previous(): lastReturned = элемент, cursor = previousIndex + 1
4. После remove(): lastReturned = null
5. После set(): lastReturned остается установленным
6. После add(): lastReturned = null, cursor увеличивается на 1

Допустимые переходы:
- next() → remove() ✓, set() ✓, add() ✓
- previous() → remove() ✓, set() ✓, add() ✓
- remove() → next() ✓, previous() ✓, add() ✗, set() ✗, remove() ✗
- set() → next() ✓, previous() ✓, remove() ✗, add() ✗
- add() → next() ✓, previous() ✓, remove() ✗, set() ✗


Валидация последовательности операций
public class ListIteratorValidator {

public static <T> boolean validateOperationSequence(ListIterator<T> iterator,
List<String> operations) {
boolean canRemoveOrSet = false;

for (String op : operations) {
switch (op) {
case "next":
if (!iterator.hasNext()) return false;
iterator.next();
canRemoveOrSet = true;
break;

case "previous":
if (!iterator.hasPrevious()) return false;
iterator.previous();
canRemoveOrSet = true;
break;

case "remove":
if (!canRemoveOrSet) return false;
iterator.remove();
canRemoveOrSet = false;
break;

case "set":
if (!canRemoveOrSet) return false;
// Необходим элемент для замены
iterator.set(null); // Упрощенная проверка
canRemoveOrSet = false;
break;

case "add":
iterator.add(null); // Всегда допустимо
canRemoveOrSet = false;
break;

default:
return false;
}
}

return true;
}
}



#Java #для_новичков #beginner #ListIterator
👍2
Оптимизированные паттерны использования

Эффективное использование для ArrayList

public class ArrayListOptimizations {

// Избегайте частых add() и remove() в середине списка
public static void efficientArrayListProcessing(List<String> list) {
// Вместо множественных add() через ListIterator
// Собирайте изменения и применяйте их пакетно

List<String> toAdd = new ArrayList<>();
ListIterator<String> iterator = list.listIterator();

while (iterator.hasNext()) {
String current = iterator.next();
if (shouldDuplicate(current)) {
toAdd.add(current + "_copy");
}
}

// Пакетное добавление в конец
list.addAll(toAdd);
}

// Используйте set() вместо remove() + add() для замены
public static void replaceEfficiently(List<Integer> numbers) {
ListIterator<Integer> iterator = numbers.listIterator();

while (iterator.hasNext()) {
Integer current = iterator.next();
if (current % 2 == 0) {
iterator.set(current * 2); // Эффективнее, чем remove() + add()
}
}
}
}


Эффективное использование для LinkedList
public class LinkedListOptimizations {

// LinkedList идеально подходит для частых вставок/удалений через ListIterator
public static void efficientLinkedListProcessing(LinkedList<String> list) {
ListIterator<String> iterator = list.listIterator();

// Частые вставки эффективны
while (iterator.hasNext()) {
String current = iterator.next();
if (requiresPrefix(current)) {
iterator.add("prefix_" + current);
}
}

// Частые удаления также эффективны
iterator = list.listIterator();
while (iterator.hasNext()) {
if (shouldRemove(iterator.next())) {
iterator.remove();
}
}
}

// Использование previous() для навигации назад
public static void findAndProcessFromEnd(LinkedList<String> list, String target) {
ListIterator<String> iterator = list.listIterator(list.size());

while (iterator.hasPrevious()) {
String current = iterator.previous();
if (current.equals(target)) {
// Нашли - обрабатываем и можем идти в обе стороны
processFound(current);

// Можем продолжить в любом направлении
if (iterator.hasPrevious()) {
processPrevious(iterator.previous());
}
if (iterator.hasNext()) {
processNext(iterator.next());
}
break;
}
}
}
}



#Java #для_новичков #beginner #ListIterator
👍2
Best Practices

1. Выбор начальной позиции
// Начинайте с нужной позиции, а не всегда с начала
ListIterator<T> iterator = list.listIterator(startPosition);

// Для обработки с конца
ListIterator<T> reverseIterator = list.listIterator(list.size());


2. Минимизация переходов между next() и previous()
// Неэффективно:
while (iterator.hasNext()) {
T current = iterator.next();
if (condition(current)) {
iterator.previous(); // Дорогой переход
iterator.add(newElement);
iterator.next(); // Еще один переход
iterator.next(); // Пропускаем добавленный элемент
}
}

// Более эффективно:
while (iterator.hasNext()) {
T current = iterator.next();
if (condition(current)) {
iterator.add(newElement);
// current остался тем же, newElement теперь перед ним
}
}


3. Использование nextIndex()/previousIndex() для логики, зависящей от позиции
ListIterator<T> iterator = list.listIterator();
while (iterator.hasNext()) {
int currentIndex = iterator.nextIndex();
T element = iterator.next();

if (currentIndex % 2 == 0) {
// Обработка четных позиций
processEven(element, currentIndex);
}
}


4. Безопасность в многопоточных сценариях

// Всегда синхронизируйте доступ к ListIterator
synchronized(list) {
ListIterator<T> iterator = list.listIterator();
while (iterator.hasNext()) {
// Обработка
}
}

// Или используйте потокобезопасные альтернативы
List<T> syncList = Collections.synchronizedList(new ArrayList<>());
// Но даже synchronizedList требует внешней синхронизации для итерации


#Java #для_новичков #beginner #ListIterator
👍2