Десять функций Kotlin для ускорения разработки под Android

Опубликовано: 2022-03-11

Введение

Некоторое время назад Томаш представил разработку Kotlin для Android. Напомним: Kotlin — это новый язык программирования, разработанный Jetbrains, компанией, стоящей за одной из самых популярных Java IDE, IntelliJ IDEA. Как и Java, Kotlin — язык общего назначения. Поскольку он соответствует байт-коду виртуальной машины Java (JVM), его можно использовать бок о бок с Java, и он не приводит к снижению производительности.

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

Примечание . На момент написания этой статьи актуальными версиями были Android Studio 2.1.1. и Котлин 1.0.2.

Котлин

Устали от бесконечного Java-кода? Попробуйте Kotlin и сэкономьте свое время и рассудок.
Твитнуть

Установка Котлина

Поскольку Kotlin разработан JetBrains, он хорошо поддерживается как в Android Studio, так и в IntelliJ.

Первый шаг — установить плагин Kotlin. После успешного выполнения этого действия будут доступны новые действия для преобразования вашей Java в Kotlin. Две новые опции:

  1. Создайте новый проект Android и настройте Kotlin в проекте.
  2. Добавьте поддержку Kotlin в существующий проект Android.

Чтобы узнать, как создать новый проект Android, ознакомьтесь с официальным пошаговым руководством. Чтобы добавить поддержку Kotlin во вновь созданный или существующий проект, откройте диалоговое окно действия поиска, используя Command + Shift + A на Mac или Ctrl + Shift + A на Windows/Linux, и вызовите действие « Configure Kotlin in Project .

Чтобы создать новый класс Kotlin, выберите:

  • File > New > Kotlin file/class или
  • File > New > Kotlin activity

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

Еще один полезный элемент, который экономит много времени на наборе текста, — это расширения Kotlin. Чтобы использовать их, вы должны применить другой плагин в файле build.gradle вашего модуля:

 apply plugin: 'kotlin-android-extensions'

Предупреждение : если вы используете действие плагина Kotlin для настройки своего проекта, он поместит следующий код в ваш файл build.gradle верхнего уровня:

 buildscript { ext.kotlin_version = '1.0.2' repositories { jcenter() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }

Это приведет к тому, что расширение не будет работать. Чтобы это исправить, просто скопируйте этот код в каждый из модулей проекта, в котором вы хотите использовать Kotlin.

Если вы все настроите правильно, вы сможете запустить и протестировать свое приложение так же, как в стандартном проекте Android, но теперь с использованием Kotlin.

Экономия времени с Kotlin

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

Функция № 1: импорт статического макета

Один из наиболее распространенных шаблонных кодов в Android — это использование функции findViewById() для получения ссылок на ваши представления в действиях или фрагментах.

Существуют решения, такие как библиотека Butterknife, которые экономят часть ввода, но Kotlin делает еще один шаг, позволяя вам импортировать все ссылки на представления из макета с помощью одного импорта.

Например, рассмотрим следующий XML-макет действия:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="co.ikust.kotlintest.MainActivity"> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>

И сопутствующий код активности:

 package co.ikust.kotlintest import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) helloWorldTextView.text = "Hello World!" } }

Чтобы получить ссылки для всех представлений в макете с определенным идентификатором, используйте расширение Android Kotlin Anko. Не забудьте ввести этот оператор импорта:

 import kotlinx.android.synthetic.main.activity_main.*

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

TextView из макета импортируется как экземпляр TextView с именем, равным идентификатору представления. Не смущайтесь синтаксисом, который используется для установки метки:

 helloWorldTextView.text = "Hello World!"

Мы расскажем об этом в ближайшее время.

Предостережения :

  • Убедитесь, что вы импортируете правильный макет, иначе импортированные ссылки View будут иметь null значение.
  • При использовании фрагментов убедитесь, что импортированные ссылки View используются после вызова функции onCreateView() . Импортируйте макет в onCreateView() и используйте ссылки View для настройки пользовательского интерфейса в onViewCreated() . Ссылки не будут назначены до завершения onCreateView() .

Функция № 2: Написание классов POJO с помощью Kotlin

Что-то, что сэкономит больше всего времени с Kotlin, — это написание классов POJO (Plain Old Java Object), используемых для хранения данных. Например, в теле запроса и ответа RESTful API. В приложениях, использующих RESTful API, таких классов будет много.

В Kotlin многое сделано за вас, а синтаксис лаконичен. Например, рассмотрим следующий класс в Java:

 public class User { private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }

При работе с Kotlin вам не нужно снова писать ключевое слово public. По умолчанию все является общедоступным. Например, если вы хотите объявить класс, вы просто пишете:

 class MyClass { }

Эквивалент приведенного выше Java-кода в Kotlin:

 class User { var firstName: String? = null var lastName: String? = null }

Что ж, это экономит много времени, не так ли? Давайте пройдемся по коду Kotlin.

Kotlin экономит много текста

При определении переменных в Kotlin есть два варианта:

  • Изменяемые переменные, определяемые ключевым словом var .
  • Неизменяемые переменные, определяемые ключевым словом val .

Следующее, на что следует обратить внимание, это то, что синтаксис немного отличается от Java; сначала вы объявляете имя переменной, а затем указываете ее тип. Кроме того, по умолчанию свойства являются ненулевыми типами, что означает, что они не могут принимать null значение. Чтобы определить переменную, которая принимает null значение, после типа необходимо добавить вопросительный знак. Мы поговорим об этом и о нулевой безопасности в Котлине позже.

Еще одна важная вещь, которую следует отметить, это то, что Kotlin не имеет возможности объявлять поля для класса; могут быть определены только свойства. Таким образом, в данном случае firstName и lastName являются свойствами, которым назначены методы получения/установки по умолчанию. Как уже упоминалось, в Kotlin они оба общедоступны по умолчанию.

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

 class User { var firstName: String? = null var lastName: String? = null val fullName: String? get() firstName + " " + lastName }

Снаружи, когда дело доходит до синтаксиса, свойства ведут себя как общедоступные поля в Java:

 val userName = user.firstName user.firstName = "John"

Обратите внимание, что новое свойство fullName только для чтения (определяется ключевым словом val ) и имеет собственный геттер; он просто добавляет имя и фамилию.

Все свойства в Kotlin должны быть назначены при объявлении или в конструкторе. Бывают случаи, когда это неудобно; например, для свойств, которые будут инициализированы посредством внедрения зависимостей. В этом случае можно использовать модификатор lateinit . Вот пример:

 class MyClass { lateinit var firstName : String; fun inject() { firstName = "John"; } }

Более подробную информацию о свойствах можно найти в официальной документации.

Функция № 3: Наследование классов и конструкторы

Kotlin также имеет более лаконичный синтаксис, когда дело доходит до конструкторов.

Конструкторы

Классы Kotlin имеют первичный конструктор и один или несколько вторичных конструкторов. Пример определения основного конструктора:

 class User constructor(firstName: String, lastName: String) { }

Первичный конструктор идет после имени класса в определении класса. Если основной конструктор не имеет аннотаций или модификаторов видимости, ключевое слово конструктора можно опустить:

 class Person(firstName: String) { }

