JavaRush /Java блог /Random /Кофе-брейк #247. Неизменяемые объекты в Java с использова...

Кофе-брейк #247. Неизменяемые объекты в Java с использованием шаблона Builder и функционального интерфейса. Java 21 — больше никаких public static void main()

Статья из группы Random

Неизменяемые объекты в Java с использованием шаблона Builder и функционального интерфейса

Источник: Medium Благодаря этому руководству вы научитесь создавать сложные неизменяемые объекты с помощью шаблона Builder и функционального интерфейса. Кофе-брейк #247. Неизменяемые объекты в Java с использованием шаблона Builder и функционального интерфейса. Java 21 — больше никаких public static void main() - 1

Почему важны неизменяемые объекты

Неизменяемые объекты (Immutable Objects) в Java — это объекты, состояния которых нельзя изменить после создания. Атрибут неизменности дает объекту несколько преимуществ: Потокобезопасность: неизменяемые объекты по своей сути потокобезопасны, так как их состояние не может измениться после создания. Это делает их подходящими для многопоточных сред, где важны синхронизация и безопасность потоков. Простота и ясность: неизменяемые объекты проще проектировать, реализовывать и использовать. Поскольку они не могут изменить свое состояние, вам не нужно беспокоиться об этом изменении с течением времени. Это делает код понятным и простым для понимания. Хеширование: неизменяемые объекты отлично подходят для использования в качестве ключей в HashMap или элементов в HashSet, поскольку их хэш-код остается постоянным. Риски безопасности при десериализации и удаленном исполнении кода: уязвимости десериализации и удаленного исполнения кода в Java часто связаны с изменяемыми объектами. Десериализация преобразует поток байтов обратно в объект. Если этот процесс происходит без надлежащей проверки, то это может привести к удаленному выполнению кода. Изменяемые объекты, которые могут изменять свое состояние после создания, могут использоваться во время десериализации для изменения потока управления программой или вызова произвольных функций. Примером этого риска является уязвимость десериализации коллекций Apache Commons (CVE-2015–4852), когда во время десериализации манипулировали изменяемыми объектами, что приводило к удаленному выполнению кода. Неизменяемые объекты, которые не могут изменить свое состояние после создания, по своей природе устойчивы к таким манипуляциям, что подчеркивает важность неизменности при обработке потенциально ненадежных входных данных.

Как сделать объекты Java неизменяемыми с помощью функционального интерфейса и шаблона Builder

Чтобы проиллюстрировать, как создавать сложные неизменяемые объекты с помощью шаблона Builder и функционального интерфейса, давайте представим, что мы создаем объект House. Этот House содержит несколько других объектов, в том числе MainDoor, Terrace, Lobby, MainBedroom, GuestRoom, каждый со своими свойствами. Мы хотим создать эти объекты неизменным образом и интегрировать их в объект House.

Настройка проекта

Весь код этой статьи размещен на GitHub и доступен здесь. Это проект на основе Maven, разработанный в Java 17.

Определение функционального интерфейса

Начнем с определения функционального интерфейса Customizer. Этот интерфейс используется в наших классах builder, чтобы предоставить клиентам механизм настройки создаваемых объектов.

@FunctionalInterface
public interface Customizer<T> {
    T customize(T t);
}

Определение класса MainDoor

Класс MainDoor является примером того, как строится каждый компонент дома (House). Класс объявлен как final, и все его поля являются private final, что делает его неизменяемым классом. Он использует шаблон Builder для создания экземпляра, который возвращается с помощью метода build().

public final class MainDoor {
    private final String doorType;
    private final String doorColor;
    private final String doorLockType;

    // Конструктор является private для управления созданием экземпляров.
    private MainDoor(Builder builder) {
        this.doorType = builder.doorType;
        this.doorColor = builder.doorColor;
        this.doorLockType = builder.doorLockType;
    }

    // Геттеры для всех полей, сеттеры для обеспечения неизменности не предусмотрены.
    public String getDoorType() {
        return doorType;
    }

    public String getDoorColor() {
        return doorColor;
    }

    public String getDoorLockType() {
        return doorLockType;
    }

    // Класс Builder
    public static class Builder {
        private String doorType;
        private String doorColor;
        private String doorLockType;

        public Builder doorType(String doorType) {
            this.doorType = doorType;
            return this;
        }

        public Builder doorColor(String doorColor) {
            this.doorColor = doorColor;
            return this;
        }

        public Builder doorLockType(String doorLockType) {
            this.doorLockType = doorLockType;
            return this;
        }

        public MainDoor build() {
            return new MainDoor(this);
        }
    }
}

Определение House

Класс House использует Builder для инкапсуляции логики построения. Каждый компонент создается с помощью Customizer и включается в объект House. Этот дизайн позволяет клиенту использовать лямбда-выражения для настройки каждого компонента дома во время строительства.

public final class House {
    private final MainDoor mainDoor;
    //... другие компоненты

    // Private-конструктор для управления созданием экземпляра.
    private House(Builder builder) {
        this.mainDoor = builder.mainDoor;
        //... другие компоненты
    }

    // Геттеры для всех компонентов, нет сеттеров для сохранения неизменности.
    public MainDoor getMainDoor() {
        return mainDoor;
    }
    //... другие геттеры

    public static class Builder {
        private MainDoor mainDoor;
        //... другие компоненты

        public Builder mainDoor(Customizer<MainDoor.Builder> customizer) {
            this.mainDoor = customizer.customize(new MainDoor.Builder()).build();
            return this;
        }
        //... другие методы builder

        public House build() {
            return new House(this);
        }
    }
}

Строительство House

Объект House может быть построен с использованием лямбда-выражений для настройки каждого компонента. Этот подход интуитивно понятен и обеспечивает превосходную гибкость при создании объектов.

House house = new House.Builder()
      .mainDoor(md -> md.doorType("Wooden Door"))
      .terrace(t -> t.view("Garden View").size(300).hasGarden(true))
      .lobby(l -> l.style("Modern").area(500).hasFurniture(true))
      .mainBedroom(mb -> mb.wallColor("White").bedType("King Size").hasAirConditioner(true))
      .guestRoom(gr -> gr.wallColor("Cream").bedType("Queen Size").hasAirConditioner(false))
      .build();
Вот и все! Надежный метод создания неизменяемых объектов в Java. Комбинируя шаблон Builder с функциональным интерфейсом, мы получаем значительный контроль над созданием объектов.

Java 21 — больше никаких public static void main()

Источник: Medium Ознакомившись с этой публикацией, вы получите представление о безымянных классах — новой функции из релиза Java 21, которая пришла на замену методу main(). Да, вы правильно прочитали заголовок. В Java 21 метод public static void main() больше не требуется. В новой версии языка появилась новая функция для упрощения кодирования приложений Java. Сейчас в Java метод main() является отправной точкой для всех Java-приложений. Когда вы запускаете программу, виртуальная машина Java (JVM) вызывает метод main(), чтобы начать работу. Так почему же нужно отказываться от метода main()? Ну, на это есть пара причин.

1. Простота

Во-первых, это упрощает изучение Java для начинающих. Метод main() часто рассматривается как барьер для начинающих программистов, поскольку некоторых он может сбивать с толку. Метод main() является особым методом — у него есть определенная подпись, и он должен быть объявлен определенным образом. Иногда это путает новичков, которые могут не понимать назначения метода main() или того, как правильно его объявить. Например, метод main() должен быть общедоступным (public), статическим (static) и недействительным (void). Он также должен принимать массив строк в качестве параметра. Это довольно много для запоминания новичку, и это может затруднить написание простой Java-программы.

2. Лучшая совместимость с другими языками программирования

Во-вторых, новая функция делает Java более совместимым с другими языками программирования. Во многих других языках точкой входа в программу является не конкретный метод, такой как main(), а скорее блок кода, выполняемый в начале. Поймите: Java пытается быть модным и совместимым с другими языками программирования, такими как Python и JavaScript. Эти языки не нуждаются в методе main(), так зачем же он нужен Java?

3. Гибкость

Наконец, это открывает больше возможностей для структурирования ваших Java-программ. Теперь вы можете создавать программы Java без класса main, что дает вам больше гибкости. Вы также можете создавать программы без использования безымянных классов. Это делает приложения более модульными и более простыми для понимания. Раньше вам приходилось следовать строгому способу объявления метода main(), и это связывало руки, если вы хотели попробовать что-то новое. Но теперь, без метода main(), вы можете свободно исследовать различные стили программирования. Например, такой стиль, как функциональное программирование, метод main() вообще не использует. В функциональном программировании программы создаются из небольших повторно используемых функций, и нет необходимости добавлять main() для запуска программы. Вот пример кода, который вы можете использовать для запуска программы Java без метода public static void main():

class HelloWorld {

     void main() {
        System.out.println("Hello, world!");
    }

}
Теперь давайте сделаем это еще лучше: Безымянные классы (Unnamed classes) — новая функция Java 21, позволяющая писать простые программы на Java без объявления класса. Например, разработчик может создать программу, состоящую из нескольких небольших автономных функций. Каждая функция может быть определена в своем собственном безымянном классе, и эти функции могут вызываться из метода main. Это сделало бы программу более модульной и простой для понимания, поскольку каждая функция отвечала бы за определенную задачу. Чтобы создать безымянный класс, вам просто нужно написать метод, который начинается с ключевого слова new. Например, следующий код создаст безымянный класс, который печатает “Hello, world!” в консоль:

new {
    System.out.println("Hello, world!");
}
В целом удаление метода main() является положительным изменением для Java. Это делает язык более доступным, современным и гибким. Если вы Java-разработчик, я рекомендую вам это учитывать.
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Анна Уровень 34
7 августа 2023
Ну, если метод main был барьером для начинающего программиста, то я даже не знаю, что сказать. А как он дальше будет язык учить? Упрощения, возможно, хороший путь, язык живет, подстраивается под современные потребности, но аргумент так себе.