Приветствую, я Сергей! Ознакомился с котлином, как разработчик и взглянул на него с точки зрения архитектора. Ребята молодцы, проделали колоссальный труд для производства качественно-инновационного продукта, повышающего продуктивность разработчиков, респект. С другой стороны была проведена поверхностная оценка применимости производимых котлином артефактов (классов) в разных средах. Привожу ниже довод, который сразу обратил на себя внимание – интеграция.
С интеграцией Java <–> Kotlin всё нормально, т.к. разработчики сделали на этой связке акцент. Это технический «шов» между двумя технологиями. В Java мы видим класс, как он скомпилирован котлином и «всунут» в JVM и можем с ним работать, тут вопросов нет.
Но, помимо соответствия JVM, в Java существуют некоторые архитектурные конвенции и стандарты. Речь идёт о JavaBeans. Основным фундаментальным контрактом Java, который используется в качестве фундамента большинством технологий, является JavaBeans - тот самый, который задаёт формат POJO-бинов. Основное свойство POJO – предоставление property-сетапа в виде пар get и set методов - аксессоров. Котлин предоставляет для этого отличный механизм их автогенерации. Это допустимо, ибо сами аксессоры обычно не несут логики, их функция заключается просто в существовании и предоставлении точек перехвата доступа к пропертям. Посмотрим ведущие технологии: JPA, CDI, аспектные фреймворки, Spring, и пр. Они в большем числе основаны на конвенции JavaBeans и добавляют к этому стандарту сверху свои требования. Наиболее общий и стандартный список таких требований включает: реализацию Serializable, наличие дефолтного конструктора, класс и его методы не могут быть final.
Небольшой оффтоп: задумаемся, почему именно такие требования? Во-первых, бины в этих технологиях создаются фабриками; во-вторых, для реализации своих нужд эти фреймворки очень жирно используют перехват вызовов и рефлексию, и вот именно для перехвата вызовов и динамического внедрения своей логики им жизненно важны возможности создания наследников от написанных нами бинов и переопределения методов. Например, если посмотреть на аннотированную сущность User в контексте JPA(EclipseLink), экземпляр которого нам возвращает менеджер, то через reflection можно увидеть что класс является не User, он уже User$Proxy__, и если руками вызвать setUserId() при настроенной автогенерации идентификаторов, то мы получим исключение что логично, т.к. за управление и создание ключей при настроенном генераторе отвечает менеджер.
Вернёмся к котлину. На выходе регулярного (без указания open) и data-классов мы имеем final, что противоречит вышеуказанным типовым требованиям к бинам. Чтобы kotlin так не делал нужно явно указывать open у регулярного класса и его методов, что я считаю лишним, т.к. тут начинается тернистый путь «обхода» заложенных в котлин ограничений.
У нас в компании используется свой Intercept-механизм, позволяющий перехватывать вызовы set-методов, задача которого предоставлять PropertyChangeSupport (из JavaBeans) без написания дополнительного кода для этого (прозрачное избавление от boilerplate-кода). Этот механизм использует, как и другие технологии, схему динамической генерации и создания Proxy-наследника от класса c перегруженными set-методами на лету. Котлиновские классы проксируются только с явным указанием open у класса и у методов, что я считаю костылём.
Разработчики котлина, внедряя final, руководствовались верным источником «Effective Java, Item 17: Design and document for inheritance or else prohibit it.». Это академический подход имеет свои преимущества. Но также существует практическая сторона вопроса, при которой было бы более удобно у классов в контексте аспектных фреймворков, иметь открытые акцессоры для: 1) необязательности open у регулярных классов 2) возможности в принципе использовать data-классы. С практической точки зрения, просто финальные классы котлина менее интересны, чем классы котлина, используемые в уже существующей аспектной инфраструктуре проектов.
Поэтому предлагаю разработчикам рассмотреть вопрос снятия дефолтного final.
С уважением, Сергей