ch
Feedback
Kotlin | Вопросы собесов

Kotlin | Вопросы собесов

前往频道在 Telegram

Сайт: https://easyoffer.ru/ Все каналы: t.me/+xGeAw6ckJ4liYzQy Контакт для рекламы: @easyoffer_adv

显示更多
2 444
订阅者
-124 小时
-167 天
-2730 天
吸引订阅者
九月 '26
九月 '26
+4
在0个频道中
八月 '26
+25
在0个频道中
Get PRO
七月 '26
+27
在0个频道中
Get PRO
六月 '26
+23
在1个频道中
Get PRO
五月 '26
+20
在0个频道中
Get PRO
四月 '26
+30
在0个频道中
Get PRO
三月 '26
+32
在0个频道中
Get PRO
二月 '26
+46
在0个频道中
Get PRO
一月 '26
+54
在0个频道中
Get PRO
十二月 '25
+23
在0个频道中
Get PRO
十一月 '25
+42
在0个频道中
Get PRO
十月 '25
+38
在0个频道中
Get PRO
九月 '25
+44
在0个频道中
Get PRO
八月 '25
+47
在0个频道中
Get PRO
七月 '25
+35
在0个频道中
Get PRO
六月 '25
+46
在0个频道中
Get PRO
五月 '25
+55
在0个频道中
Get PRO
四月 '25
+57
在0个频道中
Get PRO
三月 '25
+110
在1个频道中
Get PRO
二月 '25
+105
在2个频道中
Get PRO
一月 '25
+159
在53个频道中
Get PRO
十二月 '24
+59
在0个频道中
Get PRO
十一月 '24
+70
在0个频道中
Get PRO
十月 '24
+210
在42个频道中
Get PRO
九月 '24
+608
在251个频道中
Get PRO
八月 '24
+147
在0个频道中
Get PRO
七月 '24
+203
在54个频道中
Get PRO
六月 '24
+492
在244个频道中
Get PRO
五月 '24
+563
在189个频道中
Get PRO
四月 '24
+461
在23个频道中
日期
订阅者增长
提及
频道
03 九月+1
02 九月+3
01 九月0
频道帖子
🤔 Что такое сущность Observable? Это объект, который может производить данные (события) и передавать их подписчикам (Observer). Он используется для работы с асинхронными потоками данных. Основные возможности: 1. Генерация данных. 2. Обработка данных через операторы. 3. Управление подпиской и отпиской. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний

2
🤔 Что такое утечки памяти? Это ситуация, когда приложение выделяет память для каких-либо объектов, но затем не освобождает её, даже когда эти объекты больше не нужны. Это приводит к тому, что используемая память накапливается, что может со временем замедлить работу приложения и привести к его аварийному завершению (крашу) из-за нехватки доступной памяти. 🚩Почему происходят утечки памяти в Android? В Android утечки памяти могут возникнуть из-за особенностей работы виртуальной машины (ART или Dalvik), а также из-за того, что сборщик мусора (Garbage Collector, GC) не может освободить память для объектов, на которые по-прежнему существуют ссылки. Это происходит в следующих случаях: 🟠Долгоживущие ссылки Если объект ссылается на другой объект, который больше не нужен, последний не может быть освобождён GC. Например: Статические ссылки, которые продолжают "удерживать" объект. Замыкания (closures), которые хранят ссылки на контекст активности. 🟠Неявные ссылки Некоторые объекты системы Android, такие как Context, View, Handler, хранят ссылки на компоненты приложения (например, Activity), из-за чего их нельзя освободить. 🟠Неправильное использование API Ошибки, такие как регистрация слушателей (listeners) без последующей отписки, использование таймеров, которые продолжают работать даже после уничтожения активности, и так далее. 🚩Пример утечки памяти public class MyActivity extends AppCompatActivity { private static TextView myTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myTextView = findViewById(R.id.my_text_view); myTextView.setText("Hello, Memory Leak!"); } } Исправленный код public class MyActivity extends AppCompatActivity { private TextView myTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myTextView = findViewById(R.id.my_text_view); myTextView.setText("Hello, World!"); } } 🚩Как избежать утечек памяти? 🟠Избегайте долгоживущих ссылок на объекты активности или контекста Не используйте static для объектов, ссылающихся на Context или Activity. Используйте WeakReference, если нужно сохранить ссылку, которая не должна блокировать сборщик мусора. 🟠Отключайте слушателей и callback-методы Если вы регистрируете слушателей (например, через setOnClickListener), обязательно удаляйте их в методах жизненного цикла, например, в onDestroy(). 🟠Осторожно используйте анонимные классы и лямбда-выражения Анонимные классы (например, Runnable, Handler) могут неявно хранить ссылки на внешние классы, что может привести к утечке памяти. 🟠Используйте инструменты для выявления утечек памяти Android Profiler: встроенный инструмент Android Studio для мониторинга использования памяти. LeakCanary: библиотека, которая автоматически обнаруживает утечки памяти в вашем приложении. Ставь 👍 и забирай 📚 Базу знаний
175
3
🚨60 минут Пожизненный PRO-доступ на easyoffer (подготовка к IT-собесам + поиск оффера) по цене одного года закрывается прямо сейчас. Один платёж — доступ навсегда. Последнее напоминание 👇 👉 https://easyoffer.ru/pro
39
4
⚠️ 3 часа до конца акции. Последний шанс забрать пожизненный PRO на easyoffer по цене одного года. Это полный доступ к подготовке к собесам и инструментам поиска работы (вопросы с реальных интервью, ответы сеньоров, автоотклики, тренажёры) — один раз и навсегда, вместо ежегодной оплаты. В полночь цена возвращается к обычной. 👉 https://easyoffer.ru/pro
88
5
🤔 Как сделать метод потокобезопасным? Использовать ключевое слово synchronized — оно не даёт методу быть вызванным из двух потоков одновременно. Можно также использовать блокировки (Lock) или атомарные типы (AtomicInteger). Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний
251
6
🤔 Как подключить BroadcastReceiver, чтобы он смог получать сообщения? Подключение BroadcastReceiver в Android состоит из двух основных шагов: создание самого ресивера и его регистрация. Ресивер можно зарегистрировать как статически в манифесте, так и динамически в коде. 🚩Создание BroadcastReceiver Создадим простой BroadcastReceiver, который будет реагировать на определённое событие, например, на получение SMS. import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Сообщение получено!", Toast.LENGTH_SHORT).show(); } } 🚩Регистрация ресивера 🟠Статическая регистрация в манифесте Можно зарегистрировать ресивер в файле AndroidManifest.xml. Это удобно, когда вы хотите, чтобы ресивер всегда был активен и слушал определённые системные события, например, перезагрузку устройства или получение SMS. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver> </application> </manifest> 🟠Динамическая регистрация в коде Иногда нужно регистрировать ресивер только на время выполнения определённой активности или службы. В этом случае лучше использовать динамическую регистрацию. import android.content.IntentFilter; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private MyBroadcastReceiver myBroadcastReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myBroadcastReceiver = new MyBroadcastReceiver(); } @Override protected void onStart() { super.onStart(); IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED"); registerReceiver(myBroadcastReceiver, filter); } @Override protected void onStop() { super.onStop(); unregisterReceiver(myBroadcastReceiver); } } Ставь 👍 и забирай 📚 Базу знаний
242
7
⏳ Ребята, сегодня заканчивается акция, о которой стоит знать, если вы в поиске работы или планируете сменить её в ближайший год. easyoffer — это платформа для подготовки к IT-собесам и поиска оффера. Внутри: – база реальных вопросов и live-coding задач с собесов (с частотой их встречаемости) – эталонные ответы от Senior-разработчиков – 1100+ записей настоящих интервью (Сбер, Яндекс, Авито, WB, OZON, МТС) – автоотклики на hh, генератор резюме под вакансию, тренажёры собеседований Сегодня пожизненный PRO-доступ продаётся по цене одного года — платишь один раз и пользуешься всем этим всю жизнь, включая будущие фичи. С завтрашнего дня — только обычная годовая подписка. 👉 https://easyoffer.ru/pro
155
8
🤔 Какие есть реализации коллекций? - ArrayList, LinkedList, - HashSet, TreeSet, LinkedHashSet, - HashMap, TreeMap, LinkedHashMap, - PriorityQueue, ArrayDeque. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний
249
9
🤔 Зачем нужен DiffUtil? DiffUtil — это утилита для быстрого обновления списков в RecyclerView. Она сравнивает старый и новый список и находит различия, чтобы обновлять только изменённые элементы, а не весь список. 🚩Проблема без `DiffUtil` Обычное обновление списка без DiffUtil перерисовывает всё, даже если изменился один элемент. adapter.notifyDataSetChanged() // Полностью обновляет список ❌ 🟠Решение с `DiffUtil` DiffUtil находит различия между старым и новым списком и обновляет только изменённые элементы. class MyDiffUtilCallback( private val oldList: List<User>, private val newList: List<User> ) : DiffUtil.Callback() { override fun getOldListSize() = oldList.size override fun getNewListSize() = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition] == newList[newItemPosition] // Используем data class (авто `equals()`) } } 🚩Как использовать `DiffUtil` в `RecyclerView.Adapter`? После создания DiffUtil.Callback, вызываем DiffUtil.calculateDiff() и передаём результат в adapter. fun updateList(newList: List<User>) { val diffCallback = MyDiffUtilCallback(userList, newList) val diffResult = DiffUtil.calculateDiff(diffCallback) userList = newList // Обновляем старый список diffResult.dispatchUpdatesTo(this) // Обновляем только изменённые элементы } 🟠`ListAdapter` – ещё проще! Если используем ListAdapter, DiffUtil уже встроен. class UserAdapter : ListAdapter<User, UserViewHolder>(DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false) return UserViewHolder(view) } override fun onBindViewHolder(holder: UserViewHolder, position: Int) { holder.bind(getItem(position)) // `getItem()` уже работает с `ListAdapter` } companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: User, newItem: User): Boolean = oldItem == newItem } } } Обновление списка в ListAdapter adapter.submitList(newList) // DiffUtil работает внутри 🚀 Ставь 👍 и забирай 📚 Базу знаний
240
10
Пожизненный PRO — по цене одного года. Покупаешь один раз — пользуешься всю жизнь: 👉 https://easyoffer.ru/pro 🚀 PRO-доступ закроет 99% проблем на пути к офферу: 1. 1100+ записей реальных собеседований (включая топы: Сбер, Авито, Яндекс, WB, OZON, МТС). Видите всё изнутри: как спрашивают, как отвечают сильные кандидаты и на каких ошибках проваливаются 80%. 2. База live-coding задач и вопросов с реальных собесов — с уникальной системой вероятности их встречи. Готовитесь не вслепую, а точечно по темам, которые спрашивают чаще всего. 3. Эталонные ответы от Senior-разработчиков. Никакой воды и догадок — только чёткие структурированные решения, за которые дают «зелёный свет» к офферу. 4. Полный доступ ко всем грейдам и профессиям. Junior вы или Senior, тестировщик, разработчик или проджект — вы получаете ВСЕ материалы easyoffer без ограничений. Безлимитно, Все, Навсегда. 5. База 400+ тестовых заданий. Прокачивайте навыки на реальных задачах — тех самых, что дают перед собесом. 6. Автоотклики на hh.ru — пока вы спите, резюме уходит рекрутерам автоматически. Экономия сотен часов ручного кликанья. 7. Аналитика ТОП-требований из вакансий. Парсим рынок и показываем, какие скиллы сейчас в цене. Апгрейдите резюме точечно и проходите ATS-фильтры (они отсеивают до 75% резюме ещё до рекрутера). 8. Генератор резюме и CV под каждую вакансию. Забудьте про «универсальное» резюме — нейросеть адаптирует ваш опыт под конкретную позицию за минуту и повышает шансы на приглашение в разы. 9. Тренажёры подготовки к собеседованию: «Реальное собеседование» — сценарий вопросов из настоящих интервью. «Проработка вопросов» — флеш-карточки по методике интервальных повторений (как Anki) 10. 🔥 Самое важное: все будущие фичи Вы платите один раз, а продукт растёт всю жизнь. Каждое обновление, каждый новый инструмент, каждая фича, которая появится за все годы проекта, автоматически падает вам в подписку без доплат. Вы фиксируете цену года, а получаете продукт, который через пару лет будет стоить в разы дороже ⭐️ Это уникальная акция пока сайт в режиме Beta. Успей ей воспользоваться⏳ Завтра последний день. 👉 https://easyoffer.ru/pro
181
11
🤔 Как эффективно изменить данные в списке, состоящем из большого количества View? Для списков с большим количеством View лучше использовать DiffUtil, который вычисляет разницу между старым и новым списками. Это позволяет обновить только те элементы, которые изменились, без перерисовки всего списка, что значительно повышает производительность. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний
244
12
🔥 Осталось 3 дня! Пожизненный easyoffer PRO по цене одного года. Покупаешь один раз – пользуешься всю жизнь. Что входит в PRO: – Вопросы и задачи с реальных собеседований в конкретных компаниях – Лучшие ответы и видео-примеры от middle/senior специалистов – Записи реальных собеседований – Обход фильтров ATS с топ-30 ключевых слов в резюме – Автоотклики на hh – Тренажёры и симуляторы для идеальной подготовки к интервью ⏳ Акция действует только до 2 сентября 23:59 по МСК 👉 Забрать PRO со скидкой 70%: https://easyoffer.ru/pro
222
13
🤔 Какие знаешь особенности системы типов в Kotlin ? 🟠Null Safety (Безопасность null) Переменные по умолчанию не могут быть null, что предотвращает NullPointerException. var a: String = "abc" var b: String? = "abc" b = null // Допустимо 🟠Коллекции (Collections) Разделение на изменяемые и неизменяемые коллекции. val list: List<String> = listOf("a", "b", "c") // Неизменяемый список val mutableList: MutableList<String> = mutableListOf("a", "b", "c") // Изменяемый список 🟠Data Classes (Классы данных) Автоматическое создание методов equals(), hashCode(), и toString(). data class User(val name: String, val age: Int) 🟠Smart Casts (Умные приведения типов) Автоматическое приведение типа после проверки с помощью is. fun demo(x: Any) { if (x is String) { println(x.length) } } 🟠Sealed Classes (Запечатанные классы) Упрощают обработку ограниченных иерархий классов. sealed class Expr data class Const(val number: Double) : Expr() 🟠Выведение типов (Type Inference) Kotlin автоматически определяет тип переменной. val x = 10 // Int Ставь 👍 и забирай 📚 Базу знаний
268
14
Ты ищешь только по верхушке айсберга 🧊 Вбиваешь «Android разработчик» и думаешь, что видел всё. А под водой скрывается ещё 8
Ты ищешь только по верхушке айсберга 🧊 Вбиваешь «Android разработчик» и думаешь, что видел всё. А под водой скрывается ещё 80% вакансий, которые называются иначе: «мобильный разработчик», «Kotlin engineer», «SDK developer» и так далее. Рекрутеры не договариваются между собой о названиях. Поэтому те, кто ищут по одному запросу, просто сливают лучшие предложения. В свежем посте на канале я собрал полный список из 15 рабочих запросов для разных площадок + список самих площадок, где эти вакансии реально публикуют. Забирай список и перестань терять вакансии 👇 Android Оффер
209
15
🤔 Какая особенность у Data Class относительно других Kotlin Classes? Data Class автоматически генерирует equals, hashCode, toString, copy, и componentN функции. Они удобны для хранения данных и значительно упрощают работу с неизменяемыми структурами. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний
273
16
🤔 Расскажи о контрактах equals и hashCode Методы equals() и hashCode() используются для сравнения объектов и их корректной работы в коллекциях (Set, Map). 🚩Контракт `equals()` Метод equals() должен: Рефлексивность: a.equals(a) → true (объект равен самому себе). Симметричность: a.equals(b) == b.equals(a). Транзитивность: если a == b и b == c, то a == c. Согласованность: если a == b, то a.equals(b) всегда возвращает одно и то же, пока объект не изменится. Сравнение с null всегда даёт false: a.equals(null) == false. class User(val name: String, val age: Int) { override fun equals(other: Any?): Boolean { if (this === other) return true // Проверка на ссылочное равенство if (other !is User) return false // Проверка типа return name == other.name && age == other.age // Сравнение полей } } val user1 = User("Alice", 25) val user2 = User("Alice", 25) println(user1 == user2) // true (потому что переопределён equals) 🚩Контракт `hashCode()` Метод hashCode() должен: Согласованность с equals(): если a == b, то a.hashCode() == b.hashCode(). Но обратное не обязательно: два разных объекта могут иметь одинаковый hashCode(). Хеш-код не должен меняться, если объект не изменился. class User(val name: String, val age: Int) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is User) return false return name == other.name && age == other.age } override fun hashCode(): Int { return name.hashCode() * 31 + age // 31 - стандартный коэффициент } } val userSet = HashSet<User>() userSet.add(User("Alice", 25)) println(userSet.contains(User("Alice", 25))) // true 🚩Автоматическая генерация в Kotlin Чтобы не писать equals() и hashCode() вручную, можно использовать data class: data class User(val name: String, val age: Int) data class автоматически создаёт equals(), hashCode(), а также copy() и toString(). val user1 = User("Alice", 25) val user2 = User("Alice", 25) println(user1 == user2) // true (equals) println(user1.hashCode() == user2.hashCode()) // true Ставь 👍 и забирай 📚 Базу знаний
295
17
🤔 Как работает async/await? async запускает асинхронную корутину, возвращающую Deferred, аналог Future. await приостанавливает выполнение до получения результата. Это позволяет параллельно выполнять задачи и синхронно работать с результатом. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний
292
18
🤔 View Model vs OnSavedInstanceState? ViewModel и onSaveInstanceState служат для сохранения данных при изменении конфигурации активности или фрагмента (например, при повороте экрана). Однако они решают эту задачу по-разному и имеют разные области применения. 🟠ViewModel ViewModel используется для хранения и управления данными, связанных с UI, таким образом, чтобы они сохранялись при изменении конфигурации (например, при повороте экрана). 🚩Как это работает? Когда система уничтожает и пересоздаёт Activity или Fragment, ViewModel остаётся в памяти до полного уничтожения владельца (например, выхода из Activity). class MyViewModel : ViewModel() { var counter: Int = 0 // Переменная, которая сохраняется при повороте экрана } Использование в Activity class MainActivity : AppCompatActivity() { private lateinit var viewModel: MyViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel = ViewModelProvider(this).get(MyViewModel::class.java) // Теперь viewModel.counter сохранится при повороте экрана } } 🚩Плюсы ➕Хранит данные в памяти до полного уничтожения Activity или Fragment. ➕Удобно для хранения сложных объектов списки, модели, API-данные ➕Позволяет разделять логику и UI улучшая архитектуру. 🚩Минусы ➖Данные исчезают при уничтожении Activity например, при закрытии приложения ➖Не сохраняет данные при завершении процесса например, при нехватке памяти 🚩onSaveInstanceState() Метод onSaveInstanceState() используется для сохранения данных в Bundle, который система автоматически передаёт при пересоздании Activity или Fragment. 🚩Как это работает? Когда Activity уничтожается (например, при повороте экрана), система вызывает onSaveInstanceState(), в котором можно сохранить небольшие данные (строки, числа и т. д.). После пересоздания Activity эти данные можно восстановить из savedInstanceState. class MainActivity : AppCompatActivity() { private var counter: Int = 0 // Значение, которое мы хотим сохранить override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Восстанавливаем данные, если они есть counter = savedInstanceState?.getInt("counter_key") ?: 0 } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt("counter_key", counter) // Сохраняем значение перед уничтожением Activity } } 🚩Плюсы ➕Сохраняет данные даже при завершении процесса (например, при нехватке памяти). ➕Позволяет быстро восстановить состояние UI (например, текст в EditText). 🚩Минусы ➖Подходит только для небольших данных (числа, строки). ➖Не подходит для хранения сложных объектов (списки, большие модели, API-данные). ➖Требует кодирования и декодирования данных в Bundle Ставь 👍 и забирай 📚 Базу знаний
309
19
🤔 Из каких более важных компонентов состоит Compose? Compose состоит из компонентов: - Composable функции: описывают UI. - State: управляет состоянием компонентов. - Modifiers: применяются для настройки внешнего вида и поведения. - Layouts: определяют структуру расположения элементов на экране. - Recomposition: механизм обновления интерфейса при изменении состояния. Ставь 👍 если знал ответ, 🔥 если нет Забирай 📚 Базу знаний
324
20
🤔 Какой класс можно использовать что бы ловить разные жесты? Чтобы обрабатывать жесты в Android, используйте класс GestureDetector. Он помогает отслеживать стандартные жесты: одиночные нажатия, свайпы, долгие нажатия, двойные касания и т.д. 🚩Как использовать GestureDetector? Создайте экземпляр GestureDetector, передав Context и слушателя (GestureDetector.OnGestureListener). Передавайте события касания в gestureDetector.onTouchEvent(event) из метода onTouchEvent(). class GestureActivity : AppCompatActivity(), GestureDetector.OnGestureListener { private lateinit var gestureDetector: GestureDetector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) gestureDetector = GestureDetector(this, this) } override fun onTouchEvent(event: MotionEvent): Boolean { return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event) } override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean { val deltaX = e2!!.x - e1!!.x if (deltaX > 0) Log.d("Gesture", "Swipe Right") else Log.d("Gesture", "Swipe Left") return true } override fun onDown(e: MotionEvent?) = true override fun onShowPress(e: MotionEvent?) {} override fun onSingleTapUp(e: MotionEvent?) = false override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float) = false override fun onLongPress(e: MotionEvent?) {} } Ставь 👍 и забирай 📚 Базу знаний
376