Полезные фичи в Java: мой список

d9aa6986be57aa814721bead675a7252.jpg

Привет, Хабр!

В Java никогда не бывает скучно, особенно когда речь заходит о вещах, которые делают нашу жизнь проще и код — чище.

Сегодня я хочу рассказать вам о четырех фичах в Java, которые сам активно использовал в своих проектах и которые, на мой взгляд, заслуживают внимания. Да, это мой личный список, и я не претендую на то, что эти фичи новы или являются последним писком моды. Однако, по моему опыту, они действительно могут упростить жизнь.

И знаете, что самое приятное? Когда коллеги начинают говорить: «А почему я об этом не знал раньше?»

И первая фича — секционные классы.

Секционные классы

Секционные классы были введены в Java 15 как предварительная функция и стали постоянными в Java 17. Основная фича секционных классов — это возможность ограничить, какие классы могут наследовать данный класс.

Секционный класс объявляется с ключевым словом sealed, а классы, которым разрешено его наследовать, указываются с помощью ключевого слова permits. Класс, наследующий секционный класс, должен быть объявлен как final, sealed или non-sealed.

Простой пример:

public sealed class Shape permits Circle, Square {
    // общий функционал для всех фигур
}

public final class Circle extends Shape {
    // специфичный функционал для круга
}

public final class Square extends Shape {
    // специфичный функционал для квадрата
}

Абстрактный класс Shapeможет быть расширен только классами Circle и Square. Теперь можно быть уверенными, что никакая другая фигура не сможет унаследовать Shape.

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

public sealed class Transaction permits CreditTransaction, DebitTransaction {
    private double amount;

    public Transaction(double amount) {
        this.amount = amount;
    }

    public double getAmount() {
        return amount;
    }

    // общие методы для всех транзакций
}

public final class CreditTransaction extends Transaction {
    public CreditTransaction(double amount) {
        super(amount);
    }

    // специфичные методы для кредитной транзакции
}

public final class DebitTransaction extends Transaction {
    public DebitTransaction(double amount) {
        super(amount);
    }

    // специфичные методы для дебетовой транзакции
}

Мы уверены, что Transaction может быть только кредитной или дебетовой.

Секционные классы упрощают поддержку кода.

Записи

Записи — это особый вид классов, введённый в Java 14 как предварительная фича и окончательно утверждённый в Java 16. Записи позволяют создавать неизменяемые объекты с минимальным количеством шаблонного кода. Они автоматом генерируют конструкторы, методы equals(), hashCode(), и toString(), а также геттеры для всех полей.

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

Рассмотрим несколько примеров.

Создадим запись User с полями id, firstName, lastName, и email:

public record User(Long id, String firstName, String lastName, String email) {}

public class Main {
    public static void main(String[] args) {
        User user = new User(1L, "Artem", "Ivan", "artem@example.com");
        System.out.println(user);
    }
}

Код создаст неизменяемый объект User и выведет его данные в консоль. Записи позволяют избавиться от шаблонного кода.

Записи поддерживают валидацию данных при создании объекта. Добавим валидацию цены в записи Product:

public record Product(String name, double price) {
    public Product {
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Product product = new Product("Laptop", 999.99);
        System.out.println(product);
    }
}

Так можно добавить логику валидации в запись, используя компактный конструктор.

Записи также поддерживают добавление кастомных методов. Рассмотрим пример записи Rectangle с методом area:

public record Rectangle(double length, double width) {
    public double area() {
        return length * width;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle(5.0, 3.0);
        System.out.println("Area: " + rectangle.area());
    }
}

Здесь метод area вычисляет площадь прямоугольника.

Записи могут реализовывать интерфейсы. Рассмотрим запись Coordinate, реализующую интерфейс Comparable:

public record Coordinate(double x, double y) implements Comparable {
    @Override
    public int compareTo(Coordinate other) {
        return Double.compare(this.x, other.x);
    }
}

public class Main {
    public static void main(String[] args) {
        Coordinate point1 = new Coordinate(3.0, 4.0);
        Coordinate point2 = new Coordinate(2.0, 5.0);
        System.out.println("Comparison result: " + point1.compareTo(point2));
    }
}

Записи можно использовать для создания объектов, которые могут быть сравнены на основе их данных.

Что в итоге?

  1. Записи хорошо подходят для объектов, которые должны быть неизменяемыми.

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

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

Лямбда-выражения

Для большинства лямбда — это совсем не новость, а уже ежедневная практика, но было бы странно не вписать ее в этот список. Лямбда-выражения были введены аж в Java 8 и представляют собой сокращенный способ написания анонимных функций. Они позволяют создавать небольшие фрагменты кода, которые могут быть переданы и выполнены позже.

Синтаксис лямбда-выражений следующий:

(parameters) -> expression

или

(parameters) -> { statements; }

Пример простого лямбда-выражения:

(int a, int b) -> a + b

Лямбда-выражения могут иметь разные формы, от простых однострочных выражений до сложных многострочных блоков кода.

Одним из наиболее частых применений лямбда-выражений является работа с коллекциями, особенно в сочетании с API потоков.

Фильтрация списка чисел, чтобы оставить только четные:

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4, 6]

Лямбда-выражения делают сортировку коллекций более лаконичной.

Сортировка списка строк по длине:

List strings = Arrays.asList("short", "very long string", "medium");
strings.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));

System.out.println(strings); // [short, medium, very long string]

Лямбда-выражения отлично подходят для обработки событий в графических интерфейсах.

Обработка нажатия кнопки в JavaFX:

Button button = new Button("Click me");
button.setOnAction(event -> System.out.println("Button clicked!"));

До появления лямбда-выражений, для создания небольших анонимных функций использовались анонимные классы. Сравним два подхода.

Пример с анонимным классом:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running in a thread");
    }
};

new Thread(runnable).start();

Пример с лямбда-выражением:

Runnable runnable = () -> System.out.println("Running in a thread");
new Thread(runnable).start();

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

Вар-аргументы

Вар-аргументы позволяют методам принимать переменное количество параметров. Это достигается с помощью синтаксиса ..., который указывает, что метод может принимать от нуля до множества аргументов одного типа. Внутри метода эти аргументы рассматриваются как массив.

Объявление метода с вар-аргументами выглядит так:

public void methodName(Type... parameterName) {
    // тело метода
}

Здесь Type... указывает на тип аргументов, а parameterName — имя переменной, которая внутри метода будет доступна как массив.

Вар-аргумент должен быть последним параметром в методе.

Рассмотрим несколько примеров

Метод для суммирования чисел:

public static int sum(int... numbers) {
    int sum = 0;
    for (int number : numbers) {
        sum += number;
    }
    return sum;
}

public static void main(String[] args) {
    System.out.println(sum(1, 2, 3)); // 6
    System.out.println(sum(4, 5));    // 9
    System.out.println(sum());        // 0
}

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

Метод для создания строки из нескольких строк:

public static String concatenate(String... strings) {
    StringBuilder result = new StringBuilder();
    for (String str : strings) {
        result.append(str);
    }
    return result.toString();
}

public static void main(String[] args) {
    System.out.println(concatenate("Hello", " ", "world", "!")); // "Hello world!"
    System.out.println(concatenate("Java", " ", "is", " ", "fun")); // "Java is fun"
}

Метод concatenate принимает переменное количество строк и возвращает их объединение. Удобно, когда нужно собрать несколько строк в одну.

Метод для обработки ошибок:

public static void logErrors(String... errors) {
    for (String error : errors) {
        System.err.println("Error: " + error);
    }
}

public static void main(String[] args) {
    logErrors("File not found", "Access denied", "Network error");
}

logErrors принимает переменное количество сообщений об ошибках и выводит их на стандартный поток ошибок.

Использование вар-аргументов оправдано в следующих случаях:

  1. Когда количество аргументов, передаваемых в метод, неизвестно заранее.

  2. Когда нужноуменьшить количество шаблонного кода.

Заключение

Вот и подошел к концу мой обзор крутых фич в Java. Надеюсь, вам было так же интересно читать, как мне — рассказывать об этих штуках. Пусть ваш код будет чистым, жизнь — лёгкой, а коллеги завидуют вашей крутости.

Больше фич коллеги из OTUS рассматривают в рамках курса Java Developer. Advanced. По ссылке ниже можете зарегистрироваться на бесплатный вебинар курса и оценить полезность курса самостоятельно.

© Habrahabr.ru