JavaRush /Курсы /JAVA 25 SELF /NavigableSet/NavigableMap

NavigableSet/NavigableMap

JAVA 25 SELF
27 уровень , 3 лекция
Открыта

1. Введение

В стандартной библиотеке Java есть множество коллекций, но когда речь заходит о диапазонах, поиске «ближайших» элементов, приоритизации и работе с временными слотами, на сцену выходят интерфейсы NavigableSet и NavigableMap.

NavigableSet расширяет SortedSet, добавляя методы для поиска элементов относительно заданного значения (меньше, больше, ближайший и т. д.), а также для работы с диапазонами.
NavigableMap расширяет SortedMap, добавляя похожие методы для поиска по ключам и работы с диапазонами.

Самые известные реализации: TreeSet и TreeMap.

Когда нужны NavigableSet/NavigableMap?

  • Когда важно не просто хранить уникальные элементы/ключи в отсортированном порядке, но и быстро находить «соседей» (например, ближайшее свободное время, приоритетную задачу, диапазон значений).
  • Когда нужно работать с диапазонами: «все элементы между X и Y», «все ключи больше/меньше заданного», «найти ближайший ключ».

2. Диапазоны: subSet, headSet, tailSet

Методы для работы с диапазонами

NavigableSet и NavigableMap позволяют получать «живые» представления (view) на подмножества коллекции:

  • subSet(fromElement, fromInclusive, toElement, toInclusive) — элементы в диапазоне [fromElement; toElement], включительно/исключительно.
  • headSet(toElement, inclusive) — все элементы меньше (или меньше либо равно) toElement.
  • tailSet(fromElement, inclusive) — все элементы больше (или больше либо равно) fromElement.

Пример:

NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));

// Диапазон от 15 (включительно) до 45 (исключительно)
NavigableSet<Integer> sub = set.subSet(15, true, 45, false); // [20, 30, 40]
System.out.println(sub); // [20, 30, 40]

// Все элементы <= 30
NavigableSet<Integer> head = set.headSet(30, true); // [10, 20, 30]

// Все элементы > 20
NavigableSet<Integer> tail = set.tailSet(20, false); // [30, 40, 50]

Включительность/исключительность:

  • true — включительно (элемент входит в диапазон)
  • false — исключительно (элемент не входит)

Для NavigableMap методы аналогичны, только работают с ключами.

3. Операции ближних значений: floor, ceiling, lower, higher; pollFirst/Last

Поиск ближайших элементов

NavigableSet:

  • lower(E e) — наибольший элемент < e (строго меньше)
  • floor(E e) — наибольший элемент ≤ e (меньше или равно)
  • ceiling(E e) — наименьший элемент ≥ e (больше или равно)
  • higher(E e) — наименьший элемент > e (строго больше)

Пример:

NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));

System.out.println(set.lower(30));   // 20
System.out.println(set.floor(30));   // 30
System.out.println(set.ceiling(25)); // 30
System.out.println(set.higher(40));  // 50

pollFirst() / pollLast()

  • pollFirst() — удаляет и возвращает наименьший элемент (или null, если пусто)
  • pollLast() — удаляет и возвращает наибольший элемент

Пример:

System.out.println(set.pollFirst()); // 10
System.out.println(set.pollLast());  // 50
System.out.println(set);             // [20, 30, 40]

NavigableMap: аналогичные методы для ключей — lowerKey, floorKey, ceilingKey, higherKey, а также pollFirstEntry, pollLastEntry.

4. Виды: descendingSet/descendingMap; представления‑view и их «живость»

Обратный порядок: descendingSet/descendingMap

  • descendingSet() — возвращает представление множества в обратном порядке.
  • descendingMap() — возвращает представление карты в обратном порядке ключей.

Пример:

NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));
NavigableSet<Integer> desc = set.descendingSet();
System.out.println(desc); // [50, 40, 30, 20, 10]

Важно: это не копия, а «живое» представление (view) на тот же набор данных. Изменения в одном отражаются в другом.

Представления (view) и их «живость»

  • Методы subSet, headSet, tailSet, descendingSet, descendingMap возвращают view — «живое» представление на исходную коллекцию.
  • Любые изменения в view отражаются в исходной коллекции и наоборот.
  • Если удалить элемент из subSet — он исчезнет и из исходного множества.

Пример:

