Kotlin Developer
6.38K subscribers
192 photos
7 videos
264 links
Самый топовый канал по Kotlin

По вопросам сотрудничества и рекламы: @NadikaKir

Мы на бирже: https://telega.in/c/KotlinSenior
Download Telegram
Разница между лямбда-выражением и анонимной функцией

1. Синтаксис

Лямбда-выражения определяются заключением их в фигурные скобки в виде { параметры -> тело }.
Анонимные функции определяются через ключевое слово fun как обычные функции, хотя не имеют имени.

2. Поведение оператора return без метки

• В лямбда-выражении использование оператора return без метки приводит к возврату из обрамляющей (внешней) функции, а не из самого лямбда-выражения (т.е. полностью завершает работу этой функции и код, указанный после оператора return никогда не выполнится). Это называется нелокальным возвратом (non-local return), и может иметь неожиданное поведение и привести к ошибкам. В лямбда-выражениях рекомендуется использовать метки для явного указания точки возврата.

• В анонимной функции return без метки приводит к возврату только из самой анонимной функции (а не из внешней функции), продолжая выполнение кода после вызова анонимной функции в обрамляющей функции. Анонимные функции ведут себя как ожидается для классических функций с явным оператором return.

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

3. Поведение оператора return с меткой

Оператор return с меткой позволяет указать точное место, из которого нужно вернуться при вызове return.

• Если использовать return@label в лямбда-выражении, то возврат будет осуществляться из конкретной лямбды, к которой применена метка. Вместо нелокального возврата, который происходит при использовании return без метки, return с меткой завершит только ту лямбду, которая соответствует указанной метке, и выполнение кода продолжится после этой лямбды во внешней функции. Метка позволяет читать и понимать код проще, так как явно указывает, откуда происходит возврат.

• В анонимных функциях return без метки уже осуществляет возврат из самой анонимной функции. Однако, при использовании метки return@label вы также можете контролировать возврат из анонимной функции в сложных сценариях (например, при работе с несколькими вложенными функциями).

В обоих случаях использование оператора return с меткой показывает точку возврата и делает код более явным и контролируемым.
Ну, Тинькофф как всегда: предлагает крутые условия для опытных ИТ-специалистов. Будут масштабные финтех-задачи, продукты для 30 млн пользователей и команда единомышленников.

А работать можно прямо там, где живете: у Тинькофф 25 ИТ-хабов в городах России, в Армении, Беларуси и Казахстане. Детали тут
Что такое функциональный тип, какие у него ограничения?

Язык Kotlin допускает объявлять тип анонимных функций или лямбда выражений — функциональный.

Функциональный тип — это тип данных, который позволяет работать с функциями как с обычными объектами, передавать функции в качестве аргументов и возвращать их из функций. Синтаксис функционального типа в Котлин представлен списком типов параметров, разделенных запятой, затем оператором -> и типом возвращаемого значения функции.

Пример функционального типа: (a: Int, b: Int) -> Int
Здесь функциональный тип описывает функцию с двумя параметрами типа Int и возвращаемым значением типа Int.

Функциональный тип может быть использован для создания переменных, которые могут хранить ссылки на функции. А также поддерживает перегрузку, что позволяет иметь несколько функций с различными сигнатурами, но с одинаковым именем.

Ограничения функционального типа:

• Тип передаваемой функции должен быть определен явно, чтобы компилятор мог проверить типы аргументов и возвращаемых значений.
• Функциональный тип может содержать только один тип возвращаемого значения.
• Функциональный тип не может содержать более 22 параметров из-за ограничения JVM.
• Функциональный тип не поддерживает неявные преобразования типов.

Несмотря на эти ограничения, функциональные типы позволяют обрабатывать функции как объекты, что повышает гибкость и выразительность кода. Пример определения функционального типа:

// определение функционального типа
typealias Operation = (Int, Int) -> Int

// использование функционального типа
fun calculate(op: Operation, a: Int, b: Int): Int {
return op(a, b)
}

// пример вызова функции calculate
val sum: Operation = { x, y -> x + y }
calculate(sum, 10, 5) // результат: 15

Код из примера определяет функциональный тип Operation, который представляет собой функцию, принимающую два аргумента типа Int и возвращающую значение типа Int. Затем создается функция calculate, которая принимает три параметра: функцию op типа Operation и два аргумента типа Int. Внутри функции calculate вызывается переданная функция op с переданными аргументами a и b, и результат возвращается из функции calculate. В конце кода создается переменная sum, которая содержит лямбда-выражение, реализующее операцию сложения. Далее вызывается функция calculate с параметрами sum, 10 и 5, что приводит к вызову функции sum с аргументами 10 и 5, и результатом является число 15.
Как работают SAM-conversions?

Single Abstract Method (SAM) интерфейсы — это интерфейсы только с одним абстрактным методом (функциональные интерфейсы). Kotlin поддерживает соглашение SAM — автоматическую конвертацию функций и lambda между Kotlin и Java.

SAM-conversions позволяют использовать Java-интерфейсы с единственным абстрактным методом в Kotlin, как если бы это были функциональные типы. В Kotlin вы можете использовать такие интерфейсы для создания лямбда-выражений без явного определения функционального типа.

При использовании интерфейса с единственным абстрактным методом в качестве функционального интерфейса в Java, вы можете передавать его экземпляры вместо лямбда-выражений. Это тоже возможно в Kotlin, но на самом деле Kotlin предоставляет более простой синтаксис для этого. Когда вам нужно использовать функциональный интерфейс в Kotlin, вы можете передать lambda-выражение, которое соответствует сигнатуре единственного метода интерфейса, вместо экземпляра интерфейса. Компилятор сам преобразует лямбда-выражение в экземпляр интерфейса, используя функцию-расширение метода invoke интерфейса. Пример:

interface OnClickListener {
fun onClick(view: View)
}

class Button {
fun setOnClickListener(listener: OnClickListener) {
// ...
}
}

val button = Button()
button.setOnClickListener { view ->
// обработка нажатия кнопки
}

В этом примере мы определяем интерфейс OnClickListener с единственным абстрактным методом onClick. Затем мы создаем класс Button, который может иметь слушатель, реализующий данный интерфейс. После этого мы создаем экземпляр Button и передаем лямбда-выражение с соответствующей сигнатурой в качестве слушателя. Компилятор автоматически преобразует это лямбда-выражение в экземпляр интерфейса OnClickListener, используя функцию-расширение invoke интерфейса.
Koin: Простой и легковесный фреймворк для внедрения зависимостей

Принцип внедрения зависимостей становится все более неотъемлемой частью процесса разработки. Без него сложно представить себе достижение желанного разделения обязанностей в коде или обеспечение должного уровня тестируемости.

В то же время, хотя Spring Framework и является широко распространенным выбором, он далеко не всем подходит. Некоторым было бы предпочтительнее использовать более простые и легковесные фреймворки с продвинутой поддержкой асинхронных операций ввода-вывода. Другие были бы признательны за статическое разрешение зависимостей для более быстрого запуска приложения.

Читать статью
Указатели на функции (Function references, Bound callable references)

В языке Kotlin есть возможность работать с функциями как с объектами. Функции можно сохранять в переменные, передавать как аргументы и возвращать из других функций. Для этого можно использовать функциональные ссылки (Function references), которые представляют собой указатель на функцию.

1. Function references

Синтаксис функциональной ссылки имеет следующий вид:
::function_name. Указатели на функции представляют собой сокращенную форму записи вызова функции. Вместо того, чтобы объявлять лямбда-выражение и передавать его как аргумент функции, можно использовать ссылку на существующий метод. Например, у нас есть класс Person с методом getName():

class Person(val name: String) {
fun getName(): String = name
}

Тогда мы можем использовать указатель на метод getName() вместо лямбда-выражения:

val persons = listOf(Person("Alice"), Person("Bob"))
val names = persons.map(Person::getName)

2. Bound callable references

Bound callable references (привязанные ссылки)
— это то же самое понятие, что и указатели на методы, но в случае, когда метод вызывается на экземпляре класса. В этом варианте мы можем использовать ссылку на метод, связанную с конкретным экземпляром класса. Для создания привязанной ссылки на метод используется следующий синтаксис: <object_name>::<method_name>.

Допустим, что у нас есть экземпляр класса person типа Person. Тогда мы можем использовать ссылку на метод getName() для получения его имени:

val person = Person("Alice")
val name = person::getName

Здесь name будет ссылаться на метод getName() объекта person.
Задачи про PEG-парсеры

Когда-то я хотел сделать контест по парсингу для Codeforces. Придумал задания двух типов:

1. Дается неформальное описание языка, по которому нужно создать грамматику (например, "язык с правильными скобочными последовательностями")

2. Даны примеры строк в языке, по которым нужно восстановить грамматику

У обоих типов заданий есть свои проблемы, так что контест я не сделал.

В итоге я сделал игру программу, в которой можно решать задания второго типа, при этом проверять строки на принадлежность угадываемому языку.

Читать статью
Что такое inline функции, в чем их преимущество?

В Kotlin есть два типа функций: обычные и встроенные. Обычные функции похожи на функции в других языках программирования. Но встроенные функции имеют модификатор inline. Это позволяет компилятору подставить тело функции прямо в место её вызова.

Как работают inline функции?

Использование анонимных функций (лямбда-выражений) в Kotlin приводит к дополнительным затратам памяти. При использовании лямбда-выражения создается объект FunctionN (где N — количество параметров в лямбда-выражении), который содержит ссылку на само лямбда-выражение и может содержать захваченные переменные. При передаче лямбда-выражения в качестве параметра метода также создается новый объект FunctionN, что приводит к дополнительным затратам памяти.

Поэтому, чтобы избежать создания дополнительных объектов при передаче лямбда-выражений в функцию в качестве параметра, можно использовать встраивание (inline). Ключевое слово inline позволяет компилятору подставить тело функции непосредственно в место её вызова, вместо того, чтобы создавать объекты функций. Таким образом можно уменьшить затраты на создание объектов и улучшить производительность приложения.

Пример синтаксиса inline-функций с лямбдой:

inline fun functionName(parameter1: Type1, parameter2: Type2, ..., parameterN: TypeN, block: () -> Unit): ReturnType {
// function body
}

Модификатор inline влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены в место вызова.
Ускоряем поиск по коду в Android Studio

Рассмотрим возможности Android Studio, позволяющие быстрее ориентироваться в коде: находить нужные фрагменты и выявлять связи между ними.

Если вы опытный пользователь, то вам известно большинство нижеперечисленных лайфхаков. Но я узнавал о многих возможностях случайно, через многие месяцы работы в Android Studio, поэтому хочу рассказать всё, везде и сразу.

Читать статью
Модификатор noinline

Если же вы хотите, чтобы некоторые лямбды, переданные inline-функции, не были встроены, то отметьте их модификатором noinline.

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}

Разница между ними в том, что встраиваемая лямбда может быть вызвана только внутри inline-функции, либо может быть передана в качестве встраиваемого аргумента. В то время как с noinline-функциями можно работать без ограничений: хранить внутри полей, передавать куда-либо и т.д.
Создаем нативное Kotlin приложение на Spring Boot Native, Gradle и GraalVM без докера под MacOS и Windows

В этой статье я хочу рассказать о практическом опыте нативной компиляции production приложения, написанного на Kotlin со Spring Boot, Gradle с использованием GraalVM . Начну сразу с минусов и плюсов самой возможности нативной компиляции и где она может быть полезна, и дальше перейду уже непосредственно к процессу сборки под MacOS и Windows.

В конце статьи я более подробно расскажу о проекте и почему возникла такая необходимость, учитывая довольно много ограничений и подводных камней поддержки нативной компиляции как со стороны Spring Boot, та и со стороны GraalVM.

Читать статью
Что такое нелокальный return?

В Котлин non-local return — это механизм, который позволяет выйти из внешней функции или лямбда-выражения и вернуться к вызывающему коду, обходя оставшуюся часть текущей функции или лямбда-выражения. Он работает по-разному в зависимости от того, является ли функция inline или не-inline.

В не-inline функциях:

Если внутри функции есть лямбда-выражение, non-local return из лямбда-выражения может привести к нелокальному завершению внешней функции.

Для использования non-local return внутри лямбда-выражения в не-inline функции, необходимо использовать метку (label) и оператор
return@label.

В inline-функциях:

В inline-функциях, лямбда-выражения становятся частью кода функции и имеют локальный контроль над потоком управления.

Оператор
return внутри лямбда-выражения в inline-функции приведет только к завершению самого лямбда-выражения, не влияя на внешнюю функцию.
Реализуем современный UI на Jetpack Compose

Всем привет, на связи Никита Пятаков, Android-разработчик в МТС Диджитал. В этой статье я расскажу вам о том, как в приложении Мой МТС была проведена работа над UI новой карточки услуги. Рассказ мой будет последовательным – сначала про саму задачку, потом про решение, которое разбито на подпункты.

Читать статью
Как работает модификатор crossinline?

crossinline
— ключевое слово, которое используется для указания, что лямбда-выражение не может содержать нелокальных return, даже если оно передано в inline-функцию.

Когда мы передаем лямбда-выражение в функцию в качестве параметра, мы можем использовать оператор return внутри лямбды, чтобы выйти из цикла или функции, в которой вызывается лямбда. Однако, если мы передаем лямбда-выражение в inline-функцию, код лямбда-выражения может быть вставлен прямо в место вызова функции. В этом случае, если в лямбде используется оператор return, это может привести к выходу из внешней функции, что не всегда желательно.
Protobuf или Reflection в JNI

Вы когда нибудь задумывались, на сколько grpc быстрый. Да в сети, ему равных нет. Если вы гоняете маленькие сообщения, которые надо быстро доставить, то лучше grpc попросту не найти ( найти, но по мнению автору protobuf остается движком сериализации, поддерживающим большее кол-во языков ). Но насколько он хорош? Сможет ли он к примеру сравнится просто с нативными вызовами?

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

