Kotlin
2.16K subscribers
299 photos
140 videos
18 files
435 links
Подборки полезного материала по Kotlin. По всем вопросам @evgenycarter
Download Telegram
Ktor Server Fundemantals. Часть 1

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

источник

✍️ @kotlin_lib
👍3🥰1
This media is not supported in your browser
VIEW IN TELEGRAM
Маленький экран — серьёзный вызов!

В VK мобильные разработчики создают опыт, который помещается в карман, но работает на миллионах устройств. Узнайте об их подходах к сложным задачам и ключевых результатах. По ссылке — ролики и даже вакансии!
Debounce vs Sample в Kotlin Flow

Ну что ж, пора снова погрузиться в мир Flow! Сегодня мы выносим на первый план два недооценённых инструмента: debounce и sample.

Про debounce многие из вас уже слышали, а вот про sample — гораздо реже. И, если быть честными, некоторые вообще используют debounce неправильно. Так что сейчас мы разложим всё по полочкам и сделаем эти концепции предельно понятными. Погнали! 🔥

https://proandroiddev.com/debounce-vs-sample-in-kotlin-flow-a89b4a94c893

✍️ @kotlin_lib
👍2
Kotlin: val != Immutable? 🤔

Многие новички (и не только) живут с убеждением, что ключевое слово val гарантирует неизменяемость данных. Но так ли это на самом деле?

В недавней статье на ProAndroidDev разбирают популярное заблуждение: val - это read-only (доступ только для чтения), но никак не immutable (неизменяемость).

Вот два кейса, когда ваш «неизменяемый» val может измениться:

1️⃣ Изменяемость самого объекта
val гарантирует только то, что ссылка на объект останется той же. Но если объект внутри изменяемый - его состояние можно менять без проблем.


val list = mutableListOf(1, 2, 3)
list.add(4) // Ссылка та же, содержимое изменилось


2️⃣ Кастомные геттеры
Это самый коварный момент. Свойство val может возвращать разные значения при каждом обращении, если у него переопределен get().


val random: Int
get() = Random.nextInt()


В статье также приводят интересную статистику: в опросе 41% разработчиков ответили, что считают val именно immutable, что технически неверно.

По-настоящему неизменяемым объект становится только тогда, когда он состоит из примитивов или других неизменяемых объектов (например, Data Class, где все поля val и нет ссылок на мутабельные типы).

https://proandroiddev.com/the-val-property-immutable-in-kotlin-2e4cf49207d0

✍️ @kotlin_lib
👍3🤡3🫡1
🩸 Value Classes: Когда «оптимизация» убивает перформанс

Мы привыкли думать, что value class - это серебряная пуля для Type Safety без оверхеда. Обернули Int в UserId, и в рантайме это просто int, верно?

Не всегда. Есть сценарии, где value class начинает вести себя хуже, чем обычный data class, порождая скрытый боксинг на ровном месте.

Рассмотрим классический кейс: попытка написать обобщенный обработчик.


@JvmInline
value class UserId(val id: Int)

fun <T> handle(item: T) {
// Какая-то логика
println(item)
}

fun main() {
val uid = UserId(42)
handle(uid) // <--- Здесь происходит магия (плохая)
}



Что происходит под капотом?

В момент вызова handle(uid) компилятор сталкивается с проблемой. Функция handle ожидает T (который в JVM стирается до Object), а UserId в скомпилированном виде - это примитив int.

Чтобы передать int туда, где ожидается Object, JVM обязана его упаковать.

1. Создается экземпляр-обертка UserId в куче (heap allocation).
2. Этот объект передается в функцию.
3. Если внутри функции мы снова кастуем его к конкретному типу, происходит анбоксинг.

Еще хуже: Интерфейсы

Если ваш value class реализует интерфейс, и вы работаете с ним через ссылку на этот интерфейс - вы гарантированно получаете боксинг.


interface Identity {
fun raw(): Int
}

@JvmInline
value class GroupId(val id: Int) : Identity {
override fun raw() = id
}

fun process(id: Identity) { ... } // 100% боксинг при вызове



Почему это важно?

Если вы используете value classes в горячих циклах (hot paths) или высоконагруженных стримах, думая, что экономите память, использование их в качестве дженериков или через интерфейсы приведет к обратному эффекту:

🖤GC Pressure: Вы создаете короткоживущие объекты-обертки тысячами.
🖤Снижение производительности: Аллокация + боксинг/анбоксинг стоят дороже, чем передача ссылки на обычный объект.

Как лечить?

1. Избегайте интерфейсов на value classes, если критична производительность.
2. Специализируйте функции. Вместо fun <T> handle(t: T) пишите перегрузки для конкретных value типов, если это возможно.
3. Ждите Project Valhalla. Настоящие примитивные классы в JVM решат эту проблему фундаментально, но пока мы живем с ограничениями Type Erasure.

