Привет, Хабр! Представляю вашему вниманию перевод статьи «Why you should totally switch to Kotlin» Magnus Vinther.

Я хочу рассказать вам о языке программирования под названием Kotlin и о том, почему вам стоит рассмотреть его в качестве языка для реализации вашего следующего проекта.



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

Kotlin является разработкой JetBrains — компании, разработавшей такие IDE, как IntelliJ и ReSharper. Kotlin насквозь пропитан профессионализмом разработчиков JetBrains — он является прагматичным и выразительным, что делает разработку на этом языке приятной и эффективной.

Хотя Kotlin можно компилировать в JavaScript и скоро будет возможность компилировать его в машинный код, я сфокусируюсь на основном его окружении — JVM.

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

0. Совместимость с Java


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

1. Знакомый синтаксис


Kotlin не является непонятным нердо-академическим языком. Его синтаксис, скорее всего, покажется знакомым и понятным любому программисту, имеющему опыт работы с ООП. Конечно, у синтаксиса Kotlin есть некоторые отличия от Java, такие, как переработанные конструкторы, объявления var и val, синтаксис для описания типов параметров функций и некоторые другие. Некоторые базовые нововведения представлены на следующем фрагменте кода:

class Foo {

    val b: String = "b"     // val запрещает изменять значения переменной
    var i: Int = 0          // var разрешает изменять значение переменной

    fun hello() {
        val str = "Hello"
        print("$str World")
    }

    fun sum(x: Int, y: Int): Int { //типы параметров функции и возвращаемого значения пишутся через двоеточие
        return x + y
    }

    fun maxOf(a: Float, b: Float) = if (a > b) a else b

}


2. Интерполяция строк


Интерполяция строк — встроенный в язык и более читаемый аналог String.format() из Java:

val x = 4
val y = 7
print("$x + $y = ${x + y}")  // будет выведено: "4 + 7 = 11"


3. Вывод типов


Kotlin умеет автоматически выводить типы в случаях, когда вы считаете, что это улучшит читаемость кода:

val a = "abc"                         // будет выведен тип String
val b = 4                             // будет выведен тип Int

val c: Double = 0.7                   // явное объявление типа
val d: List<String> = ArrayList()     // явное объявление типа


4. Умное приведение типов


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

if (obj is String) {
    print(obj.toUpperCase())     // в этом месте компилятору известно, что obj должен быть приведён к типу String
}


5. Интуитивно-понятные сравнения


Вам больше не нужно явно вызывать equals(), потому что с помощью оператора == теперь можно проверять структурное равенство объектов:

val john1 = Person("John")
val john2 = Person("John")
john1 == john2    // true  (структурное равенство)
john1 === john2   // false (равенство ссылок)


6. Аргументы по умолчанию


Использование аргументов по умолчанию позволяет не определять отдельно копию метода с меньшим количеством аргументов (например, при вызове метода, приведённого ниже, параметр width можно опускать):

fun build(title: String, width: Int = 800, height: Int = 600) {
    Frame(title, width, height)
}


7. Именованные аргументы


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

build("PacMan", 400, 300)                           // эквивалентен двум другим  объектам
build(title = "PacMan", width = 400, height = 300)  // эквивалентен двум другим  объектам
build(width = 400, height = 300, title = "PacMan")  // эквивалентен двум другим  объектам


8. Выражение when



switch заменён гораздо более читаемым и гибким выражением when:

when (x) {
    1 -> print("x is 1")
    2 -> print("x is 2")
    3, 4 -> print("x is 3 or 4")
    in 5..10 -> print("x is 5, 6, 7, 8, 9, or 10")
    else -> print("x is out of range")
}


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

val res: Boolean = when {
    obj == null -> false
    obj is String -> true
    else -> throw IllegalStateException()
}


9. Свойства


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

class Frame {
    var width: Int = 800
    var height: Int = 600

    val pixels: Int
        get() = width * height
}


9. Data Class


С помощью конструкции data class можно легко создать класс, являющийся POJO, реализующий методы toString(), equals(), hashCode() иcopy() без необходимости писать для этого десятки строк кода:

data class Person(val name: String,
                  var email: String,
                  var age: Int)

val john = Person("John", "john@gmail.com", 112)


11. Перегрузка операторов


Для повышения читаемости кода можно перегружать стандартные операторы:

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)


12. Деструктурирующее присваивание



