На первый взгляд, выбор правильного Java-рантайма для вашего проекта на Spring Boot может показаться тривиальным. В конце концов, все популярные рантаймы основываются на коде OpenJDK и предлагают одинаковые программные интерфейсы.
Но не все рантаймы реализованы одинаково. В этой статье мы обсудим различные показатели, которые могут повлиять на ваше решение выбрать определенный дистрибутив Java для Spring Boot приложения.
🔗 https://habr.com/ru/companies/spring_aio/articles/819899/
#JVM #Java #SpringBoot
Please open Telegram to view this post
VIEW IN TELEGRAM
Не так давно в Java была добавлена интересная возможность для конструкторов. Теперь в них можно размещать операторы перед явным вызовом другого конструктора, такого как
super(..)
или this(..)
. Эта фича была предложена в JEP 447 и впервые представлена в качестве preview-фичи в JDK 22. Ранее в Java вызов конструктора суперкласса (
super(..)
) или другого конструктора текущего класса (this(..)
) должен был быть первой строкой в теле конструктора. Другими словами, до вызова конструктора нельзя было выполнять никакие проверки или инициализации:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(value); // Вызов конструктора суперкласса должен быть первым
if (value <= 0) throw new IllegalArgumentException("Value must be positive");
}
}
Начиная с Java 22 мы можем сначала выполнять проверки или инициализацию, и только затем обращаться к конструктору суперкласса:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0) throw new IllegalArgumentException("Value must be positive");
super(value);
}
}
Благодаря этой фичи мы можем улучшить читаемость и поддержку кода, размещая логику валидации и подготовки аргументов там, где нам это действительно нужно.
Но у этой фичи есть некоторые ограничения. Например, мы не можем ссылаться на создаваемый экземпляр и инициализировать его поля до обращения к
super(..)
или this(..)
.
class A {
int i;
A() {
this.i++; // Ошибка
this.hashCode(); // Ошибка
System.out.print(this); // Ошибка
super();
}
}
В Java 23 эту фичу предлагают протестировать повторно с одним значительным изменением: позволить телу конструктора инициализировать поля класса до явного вызова другого конструктора. Про это мы расскажем в следующей части этого поста.
Ставьте 🔥 если хотите вторую часть!
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
Не так давно мы рассказывали про гибкие конструкторы в Java. В 23 версии Java появится возможность инициализировать поля в том же классе перед явным вызовом конструктора (JEP-482). А пока эта фича не реализована, мы вынуждены использовать вспомогательные методы, если нам необходимо проиницизировать поля.
Рассмотрим класс, который принимает аргумент типа
Certificate
и должен преобразовать его в массив байтов для конструктора суперкласса.Java ≤ 22:
public class Sub extends Super {
private static byte[] prepareByteArray(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null) throw new IllegalArgumentException(..);
return switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
default -> ...
};
}
public Sub(Certificate certificate) {
super(prepareByteArray(certificate));
}
}
В коде выше метод
prepareByteArray
выполняет необходимую подготовку аргументов. Затем этот метод вызывается внутри конструктора Sub
как часть вызова super
.Java ≥ 23:
Можно будет сделать тоже самое, но без дополнительных “приседаний” в виде вынесения логики которую мы хотим выполнить в конструкторе в отдельный метод:
public class Sub extends Super {
public Sub(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null) throw new IllegalArgumentException(..);
byte[] certBytes = switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
default -> ...
};
super(certBytes);
}
}
Также иногда возникает необходимость передать одно и то же значение в несколько аргументов конструктора суперкласса. Ранее это можно было сделать только с использованием вспомогательного конструктора.
Рассмотрим пример, связанный с системой регистрации студентов в университете. У нас есть базовый класс
Person
, который представляет человека, и класс Student
, который наследует Person
. Каждый студент имеет идентификатор, который также используется в качестве номера студенческого билета и учетной записи в университетеJava ≤ 22:
class Person {
private String id;
private String accountId;
public Person(String id, String accountId) {
this.id = id;
this.accountId = accountId;
}
}
class Student extends Person {
private Student(String id) {
super(id, id); // Передача одного и того же значения дважды
}
public Student(String firstName, String lastName) {
this(generateStudentId(firstName, lastName));
}
private static String generateStudentId(String firstName, String lastName) {
// Генерация уникального идентификатора студента
return firstName + ", " + lastName + "-" + System.currentTimeMillis();
}
}
В этом примере вспомогательный конструктор
Student(String id)
используется для передачи одного и того же id
в два аргумента конструктора суперкласса Person
.Java ≥ 23:
class Person {
private String id;
private String accountId;
public Person(String id, String accountId) {
this.id = id;
this.accountId = accountId;
}
}
class Student extends Person {
public Student(String firstName, String lastName) {
String id = generateStudentId(firstName, lastName);
super(id, id); // Передача одного и того же значения дважды
}
private static String generateStudentId(String firstName, String lastName) {
// Генерация уникального идентификатора студента
return firstName + ", " + lastName + "-" + System.currentTimeMillis();
}
}
Ставьте 🔥 если понравился пост и вы были бы рады видеть больше разборов JEP
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
Судя по реакциям на предыдущих постах про JEP, они вам нравятся, так что продолжаем разбирать свежые JEP'ы.
Представим себе небольшой веб-фреймворк, который обрабатывает HTTP-запросы. Фреймворк создает контекст для каждого запроса и передает его через методы:
@Override
public void handle(Request request, Response response) {
var userInfo = readUserInfo();
}
private UserInfo readUserInfo() {
return (UserInfo) framework.readKey("userInfo", context);
}
Ранее для передачи контекста использовались переменные типа
ThreadLocal
:
private final static ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>();
void serve(Request request, Response response) {
var context = createContext(request);
CONTEXT.set(context);
Application.handle(request, response);
}
public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}
Scoped Values API впервые был представлен в JDK 20 и прошел несколько итераций и улучшений в последующих версиях JDK (JEP 429, JEP 446, JEP 464, JEP 481). Scoped Values представляют собой новый механизм для передачи неизменяемых данных между методами в одном потоке и между дочерними потоками. Этот механизм проще в использовании по сравнению с
ThreadLocal
и обладает меньшими затратами по времени и памяти.У подхода, использующего
ThreadLocal
, есть несколько недостатков:1. Неограниченная изменяемость — переменные могут изменяться в любое время любым кодом в потоке
2. Неограниченное время жизни — переменные могут существовать дольше, чем необходимо, что может приводить к утечкам памяти
3. Высокая стоимость наследования — при создании дочерних потоков переменные должны копироваться, что увеличивает затраты по памяти
Scoped Values позволяют избежать этих проблем, обеспечивая одноразовую запись и ограниченное время жизни значений:
final static ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance();
void serve(Request request, Response response) {
var context = createContext(request);
ScopedValue.runWhere(CONTEXT, context, () -> Application.handle(request, response));
}
public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}
В данном случае метод
ScopedValue.runWhere
связывает значение с текущим потоком на время выполнения лямбда-выражения, после чего связь уничтожается, что улучшает производительность и безопасность кода.В 23 версии Java фича будет пересмотрена повторно с одним изменением: тип параметра операции метода
ScopedValue.callWhere
будет являться новым функциональным интерфейсом, который позволит компилятору Java делать вывод о том, может ли быть выброшено проверяемое исключение. Подробнее про ScopedValue.callWhere
и улучшения, связанные с ним, поговорим во второй части.Ставьте 🔥 если хотите вторую часть!
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
Недавно мы рассказывали про Scoped Values. Одной из ключевых особенностей Scoped Values (JEP-481) является отсутствие метода
set
, в отличие от ThreadLocal
, что позволяет гарантировать, что после привязки значения к run
/call
методам конкретных Runnable
и Callable
, оно не может быть в них изменено:
private static final ScopedValue<String> X = ScopedValue.newInstance();
void foo() {
ScopedValue.runWhere(X, "hello", () -> bar()); //привязываем значение "hello" для метода bar()
}
void bar() {
System.out.println(X.get()); // "hello"
ScopedValue.runWhere(X, "goodbye", () -> baz()); //привязываем значение "goodbye" для метода baz()
System.out.println(X.get()); // "hello"
}
void baz() {
System.out.println(X.get()); // "goodbye"
}
1. В методе
foo()
устанавливаем значение "hello" для области видимости внутри метода bar()
2. В методе
bar()
запрашиваем значение. Получаем "hello"3. В методе
bar()
устанавливаем значение "goodbye" для области видимости внутри метода baz()
4. В методе
bar()
запрашиваем значение. Получаем "hello". Тем самым убеждаемся, что после установки значения для области видимости внутри метода baz()
оно не поменялось для метода bar()
5. В методе
baz()
запрашиваем значение. Получаем "goodbye".Важно отметить, что привязка "goodbye" действует только внутри вложенной области видимости. После завершения выполнения метода
baz()
значение X
в bar()
возвращается к "hello". Таким образом, тело метода bar()
не может изменить привязку, видимую этим методом, но может изменить привязку, видимую его вызываемыми методами. После завершения метода foo()
X
возвращается в состояние без привязки. Такое вложение гарантирует ограниченный срок действия для совместного использования нового значения.Scoped Values API также предоставляет метод
callWhere
, который позволяет получить результат выполнения:
var result = ScopedValue.callWhere(X, "hello", () -> bar());
В этом случае
callWhere
выполняет код внутри области видимости, где X
привязан к значению "hello", и возвращает результат выполнения.Подводя итог, складывается ощущение, что нововведение предоставляет более безопасный и эффективный способ передачи данных между потоками по сравнению с использованием
ThreadLocal
. Однако с уверенностью можно будет об этом говорить только после того, как фича выйдет из preview и войдет в состав языка на постоянной основе.Ставьте 🔥 если разбор JEP был полезен и вы хотели бы видеть больше подобных обзоров
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
Уже через 7 часов на YouTube канале Java пройдет прямая трансляция, посвящённая релизу Java под номером 23.
Расписание трансляции (время GMT+3 МСК):
Deep Dive into Java 23's Features!
16:00: Welcome and event agenda
16:05: Java Language Update by Gavin Bierman
16:20: Java Language Features in Action
16:35: Integrity by Default with Ron Pressler
16:55: Markdown Documentation Comments with Jonathan Gibbons
17:10: ZGC: Generational Mode by Default with Stefan Karlson
Java Outreach in 2024
17:25: Java Playground Updates with Denys Makogon and Heather Stephens
17:40: Java Community Report from Sharat Chander and Jim Grisanzio
Java Preview Features and Future work
18:10: Sneak Peek at GraalVM Updates with Alina Yurenko and Fabio Niephaus
18:30: Class-File API with Brian Goetz
18:45: State of Project Leyden with John Rose
18:15: Enjoy Your Evening!
Также про JEP'ы, включенные в этот релиз, компания JetBrains записала целую серию видео. Смотреть можно здесь:
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
Java 23 - Launch Stream
Come celebrate the #Java 23 launch with us on September 17th from 13:00 to 16:30 UTC. We will cover new features, community updates, and what to expect from future releases. We'll have presentations from exciting guests like Brian Goetz, Ron Pressler, Gavin…
С выходом Java 23 произошли важные изменения в формате написания комментариев — теперь они могут быть написаны с использованием
Markdown
. Ранее разработчики использовали HTML
и JavaDoc
-теги для оформления таких комментариев, что могло создавать сложности, особенно для тех, кто не знаком с HTML
. Теперь, благодаря поддержке Markdown
, комментарии станет писать проще и удобнее.Зачем это нужно?
HTML
с течением времени стал менее популярен для ручного написания. Сегодня чаще используют языки разметки, ориентированные на удобство разработчиков. Использование HTML
для JavaDoc
создавало трудности, так как он тяжеловесен и сложен для быстрого написания, особенно для новых разработчиков.Преимущества использования Markdown
Markdown
стал популярным благодаря своей простоте и удобочитаемости. Он идеально подходит для написания JavaDoc
, которая обычно состоит из текстовых блоков, списков, ссылок и выделенного текста. Вместо сложных HTML
-тегов можно использовать гораздо более лаконичные конструкции Markdown
.Вот так выглядит комментарий к методу
hashCode
с использованием HTML
и JavaDoc
-тегов:
/**
* Возвращает хэш-код для объекта.
* <p>
* Общий контракт метода {@code hashCode}:
* <ul>
* <li>Если метод вызывается несколько раз на одном объекте в ходе выполнения программы,
* он должен возвращать одно и то же значение, если данные объекта не изменялись.
* <li>Если два объекта равны, вызов метода {@code hashCode} должен вернуть одинаковое значение.
* <li>Если объекты не равны, метод может возвращать одинаковые хэш-коды, но этого следует избегать.
* </ul>
* @return хэш-код для объекта.
*/
Этот же комментарий в
Markdown
:
/// Возвращает хэш-код для объекта.
///
/// Общий контракт метода `hashCode`:
///
/// - Если метод вызывается несколько раз на одном объекте в ходе выполнения программы,
/// он должен возвращать одно и то же значение, если данные объекта не изменялись.
/// - Если два объекта равны, вызов метода `hashCode` должен вернуть одинаковое значение.
/// - Если объекты не равны, метод может возвращать одинаковые хэш-коды, но этого следует избегать.
///
/// @return хэш-код для объекта.
Для использования
Markdown
введён новый формат комментариев: строки начинаются с трёх слэшей (`///`), вместо традиционного синтаксиса /** ... */. Основные различия:
- Пробел между абзацами обозначается пустой строкой, как и в
Markdown
.- Списки создаются с помощью маркеров - вместо
HTML
-тегов <ul> и <li>.- Подчёркивание (`_`) используется для выделения текста, вместо
HTML
-тегов <em>.- Теги вроде {@code} заменяются обратными апострофами (\`...\`).
В то же время, важные теги, такие как @param и @return, сохраняются и могут использоваться вместе с
Markdown
-разметкой.🔗 С полным описанием JEP можно ознакомиться по ссылке: https://openjdk.org/jeps/467
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
@ManyToMany
Использовать
CascadeType.ALL
для @ManyToMany
не рекомендуется, так как это может привести к непредсказуемым результатам во время удаления JPA сущностей. Вместо этого следует использовать CascadeType.DETACH
, CascadeType.MERGE
, CascadeType.PERSIST
и CascadeType.REFRESH
. Подробнее об этом рассказано в отдельном видео!
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
Никогда не используй CascadeType.ALL вместе с @ManyToMany | Amplicode
#Amplicode #Spring #SpringBoot #SpringData #JPA #Hibernate #IntelliJ #Java #Kotlin
Использовать CascadeType.ALL для @ManyToMany не рекомендуется, так как это может привести к непредсказуемым результатам во время удаления JPA сущностей. Вместо этого следует…
Использовать CascadeType.ALL для @ManyToMany не рекомендуется, так как это может привести к непредсказуемым результатам во время удаления JPA сущностей. Вместо этого следует…
С выходом JDK 24 Stream API станет еще мощнее благодаря новому инструменту — Stream Gatherers. Этот подход позволит разработчикам создавать собственные промежуточные операции в потоках, повышая гибкость и выразительность программного кода.
Почему это важно?
До сих пор Stream API предлагал фиксированный набор операций, таких как map, filter, distinct, и другие. Однако некоторые задачи не могли быть легко решены встроенными методами. Например, если вы хотите выделить уникальные строки на основе их длины, это требовало сложных обходных путей:
record DistinctByLength(String str) {
@Override public boolean equals(Object obj) {
return obj instanceof DistinctByLength other && str.length() == other.str.length();
}
@Override public int hashCode() {
return str.length();
}
}
var result = Stream.of("foo", "bar", "baz", "quux")
.map(DistinctByLength::new)
.distinct()
.map(DistinctByLength::str)
.toList();
// result ==> [foo, quux]
С выходом фичи задача будет решаться гораздо проще:
var result = Stream.of("foo", "bar", "baz", "quux")
.gather(Gatherers.distinctBy(String::length))
.toList();
// result ==> [foo, quux]
Как работает Gatherer?
Gatherer — это интерфейс, задающий логику преобразования элементов потока. Его работа строится на четырех функциях:
1. initializer — создает объект для хранения состояния.
2. integrator — обрабатывает элементы потока, при необходимости записывая данные в состояние.
3. combiner — объединяет результаты для параллельных потоков.
4. finisher — выполняет финальные действия, такие как добавление оставшихся данных.
Например, gatherer windowFixed группирует элементы в списки фиксированного размера:
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.gather(Gatherers.windowFixed(3))
.toList();
// result ==> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Что это дает разработчику?
1. Гибкость: Возможность определять операции любой сложности.
2. Параллельность: Поддержка многопоточности при использовании combiner.
3. Снижение сложности: Простое выражение сложных операций без использования обходных решений.
4. Поддержка Stateful Stream Processing подхода
Заключение
JEP 485 — это достойный вклад в развитие Stream API, предоставляющий разработчикам инструменты для создания более выразительных и производительных потоков.
🔗 С полным описанием JEP можно ознакомиться по ссылке: https://openjdk.org/jeps/485
#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM