Java в 2026 году: 25 лучших практических примеров кода от основ до многопоточности

Title: Java в 2026 году: 25 практических примеров кода для разработчиков
Description: Практические примеры Java-кода: коллекции, Stream API, Optional, records, многопоточность, Virtual Threads, HTTP API, файлы, JSON, generics и современные возможности Java.
URL: /java-primeri-koda-2026/

Ключевые запросы: Java примеры кода, Java для начинающих, примеры Java, задачи Java, современный Java, Java Stream API, многопоточность Java, Java коллекции, Java Optional, Java records, Java Virtual Threads

T.me/javatg – крупнейший обучающий канал в тг по Java!


Java: лучшие практические примеры кода

Java остаётся одним из основных языков для backend-разработки, банковских систем, микросервисов, Android-инфраструктуры, enterprise-приложений и высоконагруженных сервисов.

Но современный Java сильно отличается от языка десятилетней давности.

Сегодня разработчик использует:

records → pattern matching → Stream API → CompletableFuture → Virtual Threads → HttpClient → sealed classes

Ниже — практические примеры, которые полезнее очередного Hello World.


1. Фильтрация списка через Stream API

Допустим, у нас есть список пользователей:

record User(String name, int age) {}

List<User> users = List.of(
        new User("Alex", 18),
        new User("Maria", 27),
        new User("John", 16),
        new User("Kate", 32)
);

Найдём всех совершеннолетних:

List<User> adults = users.stream()
        .filter(user -> user.age() >= 18)
        .toList();

Результат:

Alex
Maria
Kate

Stream API особенно удобен, когда нужно построить цепочку преобразований:

filter
→ map
→ sorted
→ distinct
→ collect

2. Получить только нужное поле из объектов

Например, список имён пользователей:

List<String> names = users.stream()
        .map(User::name)
        .toList();

Метод:

User::name

называется method reference.

Он является компактной альтернативой:

user -> user.name()

3. Найти максимальное значение

Например, самого старшего пользователя:

User oldest = users.stream()
        .max(Comparator.comparingInt(User::age))
        .orElseThrow();

Здесь max() возвращает Optional<User>.

Поэтому результат необходимо обработать.


4. Optional вместо проверки на null

Плохой вариант:

User user = findUser();

if (user != null) {
    System.out.println(user.name());
}

Можно вернуть:

Optional<User>

Пример:

Optional<User> user = findUser();

user.ifPresent(u ->
        System.out.println(u.name())
);

Или:

String name = user
        .map(User::name)
        .orElse("Unknown");

Особенно полезно, когда отсутствие значения является нормальным состоянием программы.


5. orElse() и orElseGet() — важное отличие

На первый взгляд они похожи:

optional.orElse(createUser());

и:

optional.orElseGet(() -> createUser());

Но есть важная разница.

В первом случае:

createUser()

будет вызван даже если Optional уже содержит значение.

Во втором — только когда значение отсутствует.

Поэтому для дорогих операций чаще нужен:

orElseGet()

6. Группировка элементов

Допустим:

record Employee(String name, String department) {}

Есть сотрудники:

List<Employee> employees = List.of(
        new Employee("Alex", "Backend"),
        new Employee("Maria", "Frontend"),
        new Employee("John", "Backend"),
        new Employee("Kate", "DevOps")
);

Сгруппируем их по отделам:

Map<String, List<Employee>> byDepartment =
        employees.stream()
                .collect(Collectors.groupingBy(
                        Employee::department
                ));

Получаем структуру:

Backend → Alex, John
Frontend → Maria
DevOps → Kate

Это один из самых полезных приёмов Stream API.


7. Подсчитать количество элементов в каждой группе

Map<String, Long> count =
        employees.stream()
                .collect(Collectors.groupingBy(
                        Employee::department,
                        Collectors.counting()
                ));

Результат:

Backend = 2
Frontend = 1
DevOps = 1

8. Превратить список в Map

Допустим:

record Product(long id, String name) {}

Создадим индекс по ID:

Map<Long, Product> productsById =
        products.stream()
                .collect(Collectors.toMap(
                        Product::id,
                        Function.identity()
                ));

Теперь поиск:

Product product = productsById.get(42L);

работает намного удобнее, чем постоянный перебор списка.


9. Неожиданная проблема Collectors.toMap()

Этот код:

Collectors.toMap(
        User::name,
        User::age
)

упадёт, если два пользователя имеют одинаковое имя.

Ошибка:

IllegalStateException: Duplicate key

Нужно указать стратегию:

Map<String, Integer> ages =
        users.stream()
                .collect(Collectors.toMap(
                        User::name,
                        User::age,
                        Math::max
                ));

При конфликте останется максимальный возраст.


10. Удаление дубликатов

Самый простой способ:

List<Integer> values = List.of(
        1, 2, 2, 3, 3, 3, 4
);

List<Integer> unique = values.stream()
        .distinct()
        .toList();

Результат:

[1, 2, 3, 4]

Но для собственных классов корректность distinct() зависит от equals() и hashCode().

С record они генерируются автоматически.


11. Почему record удобнее обычного DTO

Раньше:

public class User {

    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Современный вариант:

public record User(
        String name,
        int age
) {}

Java автоматически создаёт:

  • конструктор
  • accessors
  • equals()
  • hashCode()
  • toString()

Для immutable DTO это очень удобно.


12. Проверка данных внутри record

Record не означает отсутствие валидации.

public record User(
        String name,
        int age
) {
    public User {
        if (age < 0) {
            throw new IllegalArgumentException(
                    "Age cannot be negative"
            );
        }
    }
}

Это compact constructor.


13. Современный switch

Вместо:

String result;

switch (status) {
    case "OK":
        result = "Success";
        break;

    case "ERROR":
        result = "Failed";
        break;

    default:
        result = "Unknown";
}

можно написать:

String result = switch (status) {
    case "OK" -> "Success";
    case "ERROR" -> "Failed";
    default -> "Unknown";
};

Такой switch возвращает значение.


14. Pattern Matching

Допустим:

Object value = "Java";

Раньше:

if (value instanceof String) {
    String text = (String) value;
    System.out.println(text.length());
}

Теперь:

if (value instanceof String text) {
    System.out.println(text.length());
}

Java одновременно проверяет тип и создаёт переменную.


15. Sealed classes

Предположим, система поддерживает только несколько типов платежей:

sealed interface Payment
        permits CardPayment, CashPayment {
}

Реализации:

record CardPayment(
        String cardNumber
) implements Payment {}

record CashPayment(
        double amount
) implements Payment {}

Теперь компилятор знает полный набор допустимых типов.

Это особенно удобно вместе с pattern matching.


16. Работа с файлами через Files

Прочитать файл:

String text = Files.readString(
        Path.of("data.txt")
);

Записать:

Files.writeString(
        Path.of("output.txt"),
        "Hello Java"
);

Для небольших файлов зачастую этого достаточно.


17. Читать огромный файл построчно

Плохая идея:

Files.readAllLines(path);

если файл занимает несколько гигабайт.

Лучше:

try (Stream<String> lines = Files.lines(path)) {

    lines.filter(line -> line.contains("ERROR"))
            .forEach(System.out::println);
}

Файл не загружается целиком в память.


18. HTTP-запрос без сторонней библиотеки

В Java есть встроенный HttpClient.

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com"))
        .GET()
        .build();

HttpResponse<String> response =
        client.send(
                request,
                HttpResponse.BodyHandlers.ofString()
        );

System.out.println(response.statusCode());
System.out.println(response.body());

Для простых HTTP-запросов сторонняя библиотека уже не обязательна.


19. Асинхронный HTTP-запрос

client.sendAsync(
        request,
        HttpResponse.BodyHandlers.ofString()
).thenApply(HttpResponse::body)
 .thenAccept(System.out::println);

sendAsync() возвращает:

CompletableFuture<HttpResponse<T>>

20. CompletableFuture

Представим две независимые операции:

CompletableFuture<String> userFuture =
        CompletableFuture.supplyAsync(
                () -> loadUser()
        );

CompletableFuture<String> ordersFuture =
        CompletableFuture.supplyAsync(
                () -> loadOrders()
        );

Можно дождаться обоих:

CompletableFuture<Void> all =
        CompletableFuture.allOf(
                userFuture,
                ordersFuture
        );

all.join();

После чего получить результаты:

String user = userFuture.join();
String orders = ordersFuture.join();

21. Virtual Threads

Одна из важнейших возможностей современного Java — Virtual Threads.

Пример:

Thread.startVirtualThread(() -> {
    System.out.println(
            "Running in virtual thread"
    );
});

Или через executor:

try (var executor =
             Executors.newVirtualThreadPerTaskExecutor()) {

    for (int i = 0; i < 10_000; i++) {

        executor.submit(() -> {
            Thread.sleep(1000);
            return "done";
        });
    }
}

Главная идея Virtual Threads — сделать большое число блокирующих задач значительно дешевле обычных OS threads.

Особенно полезны они для I/O-bound workloads:

HTTP
SQL
RPC
файлы
очереди

22. Почему Virtual Threads не ускоряют CPU-bound код автоматически

Вот такой код:

executor.submit(() -> calculateHugeMatrix());

не станет в сотни раз быстрее только потому, что используется Virtual Thread.

Virtual Threads особенно полезны, когда задача большую часть времени ждёт:

network
database
disk

Для чистых CPU-вычислений ограничением остаются реальные CPU cores.


23. Потокобезопасный счётчик

Плохой вариант:

int counter = 0;

counter++;

При многопоточности операция ++ не атомарна.

Можно использовать:

AtomicInteger counter =
        new AtomicInteger();

counter.incrementAndGet();

Или для очень конкурентных счётчиков:

LongAdder counter =
        new LongAdder();

counter.increment();

System.out.println(
        counter.sum()
);

24. ConcurrentHashMap

Обычный HashMap не предназначен для конкурентных изменений.

Используйте:

ConcurrentHashMap<String, Integer> map =
        new ConcurrentHashMap<>();

Например:

map.compute(
        "java",
        (key, value) ->
                value == null
                        ? 1
                        : value + 1
);

Ещё удобнее:

map.merge(
        "java",
        1,
        Integer::sum
);

25. Частотный словарь буквально в несколько строк

Есть список:

List<String> languages = List.of(
        "Java",
        "Python",
        "Java",
        "Go",
        "Java",
        "Python"
);

Получаем частоты:

Map<String, Long> frequency =
        languages.stream()
                .collect(Collectors.groupingBy(
                        Function.identity(),
                        Collectors.counting()
                ));

Результат:

Java   → 3
Python → 2
Go     → 1

26. Сортировка Map по значениям

frequency.entrySet()
        .stream()
        .sorted(
                Map.Entry
                        .<String, Long>comparingByValue()
                        .reversed()
        )
        .forEach(System.out::println);

Получим:

Java=3
Python=2
Go=1

27. Самый частый элемент списка

String mostPopular =
        languages.stream()
                .collect(Collectors.groupingBy(
                        Function.identity(),
                        Collectors.counting()
                ))
                .entrySet()
                .stream()
                .max(Map.Entry.comparingByValue())
                .map(Map.Entry::getKey)
                .orElseThrow();

Результат:

Java

28. Разбить список на группы

Есть:

List<Integer> numbers =
        List.of(1, 2, 3, 4, 5, 6);

Разделим на чётные и нечётные:

Map<Boolean, List<Integer>> result =
        numbers.stream()
                .collect(Collectors.partitioningBy(
                        number -> number % 2 == 0
                ));

Получим:

true  → [2, 4, 6]
false → [1, 3, 5]

Для двух групп partitioningBy() зачастую удобнее groupingBy().


29. FlatMap

Представим:

List<List<String>> data = List.of(
        List.of("Java", "Go"),
        List.of("Python", "Rust"),
        List.of("JavaScript")
);

Нужно превратить это в один список.

List<String> result =
        data.stream()
                .flatMap(Collection::stream)
                .toList();

Получим:

Java
Go
Python
Rust
JavaScript

flatMap() особенно часто встречается в production Java.


30. Проверить условие для элементов списка

Есть ли хотя бы один пользователь старше 30:

boolean exists =
        users.stream()
                .anyMatch(
                        user -> user.age() > 30
                );

Все ли совершеннолетние:

boolean allAdults =
        users.stream()
                .allMatch(
                        user -> user.age() >= 18
                );

Ни одного администратора:

boolean noAdmins =
        users.stream()
                .noneMatch(
                        user -> user.name()
                                .equals("admin")
                );

Java Stream или обычный цикл?

Stream API не нужно использовать абсолютно везде.

Например:

for (User user : users) {
    if (user.age() >= 18) {
        sendEmail(user);
    }
}

иногда понятнее, чем сложная цепочка:

users.stream()
        .filter(...)
        .map(...)
        .peek(...)
        .flatMap(...)
        .forEach(...);

Хороший Java-код — не тот, где максимально много Stream API.

Он должен быть понятен следующему разработчику.


Частая ошибка: изменение коллекции во время итерации

Так делать нельзя:

for (String value : list) {
    if (value.isEmpty()) {
        list.remove(value);
    }
}

Можно получить:

ConcurrentModificationException

Правильнее:

list.removeIf(String::isEmpty);

Почему List.of() иногда удивляет

List<String> list =
        List.of("Java", "Go");

list.add("Rust");

Результат:

UnsupportedOperationException

List.of() создаёт immutable collection.

Если нужен изменяемый список:

List<String> list =
        new ArrayList<>(
                List.of("Java", "Go")
        );

Arrays.asList() — ещё одна ловушка

List<String> list =
        Arrays.asList(
                "Java",
                "Go"
        );

list.add("Rust");

Тоже ошибка.

Список имеет фиксированный размер.

Но:

list.set(0, "Python");

работает.

Это историческое поведение Arrays.asList().


Сравнение строк в Java

Неправильно:

if (name == "Java") {
}

Правильно:

if ("Java".equals(name)) {
}

== сравнивает ссылки на объекты.

equals() — содержимое.


StringBuilder вместо + в большом цикле

Плохо:

String result = "";

for (int i = 0; i < 100_000; i++) {
    result += i;
}

Лучше:

StringBuilder result =
        new StringBuilder();

for (int i = 0; i < 100_000; i++) {
    result.append(i);
}

String text = result.toString();

В сложных циклах это может значительно снизить число временных объектов.


Generics

Без generics:

List list = new ArrayList();

list.add("Java");

String value = (String) list.get(0);

С generics:

List<String> list =
        new ArrayList<>();

list.add("Java");

String value = list.get(0);

Компилятор уже знает тип элементов.


Generic-метод

public static <T> T first(
        List<T> values
) {
    return values.getFirst();
}

Использование:

String language =
        first(List.of("Java", "Go"));

Integer number =
        first(List.of(10, 20));

Один метод работает с разными типами.


Как правильно сравнивать объекты

Для собственных классов нужно корректно реализовать:

equals()
hashCode()

Для record Java делает это автоматически:

record Point(int x, int y) {}

Теперь:

new Point(1, 2)
        .equals(
                new Point(1, 2)
        );

вернёт:

true

Хорошая структура современного Java-проекта

Для backend-проекта структура может выглядеть так:

src/
└── main/
    └── java/
        └── com.example.app/
            ├── controller/
            ├── service/
            ├── repository/
            ├── domain/
            ├── dto/
            ├── config/
            └── exception/

Но не нужно создавать 20 слоёв архитектуры для приложения из трёх классов.

Архитектура должна соответствовать сложности задачи.


Что учить Java-разработчику в 2026 году

Хорошая последовательность:

Java Core
↓
Collections
↓
Generics
↓
Exceptions
↓
Streams
↓
Concurrency
↓
Virtual Threads
↓
SQL
↓
PostgreSQL
↓
Spring Boot
↓
REST
↓
Testing
↓
Docker
↓
Kafka
↓
Redis
↓
Microservices

После этого уже можно глубже идти в:

JVM
GC
profiling
distributed systems
performance
architecture

Что спрашивают на собеседовании Java-разработчика

Частые темы:

  • HashMap
  • equals() / hashCode()
  • ArrayList / LinkedList
  • Stream API
  • Generics
  • immutable objects
  • exceptions
  • synchronized
  • volatile
  • ConcurrentHashMap
  • JVM memory model
  • Garbage Collector
  • Spring DI
  • Spring Boot
  • Hibernate
  • SQL
  • transactions
  • Kafka
  • REST
  • concurrency

Для Senior-разработчиков намного важнее уже не синтаксис Java, а архитектурные компромиссы.


FAQ

Какой Java учить в 2026 году?

Для обучения лучше использовать современную LTS-ветку Java и одновременно понимать возможности Java 21+. Главное — не учиться по материалам, где современный Java сводится к синтаксису Java 8.

Сложно ли изучать Java?

Java требует больше структуры, чем Python, но строгая типизация и развитая экосистема хорошо помогают при работе с крупными проектами.

Где используется Java?

Java широко используется в backend, банковском ПО, enterprise-системах, микросервисах, highload-проектах, data-инфраструктуре и крупных корпоративных системах.

Нужно ли изучать Spring сразу?

Сначала стоит разобраться с Java Core, коллекциями, generics, исключениями и многопоточностью. После этого переходить к Spring Boot значительно проще.

Нужен ли Stream API?

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

Стоит ли изучать Virtual Threads?

Да, особенно backend-разработчикам. Они существенно меняют подход к большому количеству блокирующих I/O-задач.

Что важнее: Java или Spring?

Spring без хорошего понимания Java быстро превращается в запоминание аннотаций. Поэтому сначала стоит изучить сам язык и JVM.


Итог

Современный Java — это уже далеко не только:

public static void main(String[] args)

Сегодня разработчик активно использует:

records
Stream API
Optional
pattern matching
sealed classes
CompletableFuture
Virtual Threads
HttpClient
Concurrent Collections

Лучший способ изучать Java — не просто читать синтаксис, а постоянно решать небольшие практические задачи.

Поделиться:

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *