Сравнение Kotlin и Java в backend-приложениях

В статье на tproger.ru бэкенд-разработчик поделился своим опытом использования Kotlin и Java. Представляем этот рассказ вашему вниманию.

Я разработчик больших и маленьких бэкенд-систем в крупных компаниях. Приходилось писать как отдельные сервисы, которые общаются с другими бэкенд-сервисами разного уровня глубины, так и сервисы, работающие в связке с фронтом. Раньше для написания кода я использовал  Java + Spring.

А после поменял проект и столкнулся с Kotlin именно в бэкенде. И хочу поделиться преимуществами Kotlin и отличиями Kotlin от Java в абсолютно одинаковых задачах.

Забегая вперёд скажу, что колоссальной разницы, из-за которой нужно срочно переписывать всё на Kotlin, нет. Но есть огромное количество фич, которые делают разработку быстрее, проще и безопаснее.

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

Простота интеграции в уже существующий проект, написанный на Java

Если вы только присматриваетесь к Kotlin для бэкенда, то учитывайте, что в окружении, которое запускает ваш проект на Java 8, можно без танцев с бубном запустить скомпилированный проект на Kotlin. Да, на той же jvm, на том же окружении и с минимумом усилий.

Для меня было открытием, что даже в рамках одного приложения могут быть классы на Java и Kotlin. Вся магия происходит на этапе компиляции. В зависимости от настроек, можно указать что собирать первым: Kotlin-классы или Java-классы.

Сейчас доступна компиляция Kotlin-исходников в байткод от Java LTS — 8, 11 и (пока экспериментально) 16.

Инициализация и логика работы с DTO классами

Пример избитый, но максимально наглядный.

data class Cat(val name: String, val color: String, val height: Int)

И теперь то же самое на Java:

public class Cat {
    private final String name;
    private final String color;
    private final Integer height;

    public Cat(String name, String color, Integer height) {
        this.name = name;
        this.color = color;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

    public Integer getHeight() {
        return height;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Cat cat = (Cat) o;
        return Objects.equals(name, cat.name) && Objects.equals(color, cat.color) && Objects.equals(height, cat.height);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, color, height);
    }
}

Мне кажется, здесь даже комментарии излишни.

Указатели data class в Kotlin по-умолчанию подразумевают наличие getter, setter (для var полей), equals, hashcode, toString для всех полей. При желании можно переопределить каждый из этих методов по-своему, но требуется это крайне редко.

class Cat(val name: String, val color: String, val height: Int) {

    override fun toString(): String = "overridden toString"
}

Null safety

В Kotlin требуется явно указывать, может ли тот или иной метод вернуть null. Таким образом, можно считать, что все данные уже обернуты в аналог Optional. И NullPointerException будет вам встречаться настолько редко, что вы успеете по нему соскучиться.

fun saveReviewNullable(r: Review): Review? = reviewRepository.save(r)

fun bar(r: Review) {
    val savedReviewNullable: Review = saveReviewNullable(r)!! // Есть риск NPE - не феншуй
    val savedReviewNotNull: Review = saveReviewNullable(r) ?: Review() // феншуй
}

Выделение основного конструктора

Суть в чём: есть основной (Primary) конструктор и вспомогательные (Secondary). Вспомогательные обязаны вызывать основной как конструктор родительского класса.

class Cat(val name: String, val color: String, val height: Int) {

    constructor(name: String) : this(
        name = name,
        color = "fixed color",
        height = 10
    )
}

Явное объявление изменяемых и неизменяемых полей

Следующее преимущество: в Kotlin получаются довольно простые и элегантные конструкции. Если требуется, чтобы поле dto можно было менять, то для его объявления используется var. Тогда будет создан метод setter и поле не будет final.

А если нужно сделать поле иммутабельным, для объявления следует использовать val. Смотрится очень красиво и просто. Плюс не нужно следить за вспомогательными методами.

Пример: поля цвета и роста можно изменить после создания, а имя — только при инициализации объекта:

data class Cat(val name: String, var color: String, var height: Int)

Immutable коллекции по умолчанию

То, что появилось в Java несколько позже, уже давно было в Kotlin — создание коллекций сразу иммутабельными.

val list = listOf("one", "two")
val map = mapOf(1 to "one", 2 to "two")
val set = setOf(1, 2 ,3)

Любые изменения с этими коллекциями будут создавать новую иммутабельную коллекцию после преобразования:

val list = listOf("one", "two")
val list2 = list.plus("three")

Но изменить какой-то элемент отдельно не получится. Для классических мутабельных коллекций используется явно изменяемые аналоги:

val list = mutableListOf("one", "two")
val map = mutableMapOf(1 to "one", 2 to "two")
val set = mutableSetOf(1, 2 ,3)

Работа со сложными классами методами примитивов

Преимущество Kotlin, которому не перестаю радоваться — возможность использовать операторы для базовых операций к сложным классам. Если нужно сложить BigDecimal числа — берёшь и пишешь их через плюс. Не нужно явно вызывать метод у первого слагаемого.

То же самое с массивами: хочешь удалить элемент из мутабельного массива – пишешь массив минус этот элемент. И если элемент присутствует, то он удаляется.

val a = BigDecimal(1)
val b = BigDecimal(2)
val sum = a + b

В Java нужно вызывать специальный метод:

BigDecimal a = new BigDecimal(1);
BigDecimal b = new BigDecimal(2);
BigDecimal sum = a.add(b);

Аналогичные приёмы работают и с более сложными классами, например с коллекциями:

val list = listOf("one", "two") - "one" // list - коллекция из элемента "two"

Возможно писать однострочные методы действительно в одну строку

Если метод простой и состоит из одной операции или из цепочки операций, записанных в одну строчку, то не обязательно писать фигурные скобки и return. Прямо пишешь:

fun getReviewByTitle(title: String): List<Review> = reviewRepository.getAllByTitle(title)

Вместо Java-варианта:

public List<Review>(String title) {
        return reviewRepository.getAllByTitle(title);
    }

Контекстные функции

Интересные движения в сторону функционального программирования в духе выделения контекста: лямбды можно вертеть, как хочешь.

Есть функции let, apply, also, with, run. Из-за их обилия вначале возникает вопрос: что подходит для конкретного кейса. Но когда привыкаешь, становится непонятно как раньше жил без них.

Простой пример: взять результат и как-то его обработать:

fun saveReview(review: Review): Review = reviewRepository.save(review)

fun bar(r: Review) = saveReview(r).let { it.comment + it.title }

Либо инициализировать объект и дополнительно проинициализировать его var поля:

class Cat(val name: String, val height: Int) {
    var color: String? = null
}

fun bar() = Cat("Fred",10).apply { color = daoService.getPopularColor() }

Кто-то может сказать, что это сахар сахарный, и будет прав. Но по себе скажу: если большое количество шаблонных вещей входит в язык, и о них не нужно постоянно думать — процесс разработки становится проще, количество ошибок — меньше. Но нужно чаще думать про код-стайл, потому что без него навертеть можно многое 🙂

Сейчас я по-прежнему продолжаю писать и видеть код на Java, но в 99% случаев это связано с образовательными программами, в которых принимаю участие: профессия Java-разработчика на Hexlet и различные вебинары для онлайн школ и курсов. Так что я постоянно сравниваю два языка и радуюсь, что рабочие проекты пишу на Kotlin.

Советую попробовать каждому — как минимум на pet-проекте — чтобы понять, подходят вам парадигмы Kotlin или нет.

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх