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

推荐订阅源

A
About on SuperTechFans
D
DataBreaches.Net
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
S
Secure Thoughts
Y
Y Combinator Blog
The Last Watchdog
The Last Watchdog
L
LINUX DO - 最新话题
V2EX - 技术
V2EX - 技术
腾讯CDC
GbyAI
GbyAI
G
Google Developers Blog
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Microsoft Security Blog
Microsoft Security Blog
Jina AI
Jina AI
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
MyScale Blog
MyScale Blog
Help Net Security
Help Net Security
K
Kaspersky official blog
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
AI
AI
MongoDB | Blog
MongoDB | Blog
Scott Helme
Scott Helme
J
Java Code Geeks
Engineering at Meta
Engineering at Meta
H
Heimdal Security Blog
H
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
S
Security Affairs
TaoSecurity Blog
TaoSecurity Blog
The GitHub Blog
The GitHub Blog
Hacker News: Ask HN
Hacker News: Ask HN
Martin Fowler
Martin Fowler
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Last Week in AI
Last Week in AI

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Перевополщение Stable Values в JDK 26
Spring АйО · 2026-06-01 · via Все публикации подряд на Хабре

9 мин

8.6K

Ленивость

Когда мы говорим о «ленивости» (lazy) в программировании, речь не только о разработчиках, которые «ленятся», пока компилируется код, или о том, как наш Claude громит кодовую базу. Ленивость — это ещё и откладывание вычислений. Откалывать вычисления это реально бывает полезно, ведь

  • Вычисление может быть долгим, а может быть, нам вообще не придётся его выполнять

  • Оно может иметь лучший результат, если мы выполним его позже, имя больше информации

В рантайме Java многие процессы ленивые — назову лишь два примера:

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

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

Комментарий от Михаила Поливаха

В защиту Spring Framework скажу, что Spring Boot инициализирует DispatcherServlet лениво по‑умолчанию, а именно наш веб слой (@RestController и так далее) — вот его инциализиуерт eagerly.

У этого, кстати, есть причина. Причина такая, что, с одной стороны, хочется иметь более быстрый старт Spring Boot приложения, но с другой стороны, у eager инициализации есть важное преимущество — все проблемы будут известны ещё при старте, а не в рантайме.

Поэтому, Spring Boot делает так, что самую тяжелую часть веб слоя (DispatcherServlet и так далее) — это всё инфраструктурный код, в котором создатели framework‑а уверены, вот он поднимается лениво. А вот ваш веб слой, который они не контролируют, вот он поднимается eagerly, чтобы все проблемы при инициализации бинов сразу вскрылись.

Это такой некий баланс, между скоростью запуска и надежностью приложения.

И не всегда понятно, какой вариант правильный, потому что, как и большинство вещей в программировании, ленивость приносит компромиссы. Не делать ничего, пока это точно не нужно, означает, что вы можете получить лучший результат или сделать меньше работы — и то и другое хорошо. Но выполнение «on‑demand» может привести к тому, что конкретный запрос, например, будет выполняться дольше — а это плохо.

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

  • код становится сложнее, и его трудно надёжно сделать «правильно», особенно при конкуренции потоков. Т.е. условно проинициализировать поле лениво в рамках конкурецнии можно, но немного геморно.

  • Ленивая инициализация может мешать использовать ключевое слово final, что делает код более хрупким и хуже оптимизируемым, потому что поле можно переназначить позже

Чтобы было понимание, о чем речь, взгляните на пример:

public class UserController {

	private volatile LoginService login;

	public UserController() {
		// field `login` is lazily initialized
		// by `getLogin` instead of the
		// constructor;
		// that also means, you can never use
		// `login` directly as it may be null
	}

	// one variant to initialize lazily,
	// called "double-checked locking"
	private LoginService getLogin() {
		var login = this.login;
		if (login == null) {
			synchronized (this) {
				login = this.login;
				if (login == null)
					this.login = login =
						LoginService.initialize();
			}
		}
		return login;
	}

}

Так что, чтобы уравнять шансы, Java не помешал бы API, который делает ленивую инициализацию простой и даёт нам — и рантайму — гарантию, что значение, однажды вычисленное и присвоенное, остаётся константным. На сцену выходит JDK Enhancement Proposal 526 и lazy constant‑ы.

