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

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

Владимир Иванов Август 30, 2016

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

Разработчики, знакомые 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 может стать редкой находкой для ваших проектов.

Комментарии

комментарии