惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
ThreatConnect
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
H
Help Net Security
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
G
Google Developers Blog
量子位
U
Unit 42
I
InfoQ
V
V2EX
F
Fox-IT International blog
P
Privacy & Cybersecurity Law Blog
V
Visual Studio Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
C
CERT Recently Published Vulnerability Notes
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
C
Cisco Blogs
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
Recorded Future
Recorded Future
T
Tenable Blog
W
WeLiveSecurity
腾讯CDC
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
PCI Perspectives
PCI Perspectives

Все публикации подряд на Хабре

Cursor пишет вам unit‑тесты за минуту. 5 паттернов, на которых эти тесты пропустят любой баг Щука на весло: почему случайная выручка опасна для молодого продукта Как я делал VPN-сервис в 2026 году Из разработчика в системные аналитики: практический путь в профессию Почему ваш сайт не продаёт, хотя SEO-шник клянётся, что всё хорошо Сейчас никому не нужны технологии, все обсуждают только ИИ От папки с созвонами до 5K+ юзеров: как pet-проект «для себя» встретился с реальными пользователями Отстаём своим путём Лучшие приложения для изучения иностранных языков: что выбрать в 2026 году Что не нужно знать топ менеджеру, что бы провалить внедрение AI | ИИ Одна строчка .Result роняет ваш ASP.NET Core при CPU 8 %: разбор hill-climbing в .NET 9 Миграции в Go-проекте: PostgreSQL в Docker и goose на практике Что такое OpSec, если углубится Российская компания на 50 человек платит 350 000 ₽ в год за софт, который дублирует сам себя Локализовать нельзя ошибиться. Как работает локализация в автономном транспорте и почему это — самая сложная задача. 1/2 Inside AI Meetup — как это было? Делимся записями докладов, фото и атмосферой Делаем сайт из картинки в нейронке Один простой механизм управляет практически всем в игре Cities: Skylines Встраиваемая векторная БД для RAG на .NET 8: когда внешние сервисы избыточны Gemini-3.5-flash догнал GPT-5.5 на 97/S и в 2.5× дешевле. Но главное — китайцы выигрывают по цене и качеству JavaScript. Работа с большими файлами в браузере. Часть 2/2: Создание 5Gb файлов в браузере Как визуализировать задачи и зависимости в проекте: обзор трекеров, Gantt, графов и whiteboard-инструментов Как команда становится AI Native: методология из 4 этапов Как дебажить distroless-контейнер в Kubernetes без shell: ephemeral containers на практике ИИ не автоматизировал разработчиков. Он сделал кое-что хуже Как оплатить обучение за границей из России в 2026 году: способы, цены, рейтинги Сложный поиск альтернативных частиц Хиггса Тест батареек Camelion Plus Почему компании строят свои конструкторы баннеров: разбор паттерна, который никто не называет Структурированное логирование и трейсинг в Node.js: @cleverbrush/log и @cleverbrush/otel Странные образования на поверхности Венеры ставят в тупик планетологов Шифрование на уровне протокола Пять самых крупных ошибок, которые допускают компании при внедрении SRE Приложения для Битрикс24, которые реально экономят время Анатомия Claude Code. Первичный анализ и наполнение контекста Как изменились требования к разработчикам в эпоху AI: опыт техлида Распродажа «Большой Пятерки» в PlayStation – Days Of Play GIT: Как ломать и чинить историю правильно Разбираемся в ML без воды: от базы до Attention. Часть 6: Логистическая регрессия Решето как гипотетический контейнер для жидких субстанций Лучшие нейросети для генерации изображений — как создать картинку с помощью ИИ в 2026 году Волны гасят ветер: во что упирается развитие ИИ в теории длинных волн Кондратьева Почему на самом деле нельзя делить на ноль? Физический и аксиоматический подходы Zero Trust для подрядного доступа: четыре слоя Identity, Device, Access и Monitoring Zero Trust для подрядного доступа: четыре слоя Identity, Device, Access и Monitoring Базовый командный runtime для терминальных AI-агентов Спектр. Контекст создания, трудности, боли и победы Задолбал нейрослоп: честный разбор, почему мы не можем без него C3D Converter: Plug and Play Почему технические директора не проходят в CIO: портфель проектов против навыка «докрутить» SaaS умирает? Я сравнил 8 публикаций Q1 2026 с тем, что вижу внутри Kaiten Взрослый BIM для детского сада на 230 мест: крупнейший застройщик Черноземья ОДСК – сделал комплексный проект в nanoCAD Privacy-by-design: что наш edge не пишет на диск и почему это сложнее, чем кажется Civilization VII: что изменилось в механике смены эпох после патча 1.4.0 Готовые решения для интернет-магазина на 1С-Битрикс: разбираю рынок изнутри На РОИ появились инициативы с требованием ограничения полномочий РКН и блокировок Фаундер написал 15 страниц про рынок и поднял на этом $10M Формула интегрирования по частям с точки зрения дифференциальной геометрии Plan-tango: как я перестал гонять план между Claude Code и Codex руками Динозавры в проде: сколько лет языкам программирования и кто до сих пор зарабатывает на «мёртвых» Как стать postgres в чужом облаке: краш-тест безопасности управляемых БД Погружение в новый проект: как не потерять месяц жизни Простой гайд по Kling Motion Control от А до Я Семантический слой: архитектура, подходы и роль в эпоху AI-аналитики Гоняться за оптовиками и чуть не закрыться, придумать «стартовый набор новичка» и удвоить выручку НЕкурс про разработку безопасного программного обеспечения (РБПО) Теология возможных миров. Есть ли боги в мультивселенной, или мультивселенная и есть Бог? Что делать, если не прошли переаккредитацию ИТ-компании в 2026 году: пошаговый план действий Нейросеть для работы с текстом — как генерировать чистый и уникальный текст для студентов Прокачать SQLite и сократить векторы в видеоформате — открытые инструменты для работы с эмбеддингами Киберзадачи в сеттинге Minecraft. Школьники в финале ВсоШ по инфобезу Windows 11 будет работать быстрее на всех компьютерах. Теперь официально Кэширование в Symfony: как мы сломали авторизацию и починили ее через Lock Стажеры uAcademy*. Опыт кураторства дипломов: почему стажировок недостаточно Команда выросла, методы — остались «Ошибка выжившего» на примере спортсменов Испытание временем — как тестировать цифровой двойник, если физического объекта ещё не существует Как обычный кухонный таймер на ESP32 превратился в домашний центр уведомлений Как мы научили СХД TATLIN.OBJECT мигрировать данные из S3-хранилища MinIO Онлайн-приключение для IT-команд, как альтернатива корпоративу в Zoom Экскурсия по «зоопарку» сетевого трафика: топ-10 аномалий внутри вашего периметра Книга: «System Design. Проектирование мобильных систем. Подготовка к сложному интервью» Критическое мышление руководителя: как один красивый слайд может привести к дорогой ошибке Ecommerce на Laravel, или как мы собрали headless-слой для фронтов (6 часть) Обновление macOS для инженеров поддержки Делаем ностальгический фильмоскоп на Raspberry Pi Zero 2 W От баз данных до инструментов для ИИ-экосистем: проекты, которые получили гранты Yandex Open Source Больше, чем просто безопасность, или Зачем контролировать зависимости Тот неловкий момент, когда письмо от Джованни из Швейцарии не оказалось обманом Почему AAA-игры проваливаются? Разбираем примеры Как запустить 3D-приложение на сервере без GPU: от SwiftShader до WARP Благоустраиваем Firefox: встроенный VPN Современный Angular: Заменяем жизненные циклы на сигналы HR-бот на базе RAG: архитектура корпоративной базы знаний для ресторанного холдинга Почему ИИ не заменит аналитика при подготовке технического задания InSales без пушей: как бесплатно перенести уведомления о заказах в Telegram на Yandex Cloud Serverless Александрийская библиотека: краткая история античной системы хранения Почему японские компании занимаются всем подряд Откуда берутся молнии? Ответ на этот вопрос становится всё интереснее 1C Code Bench — бенчмарк для оценки способности LLM писать код на 1С
Зарядка для джависта
flaz14 · 2026-05-29 · via Все публикации подряд на Хабре