Некоторые объекты (например, структуры данных, такие как map) могут быть деструктурированы для удобного прохода по ним:

for ((key, value) in map) {
    print("Key: $key")
    print("Value: $value")
}


13. Диапазоны


Диапазоны могут быть использованы для удобного перебора чисел

for (i in 1..100) { ... } 
for (i in 0 until 100) { ... }
for (i in 2..10 step 2) { ... } 
for (i in 10 downTo 1) { ... } 
if (x in 1..10) { ... }


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


Вспомните, как вы в первый раз решили отсортировать List в Java. Вы, вероятно, не смогли найти функцию sort() и были вынуждены выяснить у преподавателя или Google о методе Collections.sort(). Позднее, когда вам понадобилось преобразовать строку к верхнему регистру, вам, возможно, пришлось писать собственную вспомогательную функцию из-за незнания о StringUtils.capitalize().

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

fun String.replaceSpaces(): String {
    return this.replace(' ', '_')
}

val formatted = str.replaceSpaces()


Стандартная библиотека таким же образом расширяет функциональность старых классов из Java, например для String были добавлены следующие методы:

str.removeSuffix(".txt");
str.capitalize();
str.substringAfterLast("/");
str.replaceAfter(":", "classified");


15. Null-безопасность


Мы можем назвать язык Java почти статически типизированным. Код на Java не гарантирует, что переменная типа String содержит именно строку, а не null. Хотя мы и привыкли к этому, это несколько снижает безопасность, обеспечиваемую статической типизацией, в результате чего разработчики на Java должны регулярно думать о возможных Null-Pointer Exceptions.

В Kotlin эта проблема решается разделением типов на non-null (типы, значением которых не может быть null) и nullable (типы, значением которых может быть null). По умолчанию типы является non-null и могут сделаны nullable посредством добавления ? к типу:

var a: String = "abc"
a = null                // ошибка компиляции

var b: String? = "xyz"
b = null                // нет ошибок


Kotlin заставляет вас обязательно проверять на null значения nullable типов:

val x = b.length //ошибка компиляции: b может быть null


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

if (b == null) return
val x = b.length //ошибки не будет


Мы также можем воспользоваться безопасным вызовом метода с помощью ?., который, если значение будет null, вернёт нам null вместо выбрасывания ошибки:

val x = b?.length       // тип x - nullable Int


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

val name = ship?.captain?.name ?: "unknown"


Если всё вышеперечисленное вам не подходит и вы всё же хотите получать Null-Pointer Exception, вам нужно явно это указать:

val x = b?.length ?: throw NullPointerException()  // результат как у выражения  ниже
val x = b!!.length                                 // результат как у выражения  выше


16. Улучшенные лямбда-функции


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

val sum = { x: Int, y: Int -> x + y }   // тип: (Int, Int) -> Int
val res = sum(4,7)                      // res == 11


Несколько хитростей:
  1. Внешние скобки могут быть опущены если лямбда-функция является последним или единственным аргументом другой функции.
  2. Мы можем не объявлять явно единственный аргумент лямбда-функции — тогда он будет неявно объявлен под именем it.


Таким образом, следующие строки кода эквивалентны:

numbers.filter({ x -> x.isPrime() })
numbers.filter { x -> x.isPrime() }
numbers.filter { it.isPrime() }


И мы получаем возможность писать выразительный функциональный код — просто взгляните на него!

persons
    .filter { it.age >= 18 }
    .sortedBy { it.name }
    .map { it.email }
    .forEach { print(it) }


Система лямбда-функций в Kotlin вместе с функциями-расширениями делают его идеальным для создания DSL (Domain-Specific Languages). anko является примером DSL, созданного с целью упрощения Android-разработки:

verticalLayout {
    padding = dip(30)
    editText {
        hint = “Name”
        textSize = 24f
    }
    editText {
        hint = “Password”
        textSize = 24f
    }
    button(“Login”) {
        textSize = 26f
    }
}


17. Поддержка IDE


У вас есть несколько вариантов, если вы решить попробовать Kotlin, но я рекомендую попробовать IntelliJ — на её примере вы сможете увидеть преимущества разработки языка и IDE для него одними и теми же людьми.

Когда я впервые попробовал скопировать код на Java со Stack Overflow, я получил следующее сообщение от IDE:



На этом я и закончу. Ссылка на официальный сайт Kotlin.

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