Совсем недавно у меня состоялся разговор с коллегой по поводу новых языков программирования. После того, как разговор зашел о Kotlin, мой друг обронил фразу «Там нулл нельзя передавать, если не указал, что переменная может быть nullable». Эта фраза сильно озадачила меня — а действительно ли, так важны киллер фичи Котлина в сравнении с уже известными языковыми возможностями Явы? Размышления на этот вопрос вылились в целый комплекс примеров, в рамках которых я хочу показать (в первую очередь самому себе, наверно), зачем же нужны все эти новые языковые возможности.

Самая скучная часть разработки на уже обкатанном языке — рутинные задачи. Например, показ картинок. Абсолютно рядовая задача, которая в любом проекте обрастает рядом условностей. Приведу в пример несколько таких:

  • загруженную картинку нужно отобразить «в кружочке» (кружочки это очень стильно)
  • после загрузки сжать изображение до размеров отображения (нечего тратить память)
  • изображение нужно сжать до нужного размера и/или пропорции
  • в случае неудачной загрузки показать какой-нибудь ресурс по умолчанию

Абсолютно бытовые условности, которые поддерживаются многими готовыми библиотеками (например, для андроида это Picasso, Glide, etc.).

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

В самом базовом виде набор таких данных будет преставлять из себя примерно следующий класс:

public class ImageSets {
    Boolean inCircle = false;
    Boolean needFit = false;
    Size size = null;
    int defaultDrawableResource = -1;
}

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

Пример
void useCase() {
     BaseJavaClass some = new BaseJavaClass();
     some.inCircle = true;
     some.needFit = true;
     some.defaultDrawableResource = 0;
     some.size = new Size(128, 128);

     System.out.println(some.inCircle);
     System.out.println(some.needFit);
     System.out.println(some.size);
}

static ImageSets pattern(){
     BaseJavaClass some = new BaseJavaClass();
     some.inCircle = true;
     some.needFit = true;
     some.defaultDrawableResource = 0;
     some.size = new Size(128, 128);
     return some;
}


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

  • Постоянное упоминание «some.» при вызове. В случае с множественным вызовом (а здесь он как раз актуален), код сильно раздувается.
  • Для использования шаблонов нужно каждый раз создавать новый экземпляр шаблона — иначе кто-то может залезть внутрь него и что-то внутри поправить — веселье обеспечено.

С первым недугом принято бороться с помощью сеттеров (можно конечно объявить конструктор со всем набором переменных, но если в Java их больше трех, плюс некоторые из них могут быть необязательными, мы точно получим где-то в программе вызовы, похожие на Some(0,0, null, null, null, -1, 5)):

Много кода
    public BaseJavaClass setInCircle(Boolean inCircle) {
        this.inCircle = inCircle;
        return this;
    }

    public BaseJavaClass setNeedFit(Boolean needFit) {
        this.needFit = needFit;
        return this;
    }

    public BaseJavaClass setSize(Size size) {
        this.size = size;
        return this;
    }
    
    public BaseJavaClass setDefaultDrawableResource(int defaultDrawableResource) {
        this.defaultDrawableResource = defaultDrawableResource;
        return this;
    }

    void useCase(){
      BaseJavaClass some = new BaseJavaClass()
                .setInCircle(true)
                .setNeedFit(true)
                .setDefaultDrawableResource(0)
                .setSize(new Size(128, 128));
    }

    static BaseJavaClass pattern1() {
        return new BaseJavaClass()
                .setInCircle(true)
                .setNeedFit(true)
                .setSize(new Size(128, 128));
    }


Что же, допустим. Но что делать со второй проблемой? Обычно для ее решения используют вспомогательный _Bulder класс, вызов которого будет содержать сеттеры, а вызов основного класса — только геттеры:

Очень много кода
public class ImageSets {
    protected Boolean inCircle = false;
    protected Boolean needFit = false;
    protected Size size = null;
    protected int defaultDrawableResource = -1;

    public Boolean getInCircle() {
        return inCircle;
    }

    public Boolean getNeedFit() {
        return needFit;
    }

    public Size getSize() {
        return size;
    }

    public int getDefaultDrawableResource() {
        return defaultDrawableResource;
    }

    public static final ImageSets pattern = new ImageSetsBuilder()
            .setInCircle(true)
            .setNeedFit(true)
            .setDefaultDrawableResource(0)
            .setSize( new Size(128, 128));
}