Обратите внимание, что первичный конструктор не может иметь никакого кода; любая инициализация должна выполняться в блоке кода init :

 class Person(firstName: String) { init { //perform primary constructor initialization here } }

Кроме того, первичный конструктор можно использовать для определения и инициализации свойств:

 class User(var firstName: String, var lastName: String) { // ... }

Как и обычные свойства, свойства, определенные в первичном конструкторе, могут быть неизменяемыми ( val ) или изменяемыми ( var ).

Классы также могут иметь вторичные конструкторы; синтаксис для его определения следующий:

 class User(var firstName: String, var lastName) { constructor(name: String, parent: Person) : this(name) { parent.children.add(this) } }

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

 class User(val firstName: String, val lastName: String) { constructor(firstName: String) : this(firstName, "") { //... } }

При создании экземпляров классов обратите внимание, что в Kotlin нет new ключевых слов, как в Java. Чтобы создать экземпляр вышеупомянутого класса User , используйте:

 val user = User("John", "Doe)

Знакомство с наследованием

В Kotlin все классы наследуются от Any , что похоже на Object в Java. По умолчанию классы закрыты, как конечные классы в Java. Итак, чтобы расширить класс, его нужно объявить open или abstract :

 open class User(val firstName, val lastName) class Administrator(val firstName, val lastName) : User(firstName, lastName)

Обратите внимание, что вы должны делегировать конструктору по умолчанию расширенного класса, что аналогично вызову метода super() в конструкторе нового класса в Java.

Подробнее о классах читайте в официальной документации.

Функция № 4: Лямбда-выражения

Лямбда-выражения, представленные в Java 8, являются одной из его любимых функций. Однако на Android все не так радужно, поскольку он по-прежнему поддерживает только Java 7, и похоже, что Java 8 не будет поддерживаться в ближайшее время. Таким образом, обходные пути, такие как Retrolambda, привносят лямбда-выражения в Android.

С Kotlin не требуются дополнительные библиотеки или обходные пути.

Функции в Котлине

Давайте начнем с быстрого обзора синтаксиса функций в Kotlin:

 fun add(x: Int, y: Int) : Int { return x + y }

Возвращаемое значение функции можно опустить, и в этом случае функция вернет Int . Стоит повторить, что все в Котлине — это объект, расширенный от Any , и здесь нет примитивных типов.

Аргумент функции может иметь значение по умолчанию, например:

 fun add(x: Int, y: Int = 1) : Int { return x + y; }

В этом случае функцию add() можно вызвать, передав только аргумент x . Эквивалентный код Java будет:

 int add(int x) { Return add(x, 1); } int add(int x, int y) { return x + y; }

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

 add(y = 12, x = 5)

Подробнее о функциях читайте в официальной документации.

Использование лямбда-выражений в Kotlin

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

В Java:

 view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(), "Clicked on view", Toast.LENGTH_SHORT).show(); } };

В Котлине:

 view.setOnClickListener({ view -> toast("Click") })

Ух ты! Всего одна строка кода! Мы видим, что лямбда-выражение заключено в фигурные скобки. Параметры объявляются первыми, а тело идет после знака -> . При прослушивании кликов тип параметра представления не указывается, поскольку его можно вывести. Тело — это просто вызов функции toast() для показа всплывающих уведомлений, которую предоставляет Kotlin.

Также, если параметры не используются, их можно не указывать:

 view.setOnClickListener({ toast("Click") })

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

Кроме того, если функция является последним параметром, ее можно вынести за скобки:

 view.setOnClickListener() { toast("Click") }

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

 view.setOnClickListener { toast("Click") }

Для получения дополнительной информации ознакомьтесь с книгой для разработчиков Kotlin для Android Антонио Лейва и официальной документацией.

Функции расширения

Kotlin, как и C#, предоставляет возможность расширять существующие классы новыми функциями с помощью функций расширения. Например, метод расширения, который будет вычислять хэш MD5 String :

 fun String.md5(): ByteArray { val digester = MessageDigest.getInstance("MD5") digester.update(this.toByteArray(Charset.defaultCharset())) return digester.digest() }

Обратите внимание, что имени функции предшествует имя расширенного класса (в данном случае String ), и что экземпляр расширенного класса доступен через this ключевое слово.

Функции расширения эквивалентны служебным функциям Java. Пример функции в Java будет выглядеть так:

 public static int toNumber(String instance) { return Integer.valueOf(instance); }

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

Особенность № 5: нулевая безопасность

Одна из вещей, с которой вы больше всего сталкиваетесь в Java, — это, вероятно, NullPointerException . Null-safety — это функция, которая была интегрирована в язык Kotlin и настолько неявна, что вам обычно не о чем беспокоиться. В официальной документации указано, что единственными возможными причинами NullPointerExceptions являются:

  • Явный вызов NullPointerException .
  • Используя !! оператора (о котором я объясню позже).
  • Внешний Java-код.
  • Если доступ к свойству lateinit осуществляется в конструкторе до его инициализации, будет выдано исключение UninitializedPropertyAccessException .

По умолчанию все переменные и свойства в Kotlin считаются non-null (не могут содержать null значение), если они явно не объявлены как обнуляемые. Как уже упоминалось, чтобы определить переменную, которая принимает null значение, после типа необходимо добавить вопросительный знак. Например:

 val number: Int? = null

Однако обратите внимание, что следующий код не будет компилироваться:

 val number: Int? = null number.toString()

Это связано с тем, что компилятор выполняет проверки на null . Для компиляции необходимо добавить null проверку:

 val number: Int? = null if(number != null) { number.toString(); }

Этот код будет успешно скомпилирован. В этом случае Kotlin делает в фоновом режиме то, что number становится nun-null ( Int вместо Int? ) внутри блока if.

Проверка null может быть упрощена с помощью оператора безопасного вызова ( ?. ):

 val number: Int? = null number?.toString()

Вторая строка будет выполнена только в том случае, если число не равно null . Вы даже можете использовать знаменитый оператор Элвиса ( ?: ):

 val number Int? = null val stringNumber = number?.toString() ?: "Number is null"

Если выражение слева от ?: не равно null , оно оценивается и возвращается. В противном случае возвращается результат выражения справа. Еще одна полезная вещь заключается в том, что вы можете использовать throw или return справа от оператора Elvis, поскольку они являются выражениями в Kotlin. Например:

 fun sendMailToUser(user: User) { val email = user?.email ?: throw new IllegalArgumentException("User email is null") //... }

!! Оператор

Если вы хотите, чтобы NullPointerException выбрасывалось так же, как в Java, вы можете сделать это с помощью !! оператор. Следующий код вызовет NullPointerException :

 val number: Int? = null number!!.toString()

Кастинг

Приведение выполняется с использованием ключевого слова as :

 val x: String = y as String

Это считается «небезопасным» приведением, так как оно вызовет ClassCastException , если приведение невозможно, как это делает Java. Существует «безопасный» оператор приведения, который возвращает null значение вместо того, чтобы генерировать исключение:

 val x: String = y as? String

Дополнительные сведения о приведении типов см. в разделе Type Casts и Casts официальной документации, а дополнительные сведения о null безопасности см. в разделе Null-Safety.

свойства lateinit

Есть случай, когда использование свойств lateinit может вызвать исключение, похожее на NullPointerException . Рассмотрим следующий класс:

 class InitTest { lateinit var s: String; init { val len = this.s.length } }

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

Особенность № 6: Функция with()

Функция with() полезна и поставляется со стандартной библиотекой Kotlin. Его можно использовать для экономии ввода, если вам нужно получить доступ ко многим свойствам объекта. Например:

 with(helloWorldTextView) { text = "Hello World!" visibility = View.VISIBLE }

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

Функция № 7: перегрузка оператора

С Kotlin можно предоставить пользовательские реализации для предопределенного набора операторов. Для реализации оператора необходимо предоставить функцию-член или функцию расширения с заданным именем.

Например, для реализации оператора умножения должна быть предоставлена ​​функция-член или функция расширения с именем times(argument) :

 operator fun String.times(b: Int): String { val buffer = StringBuffer() for (i in 1..b) { buffer.append(this) } return buffer.toString() }

В приведенном выше примере показана реализация бинарного оператора * для String . Например, следующее выражение присвоит значение «TestTestTestTest» переменной newString :

 val newString = "Test" * 4

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

Еще одним большим отличием от Java являются операторы == и != . Оператор == переводится как:

 a?.equals(b) ?: b === null

В то время как оператор != переводится как:

 !(a?.equals(b) ?:

Это означает, что использование == не выполняет проверку идентичности, как в Java (сравните, совпадают ли экземпляры объекта), но ведет себя так же, как метод equals() вместе с проверками null .

Для проверки личности в Kotlin необходимо использовать операторы === и !== .

Функция № 8: Делегированные свойства

Некоторые свойства имеют некоторые общие черты поведения. Например:

  • Свойства с ленивой инициализацией, которые инициализируются при первом доступе.
  • Свойства, реализующие шаблон Observable in Observer.
  • Свойства, которые хранятся на карте вместо отдельных полей.

Чтобы упростить реализацию подобных случаев, Kotlin поддерживает Delegated Properties :

 class SomeClass { var p: String by Delegate() }

Это означает, что функции получения и установки для свойства p обрабатываются экземпляром другого класса, Delegate .

Пример делегата для свойства String :

 class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, thank you for delegating '${property.name}' to me!" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$value has been assigned to '${property.name} in $thisRef.'") } }

В приведенном выше примере выводится сообщение, когда свойство назначается или читается.

Делегаты могут быть созданы как для изменяемых ( var ), так и для свойств только для чтения ( val ).

Для свойства, доступного только для чтения, должен быть реализован метод getValue . Он принимает два параметра (взято из официальной документации):

  • получатель - должен быть таким же или надтипом владельца свойства (для свойств расширения это расширяемый тип).
  • метаданные - должны быть типа KProperty<*> или его супертипа.

Эта функция должна возвращать тот же тип, что и свойство, или его подтип.

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

  • получатель - то же, что и для getValue() .
  • метаданные - такие же, как для getValue() .
  • новое значение - должно быть того же типа, что и свойство или его супертип.

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

  • Ленивый
  • Наблюдаемый
  • право вето

Ленивый

Lazy — это стандартный делегат, который принимает лямбда-выражение в качестве параметра. Переданное лямбда-выражение выполняется при первом вызове метода getValue() .

По умолчанию оценка ленивых свойств синхронизирована. Если вас не беспокоит многопоточность, вы можете использовать lazy(LazyThreadSafetyMode.NONE) { … } для повышения производительности.

Наблюдаемый

Delegates.observable() предназначен для свойств, которые должны вести себя как Observables в шаблоне Observer. Он принимает два параметра: начальное значение и функцию с тремя аргументами (свойство, старое значение и новое значение).

Данное лямбда-выражение будет выполняться каждый раз при вызове метода setValue() :

 class User { var email: String by Delegates.observable("") { prop, old, new -> //handle the change from old to new value } }

право вето

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

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

 var positiveNumber = Delegates.vetoable(0) { d, old, new -> new >= 0 }

В данном примере будут храниться только положительные числа, присвоенные свойству.

Для получения более подробной информации ознакомьтесь с официальной документацией.

Функция № 9: сопоставление объекта с картой

Обычный вариант использования — хранить значения свойств внутри карты. Это часто происходит в приложениях, которые работают с RESTful API и анализируют объекты JSON. В этом случае экземпляр карты можно использовать в качестве делегата для делегированного свойства. Пример из официальной документации:

 class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map }

В этом примере у User есть первичный конструктор, который принимает карту. Два свойства будут принимать значения из карты, сопоставленные с ключами, которые совпадают с именами свойств:

 val user = User(mapOf( "name" to "John Doe", "age" to 25 ))

Свойству name нового пользовательского экземпляра будет присвоено значение «John Doe», а свойству age — значение 25.

Это также работает для свойств var в сочетании с MutableMap :

 class MutableUser(val map: MutableMap<String, Any?>) { var name: String by map var age: Int by map }

Функция № 10: Коллекции и функциональные операции

Благодаря поддержке лямбда-выражений в Kotlin коллекции можно использовать на новом уровне.

Во-первых, Kotlin различает изменяемые и неизменяемые коллекции. Например, существует две версии интерфейса Iterable :

  • Итерируемый
  • MutableIterable

То же самое касается интерфейсов Collection , List , Set и Map .

Например, any операция возвращает true , если хотя бы один элемент соответствует заданному предикату:

 val list = listOf(1, 2, 3, 4, 5, 6) assertTrue(list.any { it % 2 == 0 })

Полный список функциональных операций, которые можно выполнять с коллекциями, см. в этом сообщении блога.

Заключение

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

  • Сообщения и книга Антонио Лейвы в блоге Kotlin.
  • Официальная документация и руководства от JetBrains.

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

Преимущества использования Котлина:

  • Поддержка Android Studio безупречна и превосходна.
  • Существующий проект Java легко преобразовать в Kotlin.
  • Код Java и Kotlin может сосуществовать в одном проекте.
  • В приложении нет накладных расходов на скорость.

Недостатки:

  • Kotlin добавит свои библиотеки в сгенерированный .apk , поэтому окончательный размер .apk будет примерно на 300 КБ больше.
  • При злоупотреблении перегрузка операторов может привести к нечитаемому коду.
  • IDE и автозаполнение ведут себя немного медленнее при работе с Kotlin, чем с чисто Java-проектами Android.
  • Время компиляции может быть немного больше.