Итог: value class идеален для доменной модели и сигнатур функций, но будьте предельно осторожны, как только они попадают в полиморфный контекст.

#kotlin #performance #jvm #senior

✍️ @kotlin_lib
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42
💀 ThreadLocal в мире Coroutines: Цена магии

Все знают: ThreadLocal привязан к потоку. Корутины могут прыгать между потоками. Это конфликт.
Большинство знает решение: asContextElement().
Но немногие задумываются, как именно это работает и почему злоупотребление этим механизмом может просадить пропускную способность сервиса.

Проблема: M:N Threading

В мире корутин один и тот же бизнес-процесс может начать выполняться на Thread-1, приостановиться (IO), и продолжить на Thread-2.
Если вы положили данные в ThreadLocal (например, RequestID для логирования) и не передали их в контекст корутины - после саспенда вы эти данные потеряете. Или, что хуже, прочитаете "грязные" данные от другой корутины, которая использовала этот поток ранее.

Решение: ThreadContextElement

Kotlin предлагает мост: ThreadLocal.asContextElement(value).
Выглядит просто, но под капотом происходит постоянное перекладывание данных.


val myTL = ThreadLocal<String>()

launch(Dispatchers.Default + myTL.asContextElement("foo")) {
println(myTL.get()) // "foo"
delay(10) // Suspend -> поток освобождается
println(myTL.get()) // "foo" (хотя поток может быть уже другим)
}



Что происходит в DispatchedContinuation?

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

1. Mount (updateThreadContext): Перед выполнением блока кода значение из контекста корутины принудительно устанавливается в ThreadLocal текущего потока. Старое значение потока запоминается.
2. Execution: Код корутины выполняется.
3. Unmount (restoreThreadContext): Как только корутина снова саспендится или завершается, в ThreadLocal потока возвращается старое значение.

Это гарантирует, что мы не загрязняем потоки пула.

📉 Скрытый оверхед (Performance Penalty)

Это не бесплатно. Если у вас в стеке корутины 5 элементов ThreadContextElement (MDC, SecurityContext, Tracing, TenantId и т.д.), и ваша корутина делает 100 саспендов (например, частые мелкие IO или yield()), то 100 раз произойдет цикл:
Save Old State -> Set New State -> Run -> Restore Old State.

На высоконагруженных системах (High Throughput) это создает ощутимое давление, так как эти операции часто включают работу с ThreadLocalMap, что не так быстро, как доступ к регистрам.

Best Practices для Senior-разработчика

1. Не используйте ThreadLocal как шину данных. Передавайте явные параметры в функции (context receivers в будущем помогут).
2. Ограничьте Scope. Используйте withContext(myTL.asContextElement()) только вокруг тех участков кода, где это действительно нужно (например, вызов легаси библиотеки, зависящей от ThreadLocal), а не на уровне всего GlobalScope или корневой корутины.
3. MDC Integration. Для логирования лучше использовать специализированные библиотеки, которые умеют эффективно работать с контекстом, или копировать контекст только на границах системы, а не таскать его везде.

Вывод: asContextElement - это костыль для совместимости с Thread-bound миром. В чистом Coroutine-мире данные должны течь через аргументы или CoroutineContext, но не через ThreadLocal.

#kotlin #coroutines #concurrency #jvm #senior

✍️ @kotlin_lib
3👍3
Скрытый боксинг value-классов. Мы всё еще аллоцируем?

Все любят value class (бывшие inline-классы). Обертка без оверхеда, типизация примитивов, красота. Но сеньор должен знать, когда эта магия ломается.

Смотрим код:


@JvmInline
value class UserId(val id: String)

fun processUser(id: UserId) { ... } // (1)
fun <T> logItem(item: T) { ... } // (2)

val uid = UserId("101")
processUser(uid) // Всё ок, передается String
logItem(uid) // 💥 А вот тут — боксинг!



Почему?
В строке (2) функция принимает дженерик T. JVM не знает, что там внутри value class, и вынуждена создать объект-обертку (instantiate UserId), чтобы передать его как Object.

Где еще ловим боксинг?

1. Использование внутри коллекций (List<UserId>).
2. Реализация интерфейсов (value class UserId : IEntity).
3. Nullable-обертки над примитивными value-классами (value class Code(val x: Int) -> Code?).

Вывод: Используйте value class для доменной выразительности, но не обманывайте себя, что это всегда zero-allocation. Если гонитесь за перформансом в горячих циклах, проверяйте байт-код или профайлер.

✍️ @kotlin_lib
👍3