Java поддерживает неизменяемые переменные в форме окончательного модификатора для полей и локальных переменных, но не поддерживает неизменяемость объектов на уровне языка. Существуют шаблоны проектирования, предназначенные для различения методов-мутаторов и запросов в объектах, но стандартная библиотека и библиотеки из разных источников могут не поддерживать эту функцию.
Использование неизменяемых объектов делает код более безопасным, потому что они обнаруживают ошибки программирования, проявляющиеся во время выполнения. Это так называемый принцип «быстрого отказа», который вы, безусловно, сможете понять и оценить, если перешли из области программирования на C или C ++ в Java. Если у вас может быть неизменная версия объекта, и вы передаете ее в библиотеку (будь то внешнюю или вашу собственную), исключение возникает, как только код пытается вызвать любой метод, который является мутатором. При отсутствии неизменяемой версии ошибка, вызываемая таким вызовом, проявляется гораздо позже, когда происходит сбой программы с измененным и, следовательно, предположительно несогласованным объектом.
Из-за этих преимуществ неизменяемых объектов существуют библиотеки, которые обеспечивают неизменяемость для некоторых особых случаев. Самым известным и наиболее широко используемым примером является библиотека неизменяемой коллекции Guava от Google. Это создает неизменные версии для коллекций. Однако коллекции — это не полный мир классов Java.
Когда у вас есть код под вашим собственным контролем, вы можете разделить свои интерфейсы на запрос и часть мутатора, мутатор в конечном итоге расширяет интерфейс запроса. Реализация также может быть реализована в двух классах: класс запроса, реализующий интерфейс запроса, и класс мутатора, расширяющий класс запроса, реализующий интерфейс мутатора (который также включает функции интерфейса запроса). Когда вы хотите неизменную версию объекта, вы приводите ее и передаете, используя интерфейс запроса. Это, однако, не 100% безопасность. Библиотека может, по незнанию кода или по ошибке, привести объект обратно и изменить его состояние. Надежное решение состоит в том, чтобы реализовать интерфейс запроса в классе, который настроен со ссылкой на изменяемый объект и реализует делегирование всем методам, определенным в интерфейсе запроса.Хотя поддерживать такой код на Java в случае многочисленных и огромных классов очень сложно, решение, как правило, простое и понятное. Вы даже можете сгенерировать реализацию делегирующего запроса (расширяя изменяемый класс), когда интерфейсы запроса / мутатора и реализации класса не разделены.
Проект Immutator предоставляет эту функциональность во время выполнения. Используя библиотеку, вы можете создать делегирующий прокси-класс во время выполнения, который расширит класс мутатора и передаст вызовы метода исходному объекту, когда метод считается запросом, но выдает исключение времени выполнения, когда метод считается мутатором. , Использование класса очень просто, все, что вам нужно сделать, это вызвать статический метод класса Immutable:
MyMutatorClass proxy = Immutable.of(mutableObject);
Сгенерированный прокси будет принадлежать классу, который расширяет исходный класс, к которому принадлежит mutableObject, поэтому вы можете передавать прокси к любому коду, где вы будете передавать mutableObject, но вы не хотите, чтобы код изменял состояние объекта.
Как библиотека узнает, какие методы являются запросами, а какие — мутаторами? В этом простом случае библиотечный имутатор (существуют более сложные вызовы, если простого случая недостаточно) предполагает, что любой метод void также является мутатором, а любой метод, который возвращает какое-либо значение, является методом запроса.
Для поддержки постоянно растущей популярности беглого API вызов может быть записан в виде:
MyMutatorClass proxy = Immutable.of.fluent(mutableObject);
в этом случае любой метод, который возвращает значение, совместимое с классом аргумента, также считается методом мутатора.
Если даже эта функциональность не описывает поведение класса для прокси, тогда общая форма вызова:
MyMutatorClass proxy = Immutable.of.using(Query.class).of(mutableObject);
который считает, что любой метод, определенный в интерфейсе Query, является запросом, а методы, отсутствующие в интерфейсе Query, являются мутаторами. С помощью этой формы можно создать прокси-запрос для любых объектов.
Это мило и интересно. Сказав все это, есть некоторые ограничения в реализации библиотеки, которые частично происходят из языка Java и из доступного JDK.
Вы не можете объявить какой-либо финальный метод как метод мутатора. Причина этого заключается в том, что сгенерированный прокси-класс должен расширять исходный класс, чтобы прокси-объект мог использоваться вместо исходного объекта. Однако он не может переопределить окончательные методы. Конечные методы на самом деле не проксируются, но выполнение передается непосредственно исходному методу. Вот как работает Java.
Прокси-объект создается в исходном коде Java и компилируется во время выполнения. Это может быть медленнее, чем, например, использование cglib, который использует пакет asm и генерирует байт-код напрямую. С другой стороны, библиотека может быть более устойчивой к изменениям версии Java, и легче взглянуть на внутреннюю работу библиотеки и прокси.
Наконец, что не менее важно, библиотека использует некоторые небезопасные вызовы пакетов (Google, если вам нужно), которые могут работать не на всех платформах. Это необходимо для создания экземпляра прокси-объекта. Поскольку прокси-класс является расширением исходного класса, создающего прокси-объект, «нормальный путь» неявно вызовет конструктор расширенного класса. Это может не быть проблемой, но в некоторых случаях, когда конструктор выполняет тяжелую работу, это может быть.
Знать все, кто включает библиотеку в ваше приложение, очень просто. Поскольку библиотеки com.javax0 хранятся в репозитории Sonatype, все, что вам нужно сделать, это вставить библиотеку как зависимость в ваш файл pom.xml как
<dependency> <groupId>com.javax0</groupId> <artifactId>immutator</artifactId> <version>1.0.0</version> </dependency>
и оставайтесь с нами для предстоящих релизов.