public class ImageSetsBuilder extends ImageSets {
    public ImageSetsBuilder setInCircle(Boolean inCircle) {
        this.inCircle = inCircle;
        return this;
    }

    public ImageSetsBuilder setNeedFit(Boolean needFit) {
        this.needFit = needFit;
        return this;
    }

    public ImageSetsBuilder setSize(Size size) {
        this.size = size;
        return this;
    }

    public ImageSetsBuilder setDefaultDrawableResource(int defaultDrawableResource) {
        this.defaultDrawableResource = defaultDrawableResource;
        return this;
    }
}


Существует много других способов, я привел наиболее распространенные способы решения.
Что же стало с нашими данными? Как так вышло, что класс, содержащий в себе всего 4 переменных, разросся до подобных размеров? Что произойдет при добавлении переменных?
Этот же класс, но на языке Kotlin будет выглядеть следующим образом:

class ImageSets(
        val inCircle: Boolean = false,
        val needFit: Boolean = false,
        val size: Pair<Int, Int>? = null,
        val defaultDrawableResource: Int = -1)

Все! Можем использовать:

val pattern = ImageSets(
                inCircle = true,
                needFit = true,
                defaultDrawableResource = 0,
                size = 128 to 128)

        fun useCase(){
        	val some = ImageSets(
                    inCircle = true,
                    needFit = true,
                    defaultDrawableResource = 0)
                print(some.needFit)
        }

