Главная » Хабрахабр » [Из песочницы] Из Балтийского моря в Индийский океан

[Из песочницы] Из Балтийского моря в Индийский океан

Рассказ пойдет о том, как началось мое путешествие с острова Котлин на остров Джаву.

Все началось с того, что ревизия программного обеспечения в организации, в которой я имею честь работать, показало наличие разнообразного софта для всяких нужд, и как вариант, мне предложили его унификацию, а именно – переписать часть проектов на Java, дабы всем поддерживать текущий функционал было удобно. Да и взаимозаменяемость никто не отменял.

На мою долю выпало деcктопное приложение по тестированию основного софта. Оно написано на многообещающем языке Kotlin (на время работы была версия 1.1.), но унификация есть унификация. В связи с этим хочу поделиться парой моментов, с которыми столкнулся в ходе работы, а также впечатлениями, которые на меня произвел Kotlin, так как я с ним работал впервые. Стоит отметить, что информации по данному языку довольно много, как и различных его сравнений с Java, я лишь хочу поделиться своими впечатлениями.
Начать стоит с того, что Kotlin неплохо дружит с Java, видно на что ориентировались создатели данного языка, да и наличие компиляции в байткод JVM тоже радует. Это позволяет использовать различные фреймворки и библиотеки Java, а также, при желании, почти бесшовно скрестить в одном проекте эти два языка, необходимо лишь начинать с компиляции классов Kotlin. В проекте использовались TornadoFX и Exposed, первый – это аналог JavaFX, а второй – фреймворк для работы с базами данных.

Первым ощущением было, да эта та же Джава только в профиль, но с большим количеством синтаксического сахара, что позволяет писать код быстрее и компактнее, но местами гораздо сложнее.

Взглянув в первый раз в код, слегка удивляешься обилию «?», отсутствию геттеров и сеттеров, неизменным и изменяемым переменным val и var, с автоматическим выводом типов или их наличием, конечно если их инициализация отложена на потом. Поэтому разбирая код, часто ловишь себя на мысли, какой же все-таки объект имел в виду коллега, чье наследство мне довелось переписывать. Примитивных типов данных, таких как int, boolean и т.д., в языке не наблюдается, как и любимых с детства «;» и «new».

Начнем с того, что язык считается null-безопасным, вследствие чего, в коде часто попадаются такие конструкции как:

Stand?.channell?.stream?.act() 

Прямо-таки хочется воскликнуть: «Больше вопросов Богу вопросов!».
«?» вызывает легкое недоумение в первое время, понятно, что во главе угла стоит null-безопасность, в связи с чем, каждое свойство объекта приходиться проверять на null, но зачем же тогда оставлять возможность вызова методов без проверок на null, лишь заменив «?» на «!!».
Как пример, предыдущее выражение будет выглядеть как

 Stand!!.channell!!.stream!!.act() 

Компилятор не будет проверять данное выражение. Как не будет проверять и в случае компиляции совместного проекта c Java. Спрашивается, в чем тогда преимущество?!

Проверяемые исключения в языке отсутствуют, и никаких IOException при чтении файла не увидишь. Плохо это или хорошо судить вам.

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

