Привет, хабр! Сегодня я хочу рассказать про свой опты взаимодействия с языком kotlin.

Представлюсь – я java разработчик, работаю крупном банке, создаю (и поддерживаю существующие) микросервисы.

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

Итак, почему я решил изучить kotlin. Ну, во-первых, прожужали все уши, мол сокращение объема код, лаконичность, читаемость и сахар.

Во-вторых, Kotlin полностью совместим с Java. Для понимания, ниже скриншот того, как выглядит содержание одного из пакетов intellij плагина git4idea:

кусок структуры исходников плагина git4idea
кусок структуры исходников плагина git4idea

(Java и Kotlin классы идут вперемешку, код при этом остается читаемым (местами))

Да, проекты можно мигрировать с Java на Kotlin постепенно, как по мне – киллер-фича.

Так что, я подумал, почему бы и нет.

Вот вещи, к которым я привык за свои полгода пет-проектирования на kotlin, и которыми лично мне удобно пользоваться:

  1. Закрытые для расширения классы и методы. Все по умолчанию final, в отличии от Java, где, наоборот, любой класс открыт для расширения. Kotlin же придерживается принципа, описанного еще в Effective Java Дж.Блоха, который звучит так:

    "классы и методы должны быть закрыты и не подлежат расширению или переопределению, если только у нас нет веской причины для их расширения или переопределения. И когда мы решаем, что наш класс должен быть открыт для расширения, мы должны документировать последствия переопределения любого метода.» Источник

    Для того, чтобы сделать класс в kotlin расширяемым, мы должны использовать ключевое слово open, которое не обладает транзитивным свойством – это означает, что когда Class2 наследуется от open class Class1, мы не сможем унаследовать Class3 от Class2, без навешивания на него ключевого слова open.

  2. Null-safe. Возможность на уровне компиляции разрешить/запретить(по умолчанию) присвоение null в переменную – лайк.

    var a: String = "abc"
    a = null                // compile error
    
    var b: String? = "xyz"
    b = null                // no problem

  3. Геттеры и сеттеры по умолчанию. Любое поле не извлекается напрямую, более того под капотом оно является private(по умолчанию) и может быть доступно только геттеру. Когда вы пытаетесь получить поле вашего объекта, вы на самом деле вызываете метод get

    val isEmpty: Boolean

    эквивалентно следующему Java коду:

    private final Boolean isEmpty;
    
    public Boolean isEmpty() {
        return isEmpty;
    }

    Ну, и:

    var someProperty: String = "defaultValue"

    также эквивалентно:

     var someProperty: String = "defaultValue"
        get() = field
        set(value) { field = value }

    При желании, разумеется, можно ограничить доступ к геттерам или сеттерам:

     var isEmpty: Boolean = true
        private set 

    Примеры скоммуниздил

  4. Однострочные методы. Буду краток : при объявлении метода вида:

     fun doSomething(): String{
        return “doing something”
    } 

    Kotlin позволяет убрать явное объявление возвращаемого типа, фигурные скобки и ключевое слово return

    Выглядеть это начинает следующим образом:

     fun doSomething() = “doing something”

    Сначала было непривычно (особенно когда такие объявления идут после блока инициализации полей класса), но потом привык

  5. Упрощена большая вложенность при сравнении строк и любых других сложных типов в If - чуваки зашили equals в  == (начинает работать, если переопределить метод equals явно, либо если пометить класс ключевым словом data.)

    Добавлю, что если уж вам нужно сравнить именно ссылки на объекты, в kotlin есть отдельный оператор для этого: тройное равно ===

    Java:

     if (str1.equals(str2.equals(errString)? "default":str2)){
         //... 
     }

    Kotlin:

     if (str1 == if (str2 == errString) "default" else str2) {
       //... 
     }

    Сравнение классов:

    Java:

    @EqualsAndHashCode
    @AllArgsConstructor
    public class MyClass {
       private String name;
       private int value;
     }
    
     
    public static void main(String[] args) {
      MyClass first = new MyClass("name", 5);
      MyClass second = new MyClass("name", 5);
      System.out.println(first.equals(second));//true
      System.out.println(first == second);//false 
    }

    Kotlin:

    data class MyClassK(var name: String?, var value: Int)
    
    fun main() {
      val first = MyClassK("name",10)
      val second = MyClassK("name",10)
      println(first == second)//true
      println(first === second)//false
    }

                  

  6. Именованные аргументы. В сочетании с аргументами по умолчанию именованные аргументы избавляют от необходимости использовать Строителей (паттерн Builder)

    Вместо следующего Java-кода:

    @Builder //lombok annotation
    public class MyClass {
      private String name;
      private int value;
      private double rating;
    }
    
    MyClass myClass = MyClass.builder()
       .name("MyJavaObj")
       .value(10)
       .rating(4.99f)
       .build();

    На kotlin мы можем сделать так:

    class MyClassK(name: String? = null, value: Int = 0, rating:Double = 0.0)
    
    fun main() {
      var myClassK = MyClassK(value = 10, name = "MyKotlinObj", rating = 4.99)
    } 

  7. Перегрузка операторов (Operator Overloading)

    В kotlin заранее определен набор операторов, которые можно перегружать для улучшения читабельности:

    data class Vec(val x: Float, val y: Float) {
        operator fun plus(v: Vec) = Vec(x + v.x, y + v.y)
    }
    
    val v = Vec(2f, 3f) + Vec(4f, 1f)

     пример позаимствован отсюда

  8. Функции-расширения (Extension Functions)

    Если коротко: мы можем дополнить существующие классы нашим функционалом без наследования, и относиться к этому функционалу, как к родному на протяжении всей дальнейшей программы.

    fun String.format() = this.replace(' ', '_')
    
    val str = "hello world"
    val formatted = str.format()

    или, например:

     val result = str.removeSuffix(".txt")

    (В java я бы вынес это в статический метод какого-нибудь класса StringUtils)

    Удобно, не так ли? Тем более что IDE подсказывает нашу функцию среди прочих всплывающих.

  9. Работа с NPE в цепочке вызовов. Если мы хотим извлечь из сложного, составного объекта, какую-то маленькую часть, но при этом нет гарантий, что на этом пути нас не ждет null, нам приходится делать следующее:

    Java:

     try{
       Status status = journal.getLog().getRow(0).getStatus();
       if(status == null)
         throw new NullPointerException(“null status detected in log”);
     } catch(NullPointerException e){
       status == Status.ERROR;
       //logger.error(“Journal is not correct”);
     }

    Kotlin:

    var status: Status? = journal?.log?.row?.status
    status  =  status ?: Status.ERROR

    либо

    var status: Status? = journal?.log?.row?.status
    if(status.isNull()){
      status  = Status.ERROR
      logger.error(“Journal is not correct”)
    }

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

 Как же это прекрасно, что теперь я могу не бояться NPE в цепочке вызовов. В Java, как можно видеть, приходится оборачивать небезопасную цепочку вызовов в громоздкий try-cath, но черт побери, иногда я хочу, чтобы при возврате null где-то в цепочке, в переменную просто присваивался null. Именно этот функционал мне и дает kotlin.

Лирическое завершение:

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

Много нюансов и изменений, как ни крути, хотя и совместимость.
Слушал недавно несколько подкастов с гостями – разработчиками и продактами JetBrains, ребята так увлеченно рассказывали про язык, его перспективы и горизонты, мол как Java, только лучше, Kotlin Multiplatform и тд.

Но все-таки остается фактом, что массово kotlin в бэкэнд не пошел, а занял нишу мобилок, хотя может это я чего-то не знаю. Может за моей спиной в проде все тайно уже переписали свои жаба проекты на kotlin, а мне не сказали.

Да и вообще есть ли такое понятие, как основной язык? Может не надо так к этому относиться, а придерживаться принципа «под каждую задачу свой инструмент». Но тогда под какие задачи бэкэнда kotlin, а под какие java? Или все-таки “прокачанная java”?

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

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

Может вы поменяли лично свой основной язык? Почему? Сахар и отсутствие точек с запятой? Или более глобальные причины?
(Пришла в голову мысль, что мне синтаксический сахар не кажется киллер-фичей какого-либо языка, ведь когда ты привык писать так, а другие привыкли так читать, это перестает быть проблемой)

Источники:

https://habr.com/ru/companies/otus/articles/532270/ - Проверка на равенство в Kotlin

https://youtu.be/rB5Q3y73FTo?si=piObKnscuv1S9vtg – Роман Елизаров. Корутины в kotlin

