
Привет, Хабр!
В 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<Coordinate> { @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)); } }
Записи можно использовать для создания объектов, которые могут быть сравнены на основе их данных.
Что в итоге?
-
Записи хорошо подходят для объектов, которые должны быть неизменяемыми.
-
Используем записи, чтобы сократить количество шаблонного кода и улучшить читаемость.
-
Добавляем логику валидации в компактные конструкторы, чтобы гарантировать создание корректных объектов.
Лямбда-выражения
Для большинства лямбда – это совсем не новость, а уже ежедневная практика, но было бы странно не вписать ее в этот список. Лямбда-выражения были введены аж в Java 8 и представляют собой сокращенный способ написания анонимных функций. Они позволяют создавать небольшие фрагменты кода, которые могут быть переданы и выполнены позже.
Синтаксис лямбда-выражений следующий:
(parameters) -> expression
или
(parameters) -> { statements; }
Пример простого лямбда-выражения:
(int a, int b) -> a + b
Лямбда-выражения могут иметь разные формы, от простых однострочных выражений до сложных многострочных блоков кода.
Одним из наиболее частых применений лямбда-выражений является работа с коллекциями, особенно в сочетании с API потоков.
Фильтрация списка чисел, чтобы оставить только четные:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // [2, 4, 6]
Лямбда-выражения делают сортировку коллекций более лаконичной.
Сортировка списка строк по длине:
List<String> 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 принимает переменное количество сообщений об ошибках и выводит их на стандартный поток ошибок.
Использование вар-аргументов оправдано в следующих случаях:
-
Когда количество аргументов, передаваемых в метод, неизвестно заранее.
-
Когда нужно уменьшить количество шаблонного кода.
Заключение
Вот и подошел к концу мой обзор крутых фич в Java. Надеюсь, вам было так же интересно читать, как мне — рассказывать об этих штуках. Пусть ваш код будет чистым, жизнь — лёгкой, а коллеги завидуют вашей крутости.
Больше фич коллеги из OTUS рассматривают в рамках курса Java Developer. Advanced. По ссылке ниже можете зарегистрироваться на бесплатный вебинар курса и оценить полезность курса самостоятельно.
ссылка на оригинал статьи https://habr.com/ru/articles/833462/
Добавить комментарий