Kotlin Java
private fun checkConnections(silently: Boolean = false): Boolean { var isNotFailed = true mqToClearTable.items.filter { isMqValid(it) }.forEach { mq -> if (mq.queue.isNotEmpty()) { isNotFailed = isNotFailed && checkMqConnection(MQContainer(mq.host, mq.port.toInt(), mq.channel, mq.queue, mq.manager), silently) } } return isNotFailed }
private Boolean checkConnections(Boolean tempSilently) { boolean silently = ObjectUtils.defaultIfNull(tempSilently, false); boolean isNotFailed = true; List<QueueToClearObservable> filteredQueue = mqToClearTable.getItems().stream().filter(this::isMqValid).collect(toList()); for (QueueToClearObservable mq : filteredQueue) { if (StringUtils.isNotEmpty(mq.getQueue())) { isNotFailed = isNotFailed && checkMqConnection(new MQContainer(mq.getHost(), mq.getPort(), mq.getChannel(), mq.getQueue(), mq.getManager()), silently); } } return isNotFailed; }
private fun armsTable() = armsTable.apply { columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY items = currentStand?.arms?.observable() column("Сервер", ARM::hostProperty).useTextField(DefaultStringConverter())}
private void armsTable() { armsTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); if (currentStand != null) { if (CollectionUtils.isNotEmpty(currentStand.getArms())) { List<ARM> items = FXCollections.observableList(currentStand.getArms()); armsTable.getItems().addAll(arms); } } TableColumn<ARM, String> hostPropertyColumn = new TableColumn<>("Сервер"); hostPropertyColumn.setCellValueFactory(cell -> cell.getValue().hostPropertyProperty()); hostPropertyColumn.setCellFactory(TextFieldTableCell.forTableColumn());

Наличие значений по умолчанию в сигнатурах вызова в Kotlin не может не радовать, в Java такие вещи приходится решать либо через класс Optional, либо через перезагрузку методов.
Да, функциональный стиль в Kotlin на данный момент проработан лучше, что позволяет уже при инициализации объекта класса добавить необходимые в данный момент свойства. Например,

 factory = new MQQueueConnectionFactory().applay { channel = mq.channel targetClientMatching = true hostname = mq.host.activ(); }

В Java же пришлось бы организовать несколько конструкторов, в которых можно бы было перенести добавление данных свойств, или использовать дополнительные методы после их инициализации.

Но есть, на мой взгляд, и спорные вещи:

Еще немного о функциональной части Kotlin. Наличие схожих функций run, let, apply, also с различными нюансами применения, что вызывает диссонанс, когда несколько таких функций используется в рамках одной конструкции с использованием ключевого слова it (неявное имя единственного параметра), например:

testInfo.also { it.endDateProperty.onChange { it?.let { update(Tests.endDate, it) } } }

Надо потратить больше времени при разборе кода, чем это необходимо, чтобы разобраться, к чему именно относится тот или иной it, особенно если код большой и присутствует вложенность функций.

Пара комментариев по поводу конструкторов в Kotlin. Мало того, что, как и с методами, в конструкторе можно задать значение полей по умолчанию, так и паттерн Одиночка реализуется на уровне языка, как в прочем, и дата-классы, в отличие от Java, где мы снова и снова прописываем все ручками и по нескольку раз. Впрочем, на мой взгляд, реализация наследования организована лучше именно в последней, где в отличии от Kotlin все классы изначально открыты, и при наследовании от них, мы можем спокойно их переопределять. В Kotlin же у класса или метода должна быть пометка open для его дальнейшего переопределения. Также закрытые классы не получится проксировать. Реализация статических свойств и методов в языке тоже отсутствует, вместо этого предлагается реализация через объекты-компаньоны:

 class ExceptionHandler : Thread.UncaughtExceptionHandler { companion object { fun foo{ ….} val interval = 100 } } 

В заключении хочу отметить, что Kotlin, в целом, произвел хорошее впечатление. Он позволяет писать более простой и компактный код, особенно это актуально, когда работаешь с GUI, но в промышленной разработке я пока его не вижу.


x

Ещё Hi-Tech Интересное!

Как Яндекс применил компьютерное зрение для повышения качества видеотрансляций. Технология DeepHD

Когда люди ищут в интернете картинку или видео, они часто прибавляют к запросу фразу «в хорошем качестве». Под качеством обычно имеется в виду разрешение — пользователи хотят, чтобы изображение было большим и при этом хорошо выглядело на экране современного компьютера, ...

Security Week 36: Telnet должен быть закрыт

Telnet — это очень старый протокол. Википедия сообщает, что он был разработан в 1969 году, много лет активно использовался для удаленного доступа к компьютерам и серверам, причем как под управлением Unix/Linux, так и для систем под Windows (telnet можно было ...