https://habr.com/ru/companies/vk/articles/329294/ - обзор фич kotlin от ВК

https://stackoverflow.com/questions/37906607/getters-and-setters-in-kotlin - геттеры и сеттеры. Тред на Stack Overflow

https://www.baeldung.com/kotlin/open-keyword - ключевое слово open

https://github.com/amitshekhariitbhu/from-java-to-kotlin - прикольный репо-обучалка в формате Java vs Kotlin

https://radioprog.ru/category/183 - паттерны проектирования, без воды и шелухи

 

Комментарии (12)


  1. TerraV
    06.09.2024 21:15
    +4

    Но все-таки остается фактом, что массово kotlin в бэкэнд не пошел, а занял нишу мобилок, хотя может это я чего-то не знаю. Может за моей спиной в проде все тайно уже переписали свои жаба проекты на kotlin, а мне не сказали.

    Kotlin используется во множестве энтерпрайз компаний в качестве бэкенд языка. Знаю множество компаний в банковской сфере в Европе которые плавно переходят на Kotlin (плавно это значит начали лет 3-5 назад). В плане что java остается в Book of Standards как разрешенный инструмент, но рекомендуется к использованию Kotlin (whitelist).

    Как разработчик, который использует Kotlin 7+ лет, я могу сказать что на данный момент Kotlin не имеет принципиальных преимуществ перед Java. Да, где-то больше сахара. Да, функции расширения местами очень удобны. Да, писать свой dsl гораздо легче на Kotlin. Но каждый из этих аргументов довольно узкий.

    В целом Котлин дал мощный пинок под зад Джаве и заставил развиваться. Та же nullability на аннотация которая сейчас приходит в Java к единому стандарту это последствие влияния Kotlin.


    1. ermadmi78
      06.09.2024 21:15
      +1

      Для меня в своё время основным аргументом перехода на Kotlin стали корутины. И до сих пор я корутины считаю стратегическим преимуществом Kotlin'а. И даже Project Loom здесь ситуацию не меняет. Project Loom это про многопоточность. А корутины это про императивную асинхронность. Как говорится - не путайте тёплое с мягким.


      1. TerraV
        06.09.2024 21:15
        +1

        Корутины очень мало кто умеет правильно готовить. Как правило я вижу просто обертку runBlocking, на чем собственно преимущества и заканчиваются. Я ни в коем случае не применительно к вам, я скорее в "среднем по больнице".


        1. ermadmi78
          06.09.2024 21:15

          Да, согласен - есть такая болезнь :( Нужно нести культуру в массы...

          Но, справедливости ради хочу сказать, что когда вышла революционная Java 5.0, и вместе с ней Java Memory Model, то её тоже долгое время не умели готовить. Ни один год прошёл, прежде чем JMM стала повседневной обыденностью.


      1. youngmyn Автор
        06.09.2024 21:15

        Я что-то не понимаю, или lombok - это не многопоточность? На моей памяти был библиотекой для сокращения количества бойлер-плейт кода)


        1. ermadmi78
          06.09.2024 21:15

          :))) Sorry, засыпаю и ересь пишу :) Речь конечно же шла про Project Loom. Поправил комментарий. Спасибо!


      1. Lewigh
        06.09.2024 21:15

        Project Loom это про многопоточность.

        Нет. Это решение абсолютно тех же проблем что и корутины, просто несколько другим способом.


        1. ermadmi78
          06.09.2024 21:15

          Ох, не буду спорить. Это как спор между тупоконечниками и остроконечниками :) Можно до хрипоты ругаться, при этом обе стороны будут правы и одновременно не правы:)


  1. ermadmi78
    06.09.2024 21:15
    +2

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

    (Когда вижу в проекте файлы с функциями, отдельно от классов, периодически дергаюсь.)

    Я 18 лет программировал на Java. Последние 5 лет пишу на Kotlin. И возвращаться обратно не собираюсь. Java люблю и уважаю, но для меня она как раритетный автомобиль. Изредка выгнать из гаража, покрасоваться, потусить с такими же ценителями старины. Но в повседневной жизни предпочитаю Kotlin.

    Но все-таки остается фактом, что массово kotlin в бэкэнд не пошел, а занял нишу мобилок, хотя может это я чего-то не знаю. Может за моей спиной в проде все тайно уже переписали свои жаба проекты на kotlin, а мне не сказали.

    Вот здесь у меня разрыв шаблона. По моим ощущениям Kotlin стремительно теснит Java на backend'е. Понятно, что горы легаси на Java никто не отменял, и их ещё десятилетиями нужно будет поддерживать. Но в новых проектах Kotlin играет как минимум на равне с Java. Я к мобильной разработке никакого отношения не имею, тем не менее проблем с поиском работы не замечаю.


  1. LedIndicator
    06.09.2024 21:15
    +1

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

    Для общего развития, во-первых, а во-вторых можно внезапно попасть на проект где есть и Жаба и Котлин и Скала и градл скрипты на Груви.

    Да и вообще есть ли такое понятие, как основной язык? Может не надо так к этому относиться, а придерживаться принципа «под каждую задачу свой инструмент».

    Я с годами как-то перестал думать в категориях «основного языка». На чём надо на том и пишу. JVM-бэкенд можно наколбасить на чём угодно, в общем. Вопрос вкуса.

    Мой банк работодатель (UBS) больше ориентируется на Скалу (и правильно делает, имхо). На прошлом проекте был фанат Котлина, писавший всякие компоненты, утилиты и пр, но уволился, и Котлин в проекте зачах.

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

    В общем, да. Точки с запятой и прочий синтаксис это ерунда. Мне не лень и ресурса клавиатуры не жалко лишние пару символов дописать, тем более всё равно за меня большую часть автодополнение пишет. Главное в языке это какие-то функциональные преимущества, безопасность, ограниченность стрельбы по своей ноге и всякое такое.


  1. andrey_27
    06.09.2024 21:15

    А про минусы чего не сказали?)

    Я так не люблю Котлин. Да, есть пара нереально удобных свистоперделок, но зачем-то разработчики в одном месте уменьшают код/усилия, а в другом наоборот увеличивают, что просто бесит нереально. Многие решения довольно спорные, кому-то удобны - кому-то наоборот вредительны. Вроде и хочется использовать те крутые нововведения, но отрицательная сторона как по мне побольше будет, только затормозит и выбесит меня(


  1. panzerfaust
    06.09.2024 21:15

    Функции-расширения (Extension Functions)

    На 8 месте? Это на первом должно быть вместе с элвис-оператором. 2 эти штуки как раз и дают возможность писать короче. Без расширений не было бы

    • функции let, run, also, appy

    • полезных функторов на стандартных коллекциях

    • красивых DSL с labmds with receiver

    • возможности почти любой код написать в виде цепочки fluent api

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

    Вытаскивал из джавового монолита микросервис на 50к строк с переводом на котлин. Мигратор Intellij IDEA делает 90% работы. Лишь чуть подправлять типы за ним надо.

    Про котлин в бэкенде уже сказали выше. Уже в третьей организации подряд пишу на котлине. Проблем с поиском не было.