Большинство вакансий в 2026 году требуют Java 17 или 21, а новые проекты стартуют на 21 или 25 (обе LTS). Интервьюер ждёт, что вы пишете на современной Java, а не на «Java 8 с var».
Карта версий
| Версия | Что важно для собеса |
|---|---|
| 8 | лямбды, Stream API, Optional, java.time |
| 10 | var для локальных переменных |
| 11 (LTS) | HttpClient, String.isBlank/strip/repeat |
| 14 | switch-выражения со стрелками |
| 15 | текстовые блоки """ |
| 16 | record, pattern matching для instanceof |
| 17 (LTS) | sealed-классы |
| 21 (LTS) | pattern matching в switch, record patterns, виртуальные потоки, SequencedCollection |
| 22 | безымянные переменные _ |
| 25 (LTS) | компактные исходники и void main() без класса, код до super(...) в конструкторе |
var
var list = new ArrayList<String>(); тип выводится компилятором и фиксируется, это не динамическая типизация. Только для локальных переменных с инициализатором (и в for, лямбдах). Нельзя для полей, параметров методов и var x = null.
record
public record Money(BigDecimal amount, String currency) {
public Money { // компактный конструктор
if (amount.signum() < 0) throw new IllegalArgumentException("amount < 0");
}
public Money plus(Money o) { return new Money(amount.add(o.amount), currency); }
}
Record это неизменяемый носитель данных: поля private final, канонический конструктор, методы-аксессоры amount() (без get), equals, hashCode, toString по всем компонентам. Record неявно final и наследует java.lang.Record, поэтому не может расширять другой класс, но может реализовывать интерфейсы. Дополнительные поля экземпляра объявлять нельзя, статические можно. Неизменяемость поверхностная: record R(List<String> items) хранит ссылку на изменяемый список, поэтому в компактном конструкторе делают items = List.copyOf(items).
Как JPA-сущность record не годится (нужен конструктор без аргументов и изменяемость), а как DTO и ключ HashMap идеален.
sealed
public sealed interface Shape permits Circle, Square, Rect {}
public record Circle(double r) implements Shape {}
public record Square(double side) implements Shape {}
public non-sealed class Rect implements Shape { ... }
Набор наследников закрыт: каждый должен быть final, sealed или non-sealed. Компилятор знает все варианты, и это делает switch исчерпывающим.
Pattern matching
if (obj instanceof String s && !s.isBlank()) { // Java 16
System.out.println(s.length());
}
double area = switch (shape) { // Java 21
case Circle c -> Math.PI * c.r() * c.r();
case Square(double side) -> side * side; // record pattern
case Rect r when r.isEmpty() -> 0; // guard
case Rect r -> r.width() * r.height();
};
Для sealed-иерархии default не нужен: если добавить новый класс в permits, все такие switch перестанут компилироваться, и вы найдёте каждое место, где надо его обработать. case null теперь можно обработать явно, иначе switch по null бросает NPE.
switch-выражения и текстовые блоки
Стрелочный switch не проваливается (нет break), может возвращать значение, а yield возвращает значение из блока. Текстовый блок """ убирает общий отступ и удобен для SQL и JSON в коде.
Частые ошибки
- Считать, что
varделает переменную динамической. - Делать record с изменяемыми коллекциями без копирования.
- Писать
defaultвswitchпо sealed-типу и терять проверку исчерпываемости. - Использовать record как JPA-сущность.
Что спросят на собесе
- На какой версии Java пишете, что из 17 и 21 используете?
- Что генерирует record и чем он отличается от класса с Lombok
@Value? - Зачем нужны sealed-классы и как они работают со switch?
- Что такое record patterns и guard в switch?
- Где нельзя использовать var?