Читать статью
Когда нужно использовать crossinline, а когда noinline?

Модификаторы crossinline и noinline используются для управления поведением лямбда-выражений, переданных в качестве параметров функций. Они позволяют указывать, может ли лямбда-выражение содержать операторы return.

• Модификатор crossinline используется для указания того, что лямбда-выражение не может содержать операторы return, даже если функция, принимающая лямбда-выражение, инлайновая.

• Модификатор noinline, с другой стороны, указывает на то, что лямбда-выражение может быть сохранено как объект функции, а не выполнено внутри вызывающей функции. Это может быть полезно в случае, когда вы хотите использовать лямбда-выражение где-то ещё, например, как параметр для другой функции.

ВЫВОД: crossinline должен использоваться только тогда, когда вы уверены в том, что оператор return не будет использоваться внутри лямбда-выражения. Если лямбда-выражение должно содержать оператор return, то следует использовать ключевое слово noinline вместо crossinline.
NoArchitecture Kotlin Compose

Статья про основные моменты использования Compose в Android разработке на примере простого приложения. Три таба в одном Activity. Обращение в сеть, парсинг Json. Немного анимации. Приложение сделано на коленке за пару дней.

Читать статью
Ключевое слово reified

reified
— это ключевое слово, которое может быть использовано только в inline-функциях. reified позволяет получить информацию о типе generic-параметра во время выполнения программы. В обычном случае, информация о типах стирается и недоступна во время выполнения, но с помощью reified можно сохранять эту информацию и использовать в других частях приложения.

Несколько простых примеров применения:

1. Получить доступ к типу параметра во время выполнения

fun main() {
printType<String>() // String
printType<Int>() // Int
}

private inline fun <reified T> printType() {
println(T::class.simpleName)
}

В этом примере мы определяем функцию printType() с типовым параметром T, который мы указываем с помощью reified. Внутри функции мы можем получить тип T во время выполнения, используя T::class. Затем выводим название типа на экран с помощью simpleName. Когда мы вызываем функцию printType() с типом String или Int, она выводит соответствующий тип на экран.

2. reified вместе с is для проверки типа аргумента во время выполнения

fun main() {
println(isOfType<Int>(1)) // true
println(isOfType<Int>("Hello")) // false
}

private inline fun <reified T> isOfType(value: Any): Boolean {
return value is T
}

Здесь мы определяем функцию isOfType(), которая принимает значение типа Any и возвращает true, если оно является типом T. Мы используем reified, чтобы получить доступ к типу T во время выполнения. Затем мы используем оператор is для проверки типа значения и возвращаем соответствующее boolean значение.

3. Получить список элементов перечисления

enum class Color { RED, GREEN, BLUE }

fun main() {
printEnumValues<Color>() // RED, GREEN, BLUE
}

private inline fun <reified T : Enum<T>> printEnumValues() {
enumValues<T>().forEach { value ->
println(value)
}
}

Определяем функцию printEnumValues(), которая выводит список элементов перечисления типа T. Мы применяем reified, чтобы получить доступ к типу T во время выполнения. Затем используем enumValues<T>(), чтобы получить список всех значений перечисления типа T. Внутри цикла выводим каждое значение на экран. Когда мы вызываем функцию printEnumValues() с типом Color, она выводит "RED", "GREEN" и "BLUE" в консоль.
Библиотека Scout — быстрый и безопасный DI на Kotlin

Привет! Меня зовут Александр Миронычев. Я занимаюсь инфраструктурой приложения Яндекс Маркет под Android. Около двух лет назад при работе над модульностью у меня появилось желание написать собственную библиотеку для внедрения зависимостей, которая позволила бы ускорить сборку приложения и упростить процесс модуляризации. Так появился Scout. Сегодня его код мы выложили в открытый доступ.

Эта статья — рассказ о том, как пройти путь от безумной идеи до конкурентоспособного опенсорс-фреймворка. Статья будет полезна тем, кто ищет замену DI-фреймворку в своем проекте, а также тем, кто мечтает написать свою библиотеку, но никак не может начать.

Читать статью
Почему reified возможно использовать только с inline-функциями?

Ключевое слово reified используется только с inline-функциями, т.к. оно позволяет получить доступ к информации о типе-параметре на этапе выполнения программы, что невозможно для обычных (non-inline) функций.

inline-функции в Kotlin позволяют копировать тело функции непосредственно в вызывающий код. Это позволяет избежать накладных расходов на создание объектов и вызовы функций при каждом вызове.

Именно reified в комбинации с inline позволяет сохранить информацию о типе-параметре и передать ее внутрь функции в рантайме, что было бы невозможно без inline.

Также стоит отметить, что ключевое слово reified можно применять только с обобщенными типами (дженериками).