NavigableSet<Integer> set = new TreeSet<>(List.of(10, 20, 30, 40, 50));
NavigableSet<Integer> sub = set.subSet(20, true, 40, true); // [20, 30, 40]
sub.remove(30);
System.out.println(set); // [10, 20, 40, 50]

Осторожно: если вы измените исходную коллекцию так, что элемент «выпадет» из диапазона view, при следующем обращении к view получите ConcurrentModificationException.

5. Кейсы использования NavigableSet/NavigableMap

1. Слоты времени/расписания

Задача: найти ближайший свободный временной слот для встречи.

NavigableSet<LocalTime> slots = new TreeSet<>(List.of(
    LocalTime.of(9, 0),
    LocalTime.of(10, 0),
    LocalTime.of(11, 0),
    LocalTime.of(14, 0)
));

LocalTime requested = LocalTime.of(10, 30);
LocalTime nextSlot = slots.ceiling(requested); // 11:00
System.out.println("Ближайшее время: " + nextSlot);

2. Приоритизация (очередь с приоритетом)

Задача: всегда быстро получать элемент с наивысшим/наименьшим приоритетом.

NavigableSet<Task> tasks = new TreeSet<>(Comparator.comparingInt(Task::priority));
tasks.add(new Task("Почта", 2));
tasks.add(new Task("Звонок", 1));
tasks.add(new Task("Отчёт", 3));

Task next = tasks.pollFirst(); // Задача с наивысшим приоритетом (1)

3. «Самый близкий ключ» (например, для поиска диапазона в Map)

Задача: найти диапазон значений по ключу, или ближайший ключ.

NavigableMap<Integer, String> grades = new TreeMap<>();
grades.put(50, "Неудовлетворительно");
grades.put(65, "Удовлетворительно");
grades.put(75, "Хорошо");
grades.put(85, "Отлично");

int score = 78;
int key = grades.floorKey(score); // 75
System.out.println("Оценка: " + grades.get(key)); // Хорошо

6. Практика: мини-примеры

Пример 1: Диапазон дат

NavigableSet<LocalDate> holidays = new TreeSet<>(List.of(
    LocalDate.of(2024, 1, 1),
    LocalDate.of(2024, 5, 1),
    LocalDate.of(2024, 12, 31)
));
LocalDate from = LocalDate.of(2024, 1, 1);
LocalDate to = LocalDate.of(2024, 6, 1);
NavigableSet<LocalDate> spring = holidays.subSet(from, true, to, false);
System.out.println(spring); // [2024-01-01, 2024-05-01]

Пример 2: Поиск ближайшего значения

NavigableSet<Integer> numbers = new TreeSet<>(List.of(10, 20, 30, 40, 50));
int x = 25;
System.out.println("Меньше: " + numbers.lower(x));              // 20
System.out.println("Меньше или равно: " + numbers.floor(x));    // 20
System.out.println("Больше или равно: " + numbers.ceiling(x));  // 30
System.out.println("Больше: " + numbers.higher(x));             // 30

7. Типичные ошибки и нюансы

Ошибка №1: Путают включительность/исключительность в subSet/headSet/tailSet.
Всегда внимательно смотрите на параметры inclusive — по умолчанию в старых версиях Java диапазоны были исключительными, в NavigableSet/NavigableMap можно явно указать включительно/исключительно.

Ошибка №2: Ожидание, что view — это копия.
View — это «живое» представление, а не копия! Изменения в view отражаются в исходной коллекции.

Ошибка №3: ConcurrentModificationException при изменении исходной коллекции вне view.
Если вы изменяете исходную коллекцию так, что элемент «выпадает» из диапазона view, при следующем обращении к view получите исключение.

Ошибка №4: Использование NavigableSet/NavigableMap без Comparable или Comparator.
Эти коллекции требуют, чтобы элементы (или ключи) были сравнимы (Comparable) или был передан Comparator. Иначе получите ClassCastException.

Ошибка №5: Ожидание, что pollFirst/pollLast не изменяют коллекцию.
Эти методы удаляют элементы! Если нужен просто просмотр — используйте first()/last().

1
Задача
JAVA 25 SELF, 27 уровень, 3 лекция
Недоступна
Определение уровня лояльности клиента 👑
Определение уровня лояльности клиента 👑
1
Задача
JAVA 25 SELF, 27 уровень, 3 лекция
Недоступна
Управление партиями товаров на складе 📦
Управление партиями товаров на складе 📦
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