Современная Java: от 8 к 25
Новая версия Java выходит каждые полгода, а раз в два года одна из них получает долгую поддержку — LTS (long-term support). Это версии 8, 11, 17, 21 и 25. Многие проекты и статьи в интернете до сих пор написаны на Java 8, и современный код выглядит заметно иначе. В этом уроке собраны главные изменения со ссылками на уроки, где они разобраны подробно.
Java 8: основа
Java 8 (2014) — самая важная версия за всю историю языка. В ней появились:
- лямбды и ссылки на методы — Лямбда-выражения;
- Stream API — Что такое поток данных;
Optional— Optional;- новый пакет дат
java.time— Даты и время.
Всё это — обычная часть современного кода, а не «новинки».
Java 9–11: меньше шаблонного кода
- Фабрики
List.of,Set.of,Map.ofсоздают неизменяемые коллекции одной строкой (Java 9) — Списки. varвыводит тип локальной переменной из значения (Java 10) — Переменные.- Новые методы строк
isBlank,strip,repeat,linesиFiles.readString(Java 11) — Чтение и запись файлов. - Система модулей и файл
module-info.java(Java 9) — Модули. - Встроенный
HttpClientдля запросов к веб-серверам (Java 11) — HTTP-клиент.
void main() {
var names = List.of("Анна", "Олег");
IO.println(names);
IO.println("ab".repeat(3));
}Результат:
[Анна, Олег]
abababJava 12–17: новые формы данных
switch-выражения со стрелками->возвращают значение (Java 14) — Оператор switch.- Текстовые блоки
"""для многострочных строк (Java 15) — Текстовые блоки и форматирование. - Записи
record— классы для данных в одну строку (Java 16) — Записи. - Шаблоны в
instanceof(Java 16) — Шаблоны в instanceof. - Запечатанные классы
sealed(Java 17) — Запечатанные классы.
Java 18–21: сопоставление с образцом и виртуальные потоки
- Шаблоны в
switchи шаблоны записей (Java 21) — Шаблоны в switch, Шаблоны записей. - Виртуальные потоки (Java 21) — Виртуальные потоки.
- Упорядоченные коллекции (sequenced collections, Java 21): у списков,
LinkedHashSetиLinkedHashMapпоявились общие методыgetFirst(),getLast()иreversed().
void main() {
List<String> queue = List.of("Анна", "Олег", "Ира");
IO.println(queue.getFirst());
IO.println(queue.getLast());
IO.println(queue.reversed());
}Результат:
Анна
Ира
[Ира, Олег, Анна]Раньше для последнего элемента писали list.get(list.size() - 1).
Java 22–25: проще начать
- Безымянная переменная
_для ненужных значений (Java 22) — Шаблоны записей. - Компактные файлы и метод
mainбезstaticи параметров, классIOдля ввода и вывода, автоматический импорт пакетовjava.base(Java 25) — Первая программа. - Код до вызова
super(...)в конструкторе — например, проверка аргументов (Java 25). - Ограниченные значения
ScopedValue— заменаThreadLocalдля виртуальных потоков (Java 25) — Ограниченные значения.
Было и стало
Одна и та же задача — посчитать сумму заказов по покупателям — в стиле Java 8 и в стиле Java 25. Сначала старый вариант:
import java.util.*;
public class Main {
static class Order {
private final String customer;
private final int amount;
Order(String customer, int amount) {
this.customer = customer;
this.amount = amount;
}
String getCustomer() { return customer; }
int getAmount() { return amount; }
}
public static void main(String[] args) {
List<Order> orders = Arrays.asList(new Order("Анна", 20), new Order("Олег", 35), new Order("Анна", 15));
Map<String, Integer> totals = new TreeMap<>();
for (Order order : orders) {
Integer current = totals.get(order.getCustomer());
totals.put(order.getCustomer(), (current == null ? 0 : current) + order.getAmount());
}
System.out.println(totals);
}
}Результат:
{Анна=35, Олег=35}Теперь современный — запись вместо класса, List.of, поток с группировкой и компактный файл:
record Order(String customer, int amount) {}
void main() {
var orders = List.of(new Order("Анна", 20), new Order("Олег", 35), new Order("Анна", 15));
var totals = orders.stream()
.collect(Collectors.groupingBy(Order::customer, TreeMap::new, Collectors.summingInt(Order::amount)));
IO.println(totals);
}Результат:
{Анна=35, Олег=35}Оба варианта работают в Java 25, но второй короче втрое и читается как описание задачи.
Частая ошибка
var не делает Java динамическим языком: тип переменной выводится один раз из начального значения и дальше не меняется:
void main() {
var count = 10;
count = "десять";
}Результат:
Main.java:3: error: incompatible types: String cannot be converted to int
count = "десять";
^
1 error
error: compilation failedИ помните, что новые возможности требуют новой версии: если проект собирается для Java 17, записи в нём есть, а шаблоны записей и компактные файлы — уже нет. Версию задают в настройках сборки, например <maven.compiler.release>25</maven.compiler.release> в Maven.