Зарядка для джависта

Уровень сложностиПростой

Время на прочтение5 мин

Охват и читатели0

Туториал

Как-то раз на одном из программистских форумов я наткнулся на интересную задачку. Интересна она была тем, что требовалось рекурсивное решение на Java. Мне захотелось разобраться…

Условие

Написать обобщённый метод, который принимает в качестве аргумента коллекцию объектов, обходит её и модифицирует следующим образом:

  1. Если в коллекции встречается только один объект, оставить его как есть.

  2. Если в коллекции идут подряд два одинаковых объекта и более, удалить их все и поместить на это место null.

  3. Метод должен быть рекурсивным.

Примеры:

  • [1, 2, 2][1, null]

  • ["dog", "cat", "cat", "fish"]["dog", null, "fish"]

  • [1, 2, 1, 3, 1][1, 2, 1, 3, 1]

Процедура или функция?

Очевидно, что настоящий метод (то есть предназначенный для вызова на объекте) нам не понадобится. Хватит статического. По сути это не метод даже, а процедура. Или не процедура? Сложно сказать…

Коллекцию можно обойти только с помощью итератора (под коллекцией понимаем интерфейс java.util.Collection, а не реализацию, например, java.util.ArrayList). Единственная доступная операция изменения – удаление элемента. Причём читать, модифицировать и снова читать нельзя, даже в одном потоке. Соблазнились с помощью одного итератора обходить коллекцию, а с помощью второго менять её? Тоже не получится!

Пусть мы каким-то образом преодолели это ограничение (на самом деле, способ есть, но он не решает принципиальной проблемы, о которой чуть ниже). Мы удалили из исходной коллекции несколько одинаковых идущих подряд элементов. Как мы добавим в коллекцию наш заместитель, то есть null?

Итераторы такой возможности не предоставляют. А метод add() интерфейса Collection нам не поможет. В случае списка, новый элемент добавляется в конец. В случае множества, зависит от реализации. Так, у LinkedHashSet – в конец, у HashSet – неизвестно куда.