LazyConstant

JEP 526 предлагает новый тип LazyConstant. Вместо того чтобы создавать final‑поле типа T и вычислять его значение при конструировании, вы объявляете final‑поле типа LazyConstant и создаёте его с «рецептом» вычисления. Для этого вызываете его статический фабричный метод of с Supplier. А позже, когда вам нужно значение, просто вызываете LazyConstant.get:

public class UserController {

	private final LazyConstant<LoginService> login;

	public UserController() {
		this.login = LazyConstant.of(LoginService::initialize);
		// using `login` later...
		IO.println(login.get());
	}
}

И это весь API. Ну, по крайней мере, это будет всё API в JDK 27 — но об этом чуть позже, когда будем обсуждать эволюцию. А пока давайте внимательнее посмотрим на «lazy» и «constant».

Как вы наверняка уже поняли, supplier, с которым вы создали lazy constant, выполняется, чтобы вычислить значение, которое возвращает get. Но — максимум один раз, при первом вызове get. Если несколько вызовов происходят параллельно, supplier выполняет только один поток, а все остальные ждут результат; и, разумеется, все последующие вызовы get просто возвращают тот же результат. Так решается сложность «инициализировать поле лениво максимум один раз», даже при конкуренции.

Но! Со стороны кажется, что большинство опытных Java‑разработчиков могут написать такой Lazy тип и сами. Тем не менее, тут есть нюансы.

Отличие LazyConstant — не в «lazy», а в «constant». Потому что после вычисления значение присваивается полю, помеченному аннотацией @Stable, которая сообщает рантайму Java, что поле никогда не будет переназначено; что оно константно. То есть ваша ссылка на lazy constant — final, а его ссылка на значение — константная, и это открывает дверь оптимизации под названием constant folding, когда цепочку константных ссылок можно «свернуть» до одной загрузки.

К сожалению, как вы, возможно, помните из Inside Java Newscast #101, рефлексия может менять final‑поля экземпляров у обычных классов, так что они на самом деле не константны — пока что константами являются только final static‑поля, компоненты record, final‑поля экземпляров в hidden class‑ах, и теперь — значения LazyConstant. Но как только «суперспособности» рефлексии будут ограничены опцией командной строки, все final‑поля станут константными, что заметно расширит пространство для этой оптимизации.

Поведение LazyConstant

Окей, «быстрый раунд» по нескольким свойствам поведения:

  • LazyConstant не сериализуем

  • LazyConstant отвергает null, так что не заставляйте supplier возвращать его.

  • Если Supplier выбрасывает исключение, оно выйдет наружу из get, и если вы попробуете снова позже, Supplier будет вызван снова — возможно, в этот раз повезёт. Так что технически это не «максимум один раз», а «максимум один раз успешно».

  • Если Supplier в итоге вызывает get по циклу, вы в беде, но LazyConstant замечает это и прерывает вероятно бесконечный цикл, выбрасывая IllegalStateException.

  • Если Supplier блокируется навсегда, вы в очень большой беде, потому что ни поток, который его выполняет, ни потоки, ожидающие результата, из этого не выйдут. API не предлагает ни таймаутов, ни отмены.

  • LazyConstant очень строго стоит на «lazy» и не хочет вычислять значение при вызове equals, поэтому сравнивать остаётся только identity (то есть double equals проверка) самого LazyConstant. И даже если бы он захотел вычислять значения (а сейчас мы увидим связанный случай, где это требуется), поведение становится очень быстро трудно интуитивным. Если хотите это разыграть — оставьте комментарий, и мы обсудим.

Ленивые коллекции

LazyConstant даёт связь 1:1 между владеющим классом и нужным значением, но что если вам нужно 1:n? Можно, конечно, объявить LazyConstant от списка, но тогда весь список придётся вычислять сразу:

private final LazyConstant<List> list = LazyConstant.of(() -> List.of("0", "1", "2"))

Во многих случаях это, вероятно, нормально, но в других — не очень. Поэтому JEP 526 также предлагает «ленивый» список и «ленивую» карту, но они не выставлены в системе типов. Вместо этого вы вызываете List.ofLazy и Map.ofLazy и получаете экземпляр List или Map соответственно, который реализует ленивость «под капотом».