Написанный на Kotlin пример дает нам следующие преимущества:

  • Все свойства внутри класса (обозначены с помощью val ) изменяются только в конструкторе
  • Сам шаблон тоже является свойством и не может переобозначаться
  • Объявление экземпляров класса не содержит большого количества скобок и выглядит опрятнее
  • Конструктор, который содержит в себе и свойства класса, можно вызывать с использованием конструкции arg = value, что в свою очередь позволяет нам оперировать конструкторами с любым количеством принимаемых переменных.
  • Конструкция arg = value позволяет не соблюдать порядок передачи аргументов
  • Свойства не обязательно должны быть — свойство size в последнем useCase просто проигнорировано, его не нужно заменять на null, и конструктор от этого не становится непонятным набором 0, null, -1
  • В дальнейшем обращение к значениям свойств происходит так же, как и к обычным переменным.

  • Один класс против двух, один конструктор, прозрачная работа с аргументами по умолчанию, отсутствие геттеров и сеттеров, отсутствие некрасивых возвратов return this

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


  1. qwert_ukg
    16.12.2017 17:35

    А мне нравится такая запись:


    ImageSets(
        inCircle = true,
        needFit = true,
        defaultDrawableResource = 0
    ).let { print(it) }

    Приятно читать :)


    1. Guitariz Автор
      16.12.2017 17:39

      Тогда уже)

      ImageSets(needFit = true).apply{ print(needFit) }

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


  1. fogone
    16.12.2017 18:06
    +3

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


    1. Guitariz Автор
      16.12.2017 18:09
      -1

      Скоро будет продолжение) Немного вбок, но тем не менее)


  1. nerumb
    16.12.2017 18:38

    Такими статьями вы только отпугиваете желание других людей смотреть в сторону Kotlin. Мне нравится этот язык, и я считаю его прекрасным инструментом для решения повседневных задач, но то что вы привели это не аргументы. Тот же lombok убирает весь ненужный код, и получается что с ним и Kotlin не нужен?
    Дело в том что в Kotlin куда больше прекрасных возможностей для написания красивого и выразительного кода, а читая такие посты у читателей будет создаваться именно негативное впечатление.


    1. Guitariz Автор
      16.12.2017 19:32

      Есть такая поговорка — «нет еще задачи по программированию, которую нельзя решить на ассемблере».
      Я не утверждаю, что Kotlin — язык, на который непременно нужно полностью перейти с Java. Это личный выбор каждого разработчика. Если для выполнения задач человеку хватает Java — почему нет? Более того, сама парадигма котлина о совместимости с явой — утверждает то же самое.
      Lombok сам по себе весит 1400кб, значительно увеличивает время сборки, а так же сложность самого проекта. О влиянии на скорость работы конечного кода не знаю, но подозреваю, что тоже влияет. Kotlin предоставляет тот же функционал, но уже из коробки, гораздо меньшими средствами. Разве это не хорошо?


      1. izzholtik
        16.12.2017 22:29

        Lombok работает только на этапе компиляции, в конечных классах его нет.


        1. qwert_ukg
          18.12.2017 20:05

          Геттер/сеттер и поле на байткоде уж точно есть.


          1. izzholtik
            18.12.2017 22:30

            Ну так ради их генерации ломбок и запускали, нет?
            И наверняка в котлине та же картина.


      1. igormich88
        18.12.2017 13:56

        А можно пруфы по времени сборки эквивалентного кода на:

        • Чистой Java
        • Java + Lombok
        • Kotlin

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


        1. Guitariz Автор
          18.12.2017 13:59

          Есть сравнение чистой java и kotlin
          habrahabr.ru/company/badoo/blog/329026
          При инкрементальной сборке kotlin быстрее. Сомневаюсь, что при добавлении lombok проекты на java будут собираться быстрее.


        1. qwert_ukg
          18.12.2017 19:09

          Чистой Java
          Kotlin

          тут и тут посмотрите.


  1. zagayevskiy
    16.12.2017 20:53

    Статья ни о чём. О какой защищённости речь? Примеры и на джаве и на котлине на уровне "начал чего-то учить, решил написать статью".


  1. Vitaljok
    16.12.2017 21:28

    Для чистой Java есть прекрасная библиотека lombok. Ваш длиннющий пример превращается в что-то подобное:


    @Data
    @Builder
    public class ImageSets {
        Boolean inCircle = false;
        Boolean needFit = false;
        Size size = null;
        int defaultDrawableResource = -1;
    }


    1. Guitariz Автор
      16.12.2017 21:55

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


      1. Space_Cowboy
        18.12.2017 13:36

        Так и котлин отчасти сторонняя библиотека к яве. А по поводу быстродействия, и времени сборки, еще непонятно что быстрее будет java + lombok или котлин, если вы конечно не готовы поделиться ссылочкой на тесты.


        1. Guitariz Автор
          18.12.2017 13:42

          Kotlin это никак не библиотека к Java. Безусловно, он выполняется на JVM, но по поводу библиотеки — это большое заблуждение. Более подробно уже 5 лет назад разжевывалось
          habrahabr.ru/post/150104
          Могу поделиться замерами, что Kotlin по умолчанию собирается быстрее Java
          habrahabr.ru/company/badoo/blog/329026
          Тут уже было обсуждение.
          Я думаю, не стоит объяснять, почему время сборки Java с подключением lombok будет определенно больше, чем на голой java.
          По личным наблюдениям, занимаюсь обоими языками, Kotlin существенно быстрее.


          1. Guitariz Автор
            18.12.2017 13:48

            Простите, наоборот, не по умолчанию, а при инкрементальной сборке. Не успеваю поправить.


          1. Space_Cowboy
            18.12.2017 13:49

            Kotlin это никак не библиотека к Java

            Только 50% языка это расширеные классы стандартной ява библиотеки. Со своим компилятором, а дальше называйте как хотите)


            1. e_Hector
              18.12.2017 18:31

              а как же nullability на уровне системы типов?


            1. nerumb
              18.12.2017 18:45

              Только 50% языка это расширеные классы стандартной ява библиотеки

              Где-то есть статистика?


              1. Space_Cowboy
                19.12.2017 13:12
                -1

                Котлин опенсоурс, откройте его на гитхабе и гляньте если интересно.


  1. Space_Cowboy
    18.12.2017 13:44

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


    1. Guitariz Автор
      18.12.2017 13:46

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


  1. qwert_ukg
    18.12.2017 20:09
    -1

    Пару слов в защиту Котлина:


    Дизайн джавы стар! Многие механизмы давно вросли в языки (свойства, экстнешены, иммутабельность, нулабилити).


    Изза старого дизайна не срослось с выводом типов (даймонд синтакс) и вариантностью (in/out удобнее чем вилдкардс), не получилось убрать треугольные скобочки.


    Ребята из JetBrains, вроде как много лет непосредственно работают с JVM делайя шустрый комплишн для идеи. Не мало знают о и ее компиляторе, и о дестятке других в придачу (есть из чего выбирать фичи).


    Можно псиать библиотеки в рамках старого дизайна (обвешать все аннотациями, и навставлять треугольных скобочек друг в друга), а можно написать свой язык, используя накопленные знания и опыт. А за 6 лет разработки уж наверняка


    Не нужно держаться за старое, а если и нужно, то 100%-ный интероп вам поможет :)