В этом году KotlinConf keynote Андрей Бреслав упомянул, как он был приятно удивлен результатами поиска Google по ключевым словам “Kotlin love”. Разработчикам нравится работать с ним, вот почему он занял 2-е место в категории “Самый любимый язык” опроса разработчиков StackOverflow .
Вопрос в том, почему это так? Что делает этот язык привлекательным?
Действительно трудно подобрать лучшие функции Kotlin, так как в конечном итоге можно легко написать обо всем. Но это означало бы просто переписать их потрясающую документацию :). Самое важное – это то, что на самом деле может быть использовано при решении проблем реального мира ™ .
Я работал над внутренним приложением для отслеживания времени. Серверная часть написана на Java и имеет довольно сложную логику, когда дело доходит до операций со временем. Например, рабочий день определяется как 8 часов, и все, что превышает его, считается сверхурочной работой.
Примечание: На самом деле это упрощение этой проблемы, но давайте продолжим ради этого примера
Классы данных
В Java это реализовано с использованием различных библиотек и служебных функций поверх сложных структур. Что мне действительно нравится в Kotlin, так это то, что он очень выразителен и при этом остается читабельным. Например, если мы хотели бы определить структуру для Время
, мы могли бы написать простой класс данных
data class Time(val hours: Int, val minutes: Int)
Это даст вам неизменяемую структуру данных с равными
/| хэш-кодом /
toString /
получатели и
сеттеры (и другие) функции, реализованные "из коробки". Это то, что люди Java назвали бы POJO, но определено в одной строке.
Перегрузка операторов
Следующая вещь, полезная в нашем сценарии, – это сравнение одного времени с другим. Например 1:30
больше, чем 0:45
и так далее. В Java вы можете реализовать функцию Comparable
и переопределить функцию compareTo
. То же самое работает в Kotlin, но с одним большим отличием – вы можете сравнивать экземпляры с помощью операторов равенства.
data class Time(val hours: Int, val minutes: Int) : Comparable
Вы также можете переопределить другие операторы . Например, нам понадобится оператор минус
. Вы можете просто добавить это в Time
class:
operator fun minus(other: Time): Time { return convertToTime(convertToDouble(this) - convertToDouble(other)) }
и затем
println(Time(1,30) - Time(0,45)) // Time(hours=0, minutes=45)
Сопутствующие объекты
Вы можете видеть, что я использовал некоторые служебные функции преобразовать Во Время
и преобразовать в двойной
. Если вы работаете с Java, вы привыкли создавать свои собственные классы XYZ Util
, поэтому вместо создания TimeUtil
класс, мы можем использовать сопутствующий объект .
data class Time(val hours: Int, val minutes: Int) : Comparable
Совместимость с Java
Как вы можете видеть, я аннотировал методы с помощью @JvmStatic
аннотации. У Kotlin нет статических
методов, но для обеспечения идеальной совместимости с Java , он может сделать их видимыми с помощью этой аннотации. Таким образом, вы можете вызвать это на Java:
public class JavaClass { public static void main(String[] args) { System.out.println(Time.convertToTime(2.75)); } }
Вы также можете выставлять константы и другие поля, которые были бы видны в Java.
Нулевая безопасность
Kotlin не позволяет вам использовать нулевые значения по умолчанию. Однако, если вы по какой-либо причине (например, взаимодействие с Java) хотите разрешить их, вы можете определить типы параметров с помощью ?
. Давайте определим другой конструктор в Время
которое принимает только часы:
data class Time(val hours: Int, val minutes: Int) { constructor(hours: Int?) : this(hours ?: 0, 0) }
Мы определяем его с помощью ключевого слова constructor
и указываем параметр как Int?
. Но если это значение равно null
, мы хотели бы указать ноль по умолчанию. Вы можете использовать так называемый оператор элвиса ?:
который вычислит выражение и, если оно равно null, будет использовать значение справа в качестве запасного варианта.
Умный кастинг
В Java очень раздражает выполнение процедуры “check-than-cast”, когда вы сначала проверяете, является ли экземпляр объекта экземпляром
какой-нибудь класс, а затем вручную приведите его. Давайте посмотрим на реализацию Kotlin функции convertToTime
, определенной выше:
fun convertToTime(time: Number): Time { return when (time) { is Double -> { val fullHours = Math.floor(time).toInt() Time(fullHours, ((time - fullHours) * 60).toInt()) } is Float -> convertToTime(time.toDouble()) is Int -> Time(time) else -> Time(time.toInt()) } }
Возможно, вы этого не знаете, но функция Математический этаж
ожидает Дважды
в качестве параметра. Мы не применяем его явно, но поскольку мы делаем это в одной из ветвей когда
(т.е. switch
в Java), он автоматически интеллектуальный кастинг к Двойной
. И IDE выделит это для вас, чтобы вы знали об этом! То же самое относится и к вызову is Int -> Time(время)
который является интеллектуальным приведением к Int
и использует конструктор, определенный в предыдущем разделе.
Функции расширения
Мы говорили о написании служебных функций поверх наших недавно созданных классов с использованием сопутствующих объектов. Но что, если мы хотели бы добавить функциональность в существующие классы?
Например, мы хотели бы предоставить реализацию toString
для нашего класса Time, которая печатала бы 00:45
вместо класса данных по умолчанию Время(часы=0,)
:
override fun toString() = "${hours.padZeros(2)}:${minutes.padZeros(2)}"
часы
и минуты
составляют Int
свойства класса Time, но функция заполнение нулями
не существует в Int
класс. Если вы действительно хотите использовать это во всем проекте, вы можете определить функцию расширения для всего приложения |:
fun Int.padZeros(length: Int) = "${if (this.sign < 0) "-" else ""}${this.absoluteValue.toString().padStart(length, '0')}"
this
в приведенной выше функции относится к экземпляру, для которого вызывается функция, поэтому вы можете использовать ее так, как вам нравится.
Примечание: Как вы видите, Kotlin позволяет создавать мощные шаблоны строк с использованием синтаксиса, известного из других языков: “${someVar}” 🙂
Диапазоны
Давайте соберем все это вместе в следующем примере. Мы хотели бы иметь класс для Workday
, который мог бы сообщить нам, когда человек начал и завершил свой рабочий день. Одной из специальностей этого класса было бы определение того, был ли человек на работе в определенное время.
Мы можем достичь этого, используя интерфейс Closed Range
, который позволяет нам использовать этот синтаксис:
println(Time(10, 30) in Workday(Time(8), Time(16)))
Реализация довольно проста благодаря соглашениям, используемым для Закрытого диапазона
. Он ожидает, что реализация обеспечит start
и end Включительно
свойства указанного типа (который в нашем случае является Время
)
class Workday(override val start: Time, override val endInclusive: Time) : ClosedRange
Одно важное замечание заключается в том, что это работает, потому что Закрытый диапазон
ожидает, что тип реализует Сопоставимый
интерфейс, который выполняет наш класс Time
.
Функции более высокого порядка
Kotlin поддерживает некоторые идиомы функционального программирования, рассматривая функции как первоклассных граждан языка. Это означает, что вы можете использовать их либо в качестве параметров, возвращаемых типов, либо объявлять как переменные.
Например, мы хотели бы добавить функциональность, которая будет проверять параметры конструктора после инициализации класса:
class Workday(override val start: Time, override val endInclusive: Time) : ClosedRange
Здесь происходит сразу несколько вещей. Сначала мы определяем функцию является действительным
, который подтверждает, что время начала и окончания находится в пределах дневного диапазона. Затем в блоке init
(который вызывается после создания экземпляра) вызывается функция require
, которая определена в Kotlin stdlib как это:
public inline fun require( value: Boolean, lazyMessage: () -> Any ): Unit
Другими словами, он будет вычислять первый параметр, и если он равен false
он вызовет второй параметр, представляющий собой функцию (объявленную с использованием синтаксиса лямбда-выражения ), которая определяет сообщение, используемое в Исключение IllegalArgumentException
. Так этот звонок
Workday(Time(-1), Time(12))
закончится вот так:
Exception in thread "main" java.lang.IllegalArgumentException: Range `-01:00 to 12:00` does not define a valid workday. at Workday.(WorkingTime.kt:62) at WorkingTimeKt.main(WorkingTime.kt:83)
Примечание: В Kotlin, если последний параметр является лямбда-выражением, вы можете записать его после вызова функции (как в нашем примере). Но это то же самое, что require(isValid(), { “Диапазон от ${this.start} до ${this.endInclusive} не определяет допустимый рабочий день.” })
Завершая это
Есть много скрытых драгоценных камней, которые вы обнаружите, работая с Kotlin. Это позволяет вам писать лаконичный и читаемый код при работе с известными технологиями ( например, Java, JVM и его фреймворки). Если вы хотите открыть для себя их, вы можете начать с просмотра Kotlin koans , набор упражнений, которые охватывают большинство языковых функций – и вы можете делать это в браузере!
Редактировать: Вот Суть полного исходного кода , используемого в этой статье. 😉
Оригинал: “https://dev.to/rapasoft/kotlin–ld9”