Править исходную коллекцию «на лету» не получится. Значит, процедура не годится, нужна функция.

Уточнённое условие

Требуется написать обобщённую функцию, которая принимает в качестве аргумента коллекцию объектов, обходит её и складывает элементы в список таким образом, что:

  1. Если в исходной коллекции встречается только один объект, добавить его в выходной список.

  2. Если в исходной коллекции идут подряд два одинаковых объекта и более, добавить в выходной список одно значение null.

  3. Функция должна быть рекурсивной.

Среди элементов исходной коллекции не должно быть null. Попытка сравнить такое значение с другим потерпит неудачу, а именно приведёт к NullPointerException. Элементы сравниваются посредством метода equals(), а не оператора сравнения. К слову, null в Java – такое же значение, как и все остальные (в отличие от SQL, например).

По отношению к множеству задача вообще не имеет смысла, ибо множество не содержит дубликатов. Множество в качестве исходной коллекции – это вырожденный случай.

Цикл

Решение с циклом проще, чем с рекурсией. Так что рассмотрим сначала его.

LoopSolution.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class LoopSolution {
    public static <T> List<T> cleanCollection(Collection<T> input) {
        if (input.size() < 2)
            return List.copyOf(input);

        List<T> output = new ArrayList<>();

        Iterator<T> tail = input.iterator();
        T previousItem = tail.next();
        int duplicatesCount = 0;
        do {
            T currentItem = tail.next();
            if (currentItem.equals(previousItem)) {
                duplicatesCount++;
            } else {
                T mergedItem = merged(duplicatesCount, previousItem);
                output.add(mergedItem);
                previousItem = currentItem;
                duplicatesCount = 0;
            }
        } while (tail.hasNext());

        T lastItem = merged(duplicatesCount, previousItem);
        output.add(lastItem);

        return output;
    }

    private static <T> T merged(int duplicatesCount, T previousItem) {
        return duplicatesCount == 0
                ? previousItem
                : null;
    }
}

Запоминаем первый элемент. Затем идём по коллекции до тех пор, пока не встретится значение, отличное от первого. Заодно подсчитываем число дубликатов. Благодаря счётчику становится ясно, нужно ли объединять одинаковые элементы в null или нет. Добавляем результат в выходной список, берём текущий элемент в качестве первого и мотаем цикл дальше.

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

Рекурсия

Рекурсивное решение похоже на циклическое. Просто к той части исходной коллекции, которая осталась после смены значений, функция применяется ещё раз. Последний «кусочек» обрабатывается отдельно, но несколько иначе по сравнению с циклом.

Поскольку рекурсивная функция принимает в качестве аргументов предыдущее значение и итератор, для удобства использования она «завёрнута» в обычную.

RecursiveSolution.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;

public class RecursiveSolution {
    public static <T> List<T> cleanCollection(Collection<T> input) {
        if (input.isEmpty())
            return emptyList();

        Iterator<T> tail = input.iterator();

        return cleanTail(
                tail.next(),
                tail);
    }

    private static <T> List<T> cleanTail(T previousItem,
                                         Iterator<T> tail) {
        if (!tail.hasNext())
            return singletonList(previousItem);

        int duplicatesCount = 0;
        do {
            T currentItem = tail.next();
            if (currentItem.equals(previousItem)) {
                duplicatesCount++;
            } else {
                List<T> output = new ArrayList<>();

                T mergedItem = duplicatesCount == 0
                        ? previousItem
                        : null;

                output.add(mergedItem);

                List<T> remainingItems = cleanTail(currentItem, tail);
                output.addAll(remainingItems);

                return unmodifiableList(output);
            }
        } while (tail.hasNext());

        return singletonList(null);
    }
}

Заключение

Хорошая задача мне попалась: наполовину алгоритмическая, наполовину Java-специфическая. Самое то, чтобы потренироваться.

P.S.

Тесты для обоих решений.

EverySolutionTest.java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;

public class EverySolutionTest {

    @ParameterizedTest
    @MethodSource("data")
    void loopSolutionTest(List<Integer> expected, List<Integer> input) {
        Collection<Integer> actual = LoopSolution.cleanCollection(input);
        assertEquals(expected, actual);
    }

    @ParameterizedTest
    @MethodSource("data")
    void recursiveSolutionTest(List<Integer> expected, List<Integer> input) {
        Collection<Integer> actual = RecursiveSolution.cleanCollection(input);
        assertEquals(expected, actual);
    }

    static Stream<Arguments> data() {
        return Stream.of(
                arguments(
                        emptyList(),
                        emptyList()),

                arguments(
                        singletonList("a"),
                        singletonList("a")),

                arguments(
                        singletonList(null),
                        asList("42", "42")),

                arguments(
                        asList(1, null),
                        asList(1, 2, 2)),

                arguments(
                        asList(1, 2, 1, 3, 1),
                        asList(1, 2, 1, 3, 1)),

                arguments(
                        asList(null, null, null),
                        asList(1, 1, 2, 2, 3, 3)),

                arguments(
                        asList(2, null, 3),
                        asList(2, 4, 4, 3)));
    }
}