Рубрики
Без рубрики

Котлин ❤

Демонстрируя самые крутые языковые возможности. Помеченный kotlin, jvm, java.

В этом году 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”