Первые впечатления от использования Kotlin для Android

IT-копирайтер
Время чтения: 9 минут
Разработчики, знакомые c JVM не понаслышке, хорошо знают, что в мировом IT-сообществе уже долгое время пытаются найти достойную замену языку Java. До 2011 года самым подходящим кандидатом можно было считать Scala, пока не было анонсировано создание Kotlin. Исходный код Kotlin открыли в 2012 году, а в 2016 состоялся официальный релиз версии 1.0.
Kotlin появился в качестве ответа громоздкому коду Java и медленной скорости компиляции Scala. Сегодня многие известные IT-компании используют его в своих проектах, такие как Telegram, Prezi, Yandex и другие. Внимание к языку продолжает расти, подкупает его синтаксис и поддержка в IDE. Даже Jake Wharton, признанный евангелист Android, применяет Kotlin в своих проектах и тем самым привлекает Android-сообщество к использованию этого языка.
Я решил не оставаться в стороне и попробовать Kotlin на практике, чтобы самому оценить его широко обсуждаемые плюсы и минусы. В этой статье я хочу поделиться своими первыми впечатлениями об использовании Kotlin в разработке приложений под Android.
Kotlin: плюсы и минусы языка
В основном Kotlin хвалят за краткость и безопасность кода, а также за совместимость с Java и разностороннее применение. Все его заявленные достоинства перечислить сложно, я попробую сосредоточиться на основных.
Сильные стороны Kotlin
- Компилируется не только под JVM, но и под JavaScript.
- Полностью совместим с Java. Может использовать все известные Java фреймворки и библиотеки, а также отдельные модули в текущих проектах.
- Имеет открытый исходный код, поэтому можно легко найти, обозначить проблему в случае её возникновения и сообщить об этом разработчикам языка.
- Его репозиторий весит мало в отличии от того же Scala, и добавление Kotlin в проект равносильно добавлению саппорт библиотеки от Google.
- Начинается с Java 6, может использовать большую часть Java 7 и некоторые портированные элементы Java 8, поэтому легко доступен, даже если вам проблематично обновиться до новой версии JVM.
- Иммутабельность по умолчанию.
- Higher-Order Functions, т.е. функции которые принимают функции, как параметры.
- Null safety. По умолчанию в Kotlin переменные не могут принимать null, если вы явно их так не обозначите.
Если с плюсами Kotlin всё довольно прозрачно, то минусы не сразу так очевидны, но они есть.
Слабые стороны Kotlin
- Kotlin ориентирован на байткод Java 6 и не использует ряд улучшений, доступных в Java 8, например, invoke-dynamic.
- Проблемы с annotation processing.
- Отсутствуют аналоги плагинов-макросов или компиляторов, что ограничивает использование удобных макросов из Scala или плагинов наподобие Checker Framework из Java.
- При использовании Java и Kotlin совместно нужно соблюдать определённые правила совместимости.
- Некоторые состоявшиеся, проверенные решения в Android, в том числе и архитектурные, потребуется переписать из-за того, что в Kotlin можно использовать альтернативный подход.
- Язык довольно молодой и не нашёл себя в какой-то конкретной нише, хотя хорошо подходит как для Android разработки, так и для серверной.
- Из-за молодости языка нет каких-то выведенных best practices для решения конкретных задач.
Самые интересные функции
На самых полезных и интересных функциях нового языка я хотел бы остановиться отдельно. Следующие функции выгодно отличают Kotlin от других языков, применяемых в Android разработке:
*Здесь использованы примеры из официальной документации языка.
- Break, continue, return at labels:
loop@ for (i in 1..100) { for (j in 1..100) { if (...) break@loop } }
break будет вызван для внешнего цикла, а не для внутреннего, если бы оператор вызывался без метки @loop.
fun foo() { ints.forEach { if (it == 0) return@forEach print(it) } }
return будет вызван не для функции, а для цикла forEach.
- Extensions
fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' corresponds to the list this[index1] = this[index2] this[index2] = tmp } val l = mutableListOf(1, 2, 3) l.swap(0, 2)
Extensions — прямая замена всех Utils классов.
- Data classes
data class User(val name: String, val age: Int)
data classes позволяет избежать огромное количество boilerplate при создании классов моделей. В таком классе автоматически будут сформированы методы equals()/hashcode() по полям первичного конструктора, toString(), copy(), getters, setters по полям. Похожее решение есть в Java, только с большими затратами.
- Object declarations
Так выглядит синглтон в Kotlin:
object DataProviderManager { fun registerDataProvider(provider: DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ... }
Сущности классов, обозначенные через object, не могут быть созданы, и мы должны обращаться к ним напрямую:
DataProviderManager.registerDataProvider(...)
Так выглядит фабрика в Kotlin:
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } val instance = MyClass.create()
Хотя вызов данных методов выглядит как обычное обращение к статической переменной, в рантайме это — реальные объекты, и они могут реализовывать интерфейсы.
interface Factory<T> { fun create(): T } class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() } }
- Observable
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" user.name = "second" }
Вывод:
<no name> -> first
first -> second
Так мы можем реализовать наблюдателя и следить за изменениями объектов.
- Functions with named, default arguments
fun reformat(str: String, normalizeCase: Boolean = true, upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') { ... }
Подобным образом можно выполнить вызов функции, указывая лишь один аргумент
reformat(str)
или несколько
reformat(str, wordSeparator = '_')
- Higher-order functions
fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } }
Позволяет принимать функции как аргументы и возвращать их в качестве результата.
- Null safety
bob?.department?.head?.name
Такая строчка не вызовет NPE, только вернёт null, если одно из полей будет null.
val l = b?.length ?: -1
Если выражение слева не будет равно null — l запишет значение length иначе -1.
val aInt: Int? = a as? Int
Если a можно привести к Int — aInt = a, иначе null.
Разумеется, это далеко не полный список интересных возможностей Kotlin. Тем не менее, все перечисленные примеры наглядно демонстрируют мощный функционал языка и вполне способны заинтересовать разработчиков своими красивыми решениями.
Ещё одним аргументом в пользу Kotlin является его документация. Она написана очень кратко, но при этом ёмко, по делу и с хорошими примерами на сайте. Всё это позволяет разработчикам без длинных предисловий начать писать код. Для более полного, подробного изучения языка на официальном сайте можно ознакомиться с развёрнутой документацией.
Kotlin на практике
Kotlin – молодой язык, но когда задачи проекта требуют инновационных решений и есть возможность поэкспериментировать, мы пробуем задействовать его ресурсы на практике.
Примеры из личного опыта:
fun Context.checkNetwork() { if (!(connectivityManager.activeNetworkInfo?.isConnected ?: true)) throw IllegalStateException("no network") }
Позволяет расширять классы новыми функциями. На Java потребовалось бы описывать метод как static, и вызов его был бы не столь явным, как в этом случае, когда мы расширяем класс Context и вызов будет таким:
checkNetwork() <======> class CitiesAdapter(val cities : List<City>?, val onClick: (City) -> Unit) : RecyclerView.Adapter<CitiesAdapter.Holder>(){ override fun onBindViewHolder(holder: Holder, position: Int) { holder.bind(cities!!.get(position), position == cities.size - 1) } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder? { return Holder(LayoutInflater.from(parent?.context).inflate(R.layout.item_city, parent, false), onClick) } override fun getItemCount(): Int { return cities?.size ?: 0 } class Holder(view : View, val onClick: (City) -> Unit) : RecyclerView.ViewHolder(view) { fun bind(city : City, last : Boolean) { itemView.description.setText(city.name) val serverUrl = itemView.context.getString(R.string.conf_server_url) Picasso.with(itemView.context).load("${serverUrl}${city.imageUrl}").fit().centerCrop().into(itemView.image) itemView.divider.visibility = if (last) View.GONE else View.VISIBLE itemView.setOnClickListener { onClick(city) } } } } <===>
Kotlin: использовать или нет
Чем меньше кода, тем меньше багов – тем лучше результат. Это знает каждый разработчик. Если сравнить два класса, написанных под одни и те же задачи, на Java и на Kotlin, то код на Kotlin будет компактнее и короче. Это большой плюс, но далеко не решающий аргумент.
В целом я могу положительно оценить свой опыт использования Kotlin. Новый язык выглядит доступной альтернативой Java с хорошей документацией и достаточно простым для понимания содержанием. Однако он не поддерживает ряд полезных и удобных инструментов в отличие от Java. Но не стоит бояться недостатков Kotlin, лучше попробовать обойти их самостоятельно. Если не лениться и постоянно обращаться к новым, интересным решениям в разработке под Android, то Kotlin может стать редкой находкой для ваших проектов.
Комментарии