private final List list = List.ofLazy(3, index -> "" + index);
private final Map<Integer, String> map = Map.ofLazy(Set.of(0, 1, 2), key -> "" + key);

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

Как и ожидается, функции выполняются ровно один раз — когда элемент по данному индексу или ключу впервые понадобился, — даже при конкурентном доступе. Помимо вычисления on‑demand, эти коллекции неизменяемы, а рантайм может применять оптимизации constant folding к коду, который обращается к содержимому lazy constants через ленивые коллекции.

Комментарий от Михаила Поливаха

Для тех, кто Николая не понял. Речь о том, что использовать LazyConstant<List> конечно можно, но в таком случае, как только Вам понадобиться из этого List элемент под индексом, например 3, то вы LazyConstant вычислит весь List, а не только элемент под индексом 3. Аналогичная проблема и с Map‑ой если делать LazyConstant<Map>

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

Ещё раз, Вы могли бы написать такое сами, но если напишите, то HotSpotVM не сможет делать на вашем кастомном типе Constant Folding.

Подобная фича может быть удобна для тривиальных in‑memory cache‑ей, которым не нужна особая стратегия eviction‑а, поддержки конерентности кеша и так далее

Теперь про equals: эти коллекции не могут быть столь же равнодушны, как LazyConstant, потому что и List, и Map требуют корректной реализации, а она может потребовать вычисления некоторых значений. Каких именно? Всех — если два списка или карты действительно равны...

var eagerList = List.of("0", "1", "2");
var lazyList = List.ofLazy(
	3,
	index -> {
		IO.println("Computing " + index);
		return "" + index;
	});

IO.println(eagerList.equals(lazyList));

/*--< OUTPUT >--*/
// Computing 0
// Computing 1
// Computing 2
// true

.. но, возможно, меньшего числа, если они не равны. Я заглянул в исходники и мог бы рассказать, что и когда происходит, но Javadoc это поведение не специфицирует, а значит, это деталь реализации — полагаться на неё было бы ошибкой. Так что не буду. Буду принимать аплодисменты за самообладание в комментариях.

Эволюция API

Дайте я включу вам короткий фрагмент из разговора, который у меня был с Джоном Роузом, старшим архитектором виртуальной машины Java.

У нас в JDK есть нечто под названием «stable values», и мы используем их буквально повсюду. Со временем эти штуки становились всё полезнее и полезнее, но оставались «упакованными» внутри JDK — и это был необходимый шаг, чтобы поначалу иметь решение только для JDK: мы называем это «для своих», для друзей и семьи, — пока мы учимся правильно с этим обращаться и учим VM корректно их оптимизировать. [Per‑Ake Minborg] рассказал о своём «StableValue API», построенном поверх этих стабильных переменных, и в итоге мы наконец разобрались: (1) как их оптимизировать и (2) как их причесать и довести до состояния, подходящего для приличного общества, чтобы выставить их на улицу — на углу — а не держать только у себя в гостиной.

И эволюция на этом не остановилась. Как объяснил Джон, в JVM есть концепция stable values, которые помечаются упомянутой выше аннотацией @Stable. Первым шагом к подъёму этого в API для нас стали stable values в JDK 25. Обратите внимание: название было явно основано на низкоуровневой концепции, а API включало фабричные методы коллекций и много императивной функциональности.

Для JDK 26 имя сменили на более понятное для нас, и по мере того, как концепция оформлялась, стало ясно, что ленивые коллекции в первую очередь — не про ленивость, а про коллекции, поэтому их фабричные методы переехали туда. А по мере прояснения концепции из API убрали большую часть императивного «хлама». Впрочем, не всё. В JDK 26 вы всё ещё можете спросить у LazyConstant, инициализирован ли он, и у вас всё ещё есть метод orElse, позволяющий обработать случай, когда он не инициализирован. Но это отвлекает от целевого сценария использования, и поэтому JDK 27, скорее всего, уберёт эти методы — вот почему мы не обсуждали их раньше.

По мере того как lazy constants становятся чётко определённой концепцией, остаётся своего рода вакуум для более низкоуровневого взаимодействия с аннотацией @Stable — и, вероятно, что‑то его заполнит. А когда это случится, мы, конечно, расскажем об этом в Inside Java Newscast, так что подписывайтесь, если ещё не подписались.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.