Паттерны использования
Относительная навигация
Валидация границ
Создание подсписков по позициям
Расширенные методы модификации
set(E e): Замена текущего элемента
Концептуальное назначение
Метод set(E e) позволяет заменить последний элемент, возвращенный вызовом next() или previous(). Это расширяет возможности итератора за пределы простого удаления, позволяя модифицировать содержимое списка во время итерации.
Семантика и поведение
Условия вызова: Может быть вызван только после успешного вызова next() или previous().
Исключения: Выбрасывает IllegalStateException, если не был вызван next() или previous(), или если после последнего вызова next()/previous() был вызван add() или remove().
Эффект: Заменяет элемент в списке без изменения размера коллекции или позиции итератора.
Внутренние механизмы реализации
Общий алгоритм
Реализация для ArrayList
Особенности:
Прямой доступ к массиву по индексу
Проверка границ массива
Сохранение позиции итератора
Реализация для LinkedList
Особенности:
Прямая модификация поля item узла
Не требует поиска узла
Высокая эффективность
Особенности производительности
Временная сложность:
ArrayList: O(1) — прямой доступ по индексу
LinkedList: O(1) — модификация существующего узла
Память: Может создавать новый объект, если заменяемый элемент становится недостижимым.
Потокобезопасность: Не является потокобезопасным.
#Java #для_новичков #beginner #ListIterator
Относительная навигация
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
Паттерны использования
Модификация элементов на лету
Условная замена
Коррекция данных
add(E e): Вставка нового элемента
Концептуальное назначение
Метод add(E e) представляет собой наиболее мощную операцию ListIterator — он позволяет вставлять новые элементы в список непосредственно перед элементом, который будет возвращен следующим вызовом next(). Это уникальная возможность, недоступная в базовом Iterator.
Семантика и поведение
Позиция вставки: Элемент вставляется перед элементом, который будет возвращен next(), или в конец списка, если next() вернет NoSuchElementException.
Влияние на итератор: После вставки последующий вызов next() вернет новый элемент, а previous() вернет только что вставленный элемент.
Состояние: Сбрасывает состояние lastReturned, поэтому последующий вызов remove() или set() выбросит исключение до следующего вызова next() или previous().
Внутренние механизмы реализации
Общий алгоритм
Реализация для ArrayList
Особенности:
Вызов ArrayList.add(index, element) со сдвигом элементов
Корректировка курсора и счетчиков
Обработка возможного расширения массива
Реализация для LinkedList
Особенности:
Эффективная вставка за O(1) после нахождения позиции
Манипуляции со ссылками между узлами
Автоматическая обработка граничных условий
Особенности производительности
Временная сложность:
ArrayList: O(n) — требуется сдвиг элементов
LinkedList: O(1) — вставка в найденную позицию
Память: Создает новый объект узла (LinkedList) или может вызвать расширение массива (ArrayList).
Потокобезопасность: Не является потокобезопасным.
#Java #для_новичков #beginner #ListIterator
Модификация элементов на лету
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
Эффективное использование для LinkedList
#Java #для_новичков #beginner #ListIterator
Эффективное использование для 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. Выбор начальной позиции
2. Минимизация переходов между next() и previous()
3. Использование nextIndex()/previousIndex() для логики, зависящей от позиции
4. Безопасность в многопоточных сценариях
#Java #для_новичков #beginner #ListIterator
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
Что выведет код?
#Tasks
import java.util.*;
public class Task121225 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
ListIterator<String> it = list.listIterator();
System.out.print(it.next() + " ");
System.out.print(it.next() + " ");
it.add("X");
System.out.print(it.previous() + " ");
System.out.print(it.next() + " ");
System.out.print(it.previous() + " ");
it.remove();
System.out.println(list);
}
}
#Tasks
👍1
Варианты ответа:
Anonymous Quiz
38%
A B X C X [A, B, C, D]
38%
A B X X X [A, B, X, C, D]
25%
A B X X [A, B, C, D]
0%
A B X X [A, B, X, C, D]
👍1
Вопрос с собеседований
Как работает ReentrantReadWriteLock?🤓
Ответ:
Он разделяет блокировки на read и write.
Несколько читателей могут работать одновременно, но запись блокируется полностью. Это повышает производительность, если чтений больше, чем записей.
Однако write-блокировка может вызывать «голодание» читателей.
#собеседование
Как работает ReentrantReadWriteLock?
Ответ:
Несколько читателей могут работать одновременно, но запись блокируется полностью. Это повышает производительность, если чтений больше, чем записей.
Однако write-блокировка может вызывать «голодание» читателей.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История IT-технологий сегодня — 13 декабря
ℹ️ Кто родился в этот день
Дже́йми Ве́рнер Зави́нски (англ. Jamie Werner Zawinski, известный также как jwz; род. 3 ноября 1968, Питтсбург, Пенсильвания) — программист, один из ключевых разработчиков Netscape и Mozilla; активно повлиял на развитие ранних браузеров и веб-экосистемы.
🌐 Знаковые события
1967 — в США состоялся успешный запуск космического аппарата «Пионер-8».
#Biography #Birth_Date #Events #13Декабря
Дже́йми Ве́рнер Зави́нски (англ. Jamie Werner Zawinski, известный также как jwz; род. 3 ноября 1968, Питтсбург, Пенсильвания) — программист, один из ключевых разработчиков Netscape и Mozilla; активно повлиял на развитие ранних браузеров и веб-экосистемы.
1967 — в США состоялся успешный запуск космического аппарата «Пионер-8».
#Biography #Birth_Date #Events #13Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
С 06.12 по 12.12
Предыдущий пост(с С 29.11 по 05.12)
Воскресный мотивационный пост:
Не было мотивации
Запись встреч/видео:
не было
Обучающие статьи:
Java:
Коллекции в Java
Глава 2. List — списки в Java
Практика
Глава 6. Итераторы
Интерфейс Iterator — фундаментальный механизм обхода коллекций
Интерфейс ListIterator — двунаправленный обход и расширенные возможности
Spring Cloud Gateway
Конфигурация маршрутов в Spring Cloud Gateway
Predicates (условия маршрутизации)
Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!
Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
Предыдущий пост(с С 29.11 по 05.12)
Воскресный мотивационный пост:
Не было мотивации
Запись встреч/видео:
не было
Обучающие статьи:
Java:
Коллекции в Java
Глава 2. List — списки в Java
Практика
Глава 6. Итераторы
Интерфейс Iterator — фундаментальный механизм обхода коллекций
Интерфейс ListIterator — двунаправленный обход и расширенные возможности
Spring Cloud Gateway
Конфигурация маршрутов в Spring Cloud Gateway
Predicates (условия маршрутизации)
Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!
Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
👍1
Ну что встречаемся завтра?
Можно что-то новое рассмотреть, если желание есть😜
Предлагайте👍
Можно что-то новое рассмотреть, если желание есть
Предлагайте
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История IT-технологий сегодня — 14 декабря
ℹ️ Кто родился в этот день
Никола́й Генна́диевич Ба́сов (14 декабря 1922, Усмань, Тамбовская губерния, РСФСР — 1 июля 2001, Москва) — советский и российский физик, лауреат Нобелевской премии по физике (1964), Ленинской премии (1959) и Государственной премии СССР (1989). Дважды Герой Социалистического Труда (1969, 1982). Внёс значительный вклад в развитие квантовой электроники и создание лазерных установок.
🌐 Знаковые события
1977 — введена в эксплуатацию одна из первых коммерческих линий связи на основе оптоволокна в США.
#Biography #Birth_Date #Events #14Декабря
Никола́й Генна́диевич Ба́сов (14 декабря 1922, Усмань, Тамбовская губерния, РСФСР — 1 июля 2001, Москва) — советский и российский физик, лауреат Нобелевской премии по физике (1964), Ленинской премии (1959) и Государственной премии СССР (1989). Дважды Герой Социалистического Труда (1969, 1982). Внёс значительный вклад в развитие квантовой электроники и создание лазерных установок.
1977 — введена в эксплуатацию одна из первых коммерческих линий связи на основе оптоволокна в США.
#Biography #Birth_Date #Events #14Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
История IT-технологий сегодня — 15 декабря
ℹ️ Кто родился в этот день
Антуа́н Анри́ Беккере́ль (фр. Antoine Henri Becquerel; 15 декабря 1852 — 25 августа 1908) — французский физик, лауреат Нобелевской премии по физике и один из первооткрывателей радиоактивности. В честь него названа единица измерения активности радиоактивного источника в Международной системе единиц (СИ) — беккерель.
🌐 Знаковые события
1970 — советская межпланетная станция «Венера-7» успешно совершила посадку на Венере. Первая успешная посадка на другой планете космического аппарата, посланного с Земли.
1972 — Деннис Ритчи завершил первую работоспособную версию языка C для UNIX.
1984 — была запущена автоматическая межпланетная станция «Вега-1».
#Biography #Birth_Date #Events #15Декабря
Антуа́н Анри́ Беккере́ль (фр. Antoine Henri Becquerel; 15 декабря 1852 — 25 августа 1908) — французский физик, лауреат Нобелевской премии по физике и один из первооткрывателей радиоактивности. В честь него названа единица измерения активности радиоактивного источника в Международной системе единиц (СИ) — беккерель.
1970 — советская межпланетная станция «Венера-7» успешно совершила посадку на Венере. Первая успешная посадка на другой планете космического аппарата, посланного с Земли.
1972 — Деннис Ритчи завершил первую работоспособную версию языка C для UNIX.
1984 — была запущена автоматическая межпланетная станция «Вега-1».
#Biography #Birth_Date #Events #15Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Predicates в Spring Cloud Gateway: Механика и расширяемость условий маршрутизации
Механизм работы предикатов: от конфигурации к исполнению
Предикаты в Spring Cloud Gateway — это условия, определяющие, должен ли конкретный маршрут быть применён к входящему запросу. На архитектурном уровне предикаты реализуют паттерн Factory Method через абстракцию RoutePredicateFactory. Каждый предикат компилируется в объект Predicate<ServerWebExchange>, который затем оценивается в процессе сопоставления маршрута.
Процесс инициализации предиката
Когда приложение Spring Cloud Gateway стартует, происходит следующая последовательность:
Парсинг конфигурации: Конфигурационный файл (YAML или properties) читается, и определения предикатов преобразуются в объекты PredicateDefinition.
Поиск фабрик: Для каждого PredicateDefinition Spring ищет соответствующий bean типа RoutePredicateFactory. Имя фабрики определяется по свойству name в определении предиката.
Создание конфигурационного объекта: Фабрика создаёт конфигурационный объект (обычно static inner class), который заполняется значениями из аргументов предиката.
Генерация предиката: Вызывается метод apply() фабрики, который возвращает функциональный интерфейс Predicate<ServerWebExchange>.
Порядок оценки предикатов
Предикаты внутри маршрута оцениваются в порядке их объявления, но с важной оптимизацией: оценка происходит лениво и останавливается при первом false.
Механизм оценки реализован через цепочку вызовов and():
Детальный разбор встроенных предикатов
Path Predicate: эффективное сопоставление путей
Предикат Path — наиболее часто используемый. Внутри он использует PathPatternParser из Spring WebFlux, который компилирует строковые шаблоны в оптимизированные структуры данных.
Механика работы:
Использование переменных пути:
Host Predicate: виртуальные хосты и шаблоны
Предикат Host позволяет маршрутизировать на основе заголовка Host или имени сервера.
Расширенные шаблоны:
Шаблон {subdomain}.api.example.com извлекает поддомен как переменную.
Внутренне это преобразуется в регулярное выражение:
Query Predicate: параметры запроса
Предикат Query проверяет наличие и значение параметров URL.
Конфигурация с регулярными выражениями:
Внутренняя реализация использует ServerWebExchange.getRequest().getQueryParams() для доступа к параметрам. Важно отметить, что параметры кэшируются после первого чтения для эффективности.
#Java #middle #Spring_Cloud_Gateway
Механизм работы предикатов: от конфигурации к исполнению
Предикаты в Spring Cloud Gateway — это условия, определяющие, должен ли конкретный маршрут быть применён к входящему запросу. На архитектурном уровне предикаты реализуют паттерн Factory Method через абстракцию RoutePredicateFactory. Каждый предикат компилируется в объект Predicate<ServerWebExchange>, который затем оценивается в процессе сопоставления маршрута.
Процесс инициализации предиката
Когда приложение Spring Cloud Gateway стартует, происходит следующая последовательность:
Парсинг конфигурации: Конфигурационный файл (YAML или properties) читается, и определения предикатов преобразуются в объекты PredicateDefinition.
Поиск фабрик: Для каждого PredicateDefinition Spring ищет соответствующий bean типа RoutePredicateFactory. Имя фабрики определяется по свойству name в определении предиката.
Создание конфигурационного объекта: Фабрика создаёт конфигурационный объект (обычно static inner class), который заполняется значениями из аргументов предиката.
Генерация предиката: Вызывается метод apply() фабрики, который возвращает функциональный интерфейс Predicate<ServerWebExchange>.
Порядок оценки предикатов
Предикаты внутри маршрута оцениваются в порядке их объявления, но с важной оптимизацией: оценка происходит лениво и останавливается при первом false.
Механизм оценки реализован через цепочку вызовов and():
// Внутренняя реализация оценки предикатов маршрута
public boolean test(ServerWebExchange exchange) {
for (Predicate<ServerWebExchange> predicate : predicates) {
if (!predicate.test(exchange)) {
return false; // Прерывание при первом false
}
}
return true;
}
Детальный разбор встроенных предикатов
Path Predicate: эффективное сопоставление путей
Предикат Path — наиболее часто используемый. Внутри он использует PathPatternParser из Spring WebFlux, который компилирует строковые шаблоны в оптимизированные структуры данных.
Механика работы:
// Пример внутренней реализации
public class PathRoutePredicateFactory extends
AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
final PathPattern pattern = pathPatternParser.parse(config.getPattern());
return exchange -> {
PathContainer path = exchange.getRequest().getPath();
PathPattern.PathMatchInfo info = pattern.matchAndExtract(path);
if (info != null) {
// Сохранение извлечённых переменных в атрибуты
exchange.getAttributes().put(
PathPatternRoutePredicateHandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
info.getUriVariables()
);
return true;
}
return false;
};
}
}
Использование переменных пути:
predicates:
- Path=/api/users/{id}/orders/{orderId}
Извлечённые переменные id и orderId доступны в фильтрах через шаблоны ${id} и ${orderId}.
Host Predicate: виртуальные хосты и шаблоны
Предикат Host позволяет маршрутизировать на основе заголовка Host или имени сервера.
Расширенные шаблоны:
predicates:
- Host=**.example.com,{subdomain}.api.example.com
Шаблон {subdomain}.api.example.com извлекает поддомен как переменную.
Внутренне это преобразуется в регулярное выражение:
// Пример генерации паттерна
String pattern = "^(?<subdomain>[^.]+)\\.api\\.example\\.com$";
Query Predicate: параметры запроса
Предикат Query проверяет наличие и значение параметров URL.
Конфигурация с регулярными выражениями:
predicates:
- Query=version,v[0-9]+\.[0-9]+ # Проверка формата версии
- Query=token # Просто наличие параметра
Внутренняя реализация использует ServerWebExchange.getRequest().getQueryParams() для доступа к параметрам. Важно отметить, что параметры кэшируются после первого чтения для эффективности.
#Java #middle #Spring_Cloud_Gateway
👍2
RemoteAddr Predicate: контроль доступа по IP
Этот предикат проверяет IP-адрес клиента через CIDR-нотацию.
Механика работы:
Важные нюансы:
При использовании за прокси (nginx, load balancer) необходимо корректно настраивать X-Forwarded-For
IP-адрес извлекается из ServerHttpRequest.getRemoteAddress(), который может быть обёрткой для реального соединения
Weight Predicate: взвешенная маршрутизация
Предикат Weight используется для канареечных развёртываний и A/B тестирования. Он работает в паре: один маршрут определяет группу, а другие маршруты распределяют вес внутри группы.
Конфигурация:
Внутренняя механика:
При инициализации создаётся общий AtomicInteger для группы
Для каждого запроса вычисляется хэш (обычно на основе пути и/или заголовков)
Хэш мапируется на диапазон весов для выбора маршрута
Выбор детерминирован для одинаковых хэшей, что обеспечивает согласованную маршрутизацию
Method Predicate: фильтрация по HTTP-методам
Простейший, но эффективный предикат для ограничения доступа:
Сложные комбинации через AND/OR
Spring Cloud Gateway поддерживает логические комбинации предикатов через DSL и конфигурацию.
Комбинации через YAML
В YAML предикаты по умолчанию объединяются через логическое И (AND):
Все четыре условия должны быть истинны для применения маршрута.
Логическое ИЛИ через кастомный предикат
Для реализации ИЛИ необходимо создать составной предикат:
Использование в YAML:
Негация (NOT) через After с отрицанием
Прямой поддержки NOT нет, но можно использовать временные предикаты с отрицанием:
#Java #middle #Spring_Cloud_Gateway
Этот предикат проверяет IP-адрес клиента через CIDR-нотацию.
Механика работы:
public Predicate<ServerWebExchange> apply(Config config) {
List<IpSubnetFilterRule> sources = config.getSources().stream()
.map(IpSubnetFilterRule::new)
.collect(Collectors.toList());
return exchange -> {
InetSocketAddress remoteAddress = exchange.getRequest()
.getRemoteAddress();
if (remoteAddress != null) {
for (IpSubnetFilterRule source : sources) {
if (source.matches(remoteAddress)) {
return true;
}
}
}
return false;
};
}Важные нюансы:
При использовании за прокси (nginx, load balancer) необходимо корректно настраивать X-Forwarded-For
IP-адрес извлекается из ServerHttpRequest.getRemoteAddress(), который может быть обёрткой для реального соединения
Weight Predicate: взвешенная маршрутизация
Предикат Weight используется для канареечных развёртываний и A/B тестирования. Он работает в паре: один маршрут определяет группу, а другие маршруты распределяют вес внутри группы.
Конфигурация:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.example.org
predicates:
- Path=/api/**
- Weight=group1, 80
metadata:
response-timeout: 3000
- id: weight_low
uri: https://weightlow.example.org
predicates:
- Path=/api/**
- Weight=group1, 20
metadata:
response-timeout: 5000
Внутренняя механика:
При инициализации создаётся общий AtomicInteger для группы
Для каждого запроса вычисляется хэш (обычно на основе пути и/или заголовков)
Хэш мапируется на диапазон весов для выбора маршрута
Выбор детерминирован для одинаковых хэшей, что обеспечивает согласованную маршрутизацию
Method Predicate: фильтрация по HTTP-методам
Простейший, но эффективный предикат для ограничения доступа:
predicates:
- Method=GET,POST,OPTIONS
Внутренне преобразуется в проверку exchange.getRequest().getMethod().
Сложные комбинации через AND/OR
Spring Cloud Gateway поддерживает логические комбинации предикатов через DSL и конфигурацию.
Комбинации через YAML
В YAML предикаты по умолчанию объединяются через логическое И (AND):
predicates:
- Path=/api/**
- Method=GET
- Header=X-API-Key,.*
- Query=version,v2
Все четыре условия должны быть истинны для применения маршрута.
Логическое ИЛИ через кастомный предикат
Для реализации ИЛИ необходимо создать составной предикат:
@Component
public class OrRoutePredicateFactory extends
AbstractRoutePredicateFactory<OrRoutePredicateFactory.Config> {
public OrRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
for (PredicateDefinition def : config.getPredicates()) {
RoutePredicateFactory factory = findFactory(def);
if (factory != null && factory.apply(convert(def)).test(exchange)) {
return true;
}
}
return false;
};
}
public static class Config {
private List<PredicateDefinition> predicates = new ArrayList<>();
// геттеры и сеттеры
}
}
Использование в YAML:
predicates:
- name: Or
args:
predicates:
- name: Path
args:
pattern: /api/v1/**
- name: Path
args:
pattern: /legacy/api/**
Негация (NOT) через After с отрицанием
Прямой поддержки NOT нет, но можно использовать временные предикаты с отрицанием:
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// Инвертирование любого предиката
return !delegatePredicate.test(exchange);
};
}#Java #middle #Spring_Cloud_Gateway
👍2
Создание кастомного предиката
Реализация RoutePredicateFactory
Создание кастомного предиката требует реализации интерфейса RoutePredicateFactory:
#Java #middle #Spring_Cloud_Gateway
Реализация RoutePredicateFactory
Создание кастомного предиката требует реализации интерфейса RoutePredicateFactory:
@Component
public class JwtClaimRoutePredicateFactory extends
AbstractRoutePredicateFactory<JwtClaimRoutePredicateFactory.Config> {
private final JwtDecoder jwtDecoder;
public JwtClaimRoutePredicateFactory(JwtDecoder jwtDecoder) {
super(Config.class);
this.jwtDecoder = jwtDecoder;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// Извлечение JWT из заголовка
String authHeader = exchange.getRequest()
.getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return false;
}
String token = authHeader.substring(7);
try {
// Декодирование и проверка claims
Jwt jwt = jwtDecoder.decode(token);
// Проверка конкретного claim
String claimValue = jwt.getClaimAsString(config.getClaimName());
if (claimValue == null) {
return false;
}
// Сопоставление с ожидаемым значением
if (config.getPattern() != null) {
return claimValue.matches(config.getPattern());
}
return config.getExpectedValues()
.stream()
.anyMatch(expected -> expected.equals(claimValue));
} catch (JwtException e) {
return false;
}
};
}
// Конфигурационный класс
public static class Config {
private String claimName;
private String pattern;
private List<String> expectedValues = new ArrayList<>();
// Геттеры и сеттеры
public String getClaimName() {
return claimName;
}
public void setClaimName(String claimName) {
this.claimName = claimName;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public List<String> getExpectedValues() {
return expectedValues;
}
public void setExpectedValues(List<String> expectedValues) {
this.expectedValues = expectedValues;
}
}
// Короткая форма конфигурации
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("claimName", "pattern");
}
}
#Java #middle #Spring_Cloud_Gateway
👍2
Регистрация и использование
Spring Boot автоматически обнаружит компонент через аннотацию @Component.
Для использования в YAML:
Или в краткой форме, если реализован shortcutFieldOrder():
Предикат с динамической конфигурацией
Для предикатов, требующих внешней конфигурации или состояния:
Асинхронные предикаты
Для предикатов, требующих асинхронных операций (обращение к БД, внешним API):
Асинхронные предикаты возвращают AsyncPredicate<ServerWebExchange>, который оценивается в реактивном контексте и не блокирует event loop threads.
#Java #middle #Spring_Cloud_Gateway
Spring Boot автоматически обнаружит компонент через аннотацию @Component.
Для использования в YAML:
predicates:
- name: JwtClaim
args:
claimName: roles
expectedValues: admin,superuser
Или в краткой форме, если реализован shortcutFieldOrder():
predicates:
- JwtClaim=roles,admin|superuser
Предикат с динамической конфигурацией
Для предикатов, требующих внешней конфигурации или состояния:
@Component
public class RateLimitPredicateFactory extends
AbstractRoutePredicateFactory<RateLimitPredicateFactory.Config>
implements ApplicationListener<EnvironmentChangeEvent> {
private final RateLimiter rateLimiter;
private final Map<String, Config> configCache = new ConcurrentHashMap<>();
public RateLimitPredicateFactory(RateLimiter rateLimiter) {
super(Config.class);
this.rateLimiter = rateLimiter;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// Кэширование конфигурации для производительности
String cacheKey = config.getClientId() + ":" + config.getLimit();
configCache.put(cacheKey, config);
return exchange -> {
Config cachedConfig = configCache.get(cacheKey);
// Извлечение идентификатора клиента из запроса
String clientId = extractClientId(exchange);
if (clientId == null || !clientId.equals(cachedConfig.getClientId())) {
return false;
}
// Проверка лимита
return rateLimiter.tryAcquire(clientId, cachedConfig.getLimit());
};
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
// Очистка кэша при изменении конфигурации
configCache.clear();
}
private String extractClientId(ServerWebExchange exchange) {
// Логика извлечения clientId из запроса
return exchange.getRequest().getHeaders()
.getFirst("X-Client-Id");
}
public static class Config {
private String clientId;
private int limit;
// Геттеры и сеттеры
}
}
Асинхронные предикаты
Для предикатов, требующих асинхронных операций (обращение к БД, внешним API):
@Component
public class ExternalServicePredicateFactory extends
AbstractRoutePredicateFactory<ExternalServicePredicateFactory.Config> {
private final WebClient webClient;
@Override
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return exchange -> {
// Асинхронная проверка через внешний сервис
return webClient.post()
.uri(config.getValidationUrl())
.bodyValue(buildValidationRequest(exchange))
.retrieve()
.bodyToMono(ValidationResponse.class)
.map(response -> response.isValid())
.onErrorReturn(false); // При ошибке считаем предикат false
};
}
public static class Config {
private String validationUrl;
private int timeoutMs = 1000;
// Геттеры и сеттеры
}
}
Асинхронные предикаты возвращают AsyncPredicate<ServerWebExchange>, который оценивается в реактивном контексте и не блокирует event loop threads.
#Java #middle #Spring_Cloud_Gateway
👍2
Производительность и оптимизации
Кэширование вычислений предикатов
Для дорогих предикатов важно кэшировать результаты:
Порядок предикатов для оптимизации
Располагайте предикаты в порядке возрастания вычислительной сложности:
Мониторинг и метрики
Интеграция с Micrometer для мониторинга эффективности предикатов:
#Java #middle #Spring_Cloud_Gateway
Кэширование вычислений предикатов
Для дорогих предикатов важно кэшировать результаты:
public Predicate<ServerWebExchange> apply(Config config) {
LoadingCache<ServerWebExchange, Boolean> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(exchange -> computeExpensivePredicate(exchange, config));
return exchange -> {
try {
return cache.get(exchange);
} catch (Exception e) {
return false;
}
};
}Порядок предикатов для оптимизации
Располагайте предикаты в порядке возрастания вычислительной сложности:
predicates:
- Method=GET # Быстрая проверка
- Path=/api/** # Умеренная сложность
- Query=token,.* # Простая проверка параметра
- JwtClaim=roles,admin # Дорогая проверка JWT
Это позволяет отсеивать неподходящие запросы до выполнения дорогих операций.
Мониторинг и метрики
Интеграция с Micrometer для мониторинга эффективности предикатов:
public Predicate<ServerWebExchange> apply(Config config) {
Counter matchCounter = meterRegistry.counter(
"gateway.predicate.matches",
"predicate", config.getName()
);
Counter totalCounter = meterRegistry.counter(
"gateway.predicate.checks",
"predicate", config.getName()
);
Timer timer = meterRegistry.timer(
"gateway.predicate.duration",
"predicate", config.getName()
);
return exchange -> {
totalCounter.increment();
return timer.record(() -> {
boolean matches = internalTest(exchange, config);
if (matches) {
matchCounter.increment();
}
return matches;
});
};
}#Java #middle #Spring_Cloud_Gateway
👍3