Kotlin | Вопросы собесов
2.56K subscribers
29 photos
972 links
Download Telegram
🤔 Как передать и как получить данные из WorkManager?

Данные передаются с помощью специального контейнера Data, который прикрепляется к задаче при её создании. Внутри Worker эти данные можно получить через inputData, а также можно вернуть результат выполнения обратно, используя outputData, который затем доступен через LiveData или статус задачи.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
🤔 Что происходит с полями из супер-класса при наследовании data-класса?

При наследовании data class от какого-либо суперкласса в Kotlin, важно понимать, как работают свойства (поля) суперкласса и как они влияют на функциональность и структуру data class.

🚩Что происходит с полями суперкласса

🟠Наследование свойств
Поля (свойства), объявленные в суперклассе, автоматически становятся доступными в классе-наследнике. Вы можете использовать их в наследуемом классе как обычно. Однако свойства суперкласса не участвуют в автоматически сгенерированных функциях equals(), hashCode(), и toString() для data class.

🟠Автоматически сгенерированные функции в data-классе
У data class Kotlin генерирует функции equals(), hashCode(), toString(), copy() и другие. Эти функции работают только с параметрами, объявленными в первичном конструкторе data-класса. Поля, которые находятся в суперклассе, не участвуют в этих функциях.

🟠Почему поля суперкласса игнорируются
Это связано с тем, что контракт data class предполагает, что все его ключевые данные (data) определяются только параметрами первичного конструктора. Это позволяет гарантировать, что две одинаковые сущности будут сравниваться и обрабатываться корректно, основываясь только на данных самого data class.

// Суперкласс с полем name
open class Person(val name: String)

// Наследуемый data-класс
data class Employee(val id: Int, val position: String) : Person(name = "Default")


val employee1 = Employee(1, "Developer")
val employee2 = Employee(1, "Developer")

println(employee1 == employee2) // true, так как сравнение основано только на id и position
println(employee1.toString()) // Employee(id=1, position=Developer)


🟠Если нужно включить поля суперкласса в сравнение
Если вы хотите, чтобы поля суперкласса учитывались в логике equals() или hashCode(), вам нужно переопределить эти функции вручную.
data class Employee(val id: Int, val position: String) : Person(name = "Default") {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Employee || !super.equals(other)) return false
return id == other.id && position == other.position && name == other.name
}

override fun hashCode(): Int {
return 31 * super.hashCode() + id.hashCode() + position.hashCode()
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 На что обратить внимание при работе с фрагментами?

- Жизненный цикл — особенно при навигации и конфигурации.
- Утечки памяти через context/view.
- Передача аргументов через Bundle, а не напрямую.
- Подписка на данные — отвязка в onDestroyView.
- Навигация — через Navigation Component или FragmentManager.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊1
Forwarded from easyoffer
🎉 easyoffer 2.0 — релиз уже в этом месяце!

Вас ждут новые фичи, о которых мы ранее даже не упоминали. Они сделают путь к офферам ещё быстрее и эффективнее. Расскажу о них чуть позже 👀

В честь запуска мы готовим ограниченную акцию:

Первые 500 покупателей получат:
🚀 PRO тариф на 1 год с 50% скидкой

Что нужно сделать:

🔔 Подпишитесь на этот Telegram-канал, чтобы первыми узнать о старте релиза. Сообщение появится в нем раньше, чем где-либо еще — вы успеете попасть в число первых 500 и получить максимальную выгоду. 🎁 А еще только для подписчиков канала ценный бонус в подарок к PRO тарифу.

📅 Официальный запуск — уже совсем скоро.
Следите за новостями и не пропустите старт!
🤔 Зачем нужно разделять отображение и бизнес-логику?

Разделение UI и бизнес-логики делает код понятнее, тестируемее и проще в поддержке.
Это ключевой принцип чистой архитектуры (Clean Architecture) и паттернов MVP, MVVM, MVI.

🚩Что такое UI и бизнес-логика?

Плохой код (UI + логика в Activity)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val result = fetchData() // Логика в UI
button.text = result
}
}

fun fetchData(): String {
return "Данные с сервера" // Тут должна быть ViewModel
}
}


🚩Как правильно разделять UI и логику? (MVVM)

Разделим код на Activity + ViewModel
MainActivity (только UI)
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val button = findViewById<Button>(R.id.button)

viewModel.text.observe(this) { text ->
button.text = text // UI обновляется из ViewModel
}

button.setOnClickListener {
viewModel.loadData() // Вызываем бизнес-логику
}
}
}


MainViewModel (бизнес-логика)
class MainViewModel : ViewModel() {
private val _text = MutableLiveData<String>()
val text: LiveData<String> = _text

fun loadData() {
_text.value = "Данные с сервера" // UI не знает, откуда данные
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Какие известны способы обработки ошибок в корутинах?

1. Try-catch внутри launch {} или async {} – локальная обработка ошибок.
2. CoroutineExceptionHandler – глобальная обработка в launch, но не работает с async.
3. supervisorScope {} – дочерние корутины не завершают родительский scope при ошибке.
4. try-catch вокруг await() – обработка исключений из async.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2
🤔 Какие могут быть проблемы с элементами списка?

Проблемы с элементами списка в Android-приложениях могут быть разнообразными. Давайте рассмотрим некоторые из наиболее распространённых проблем и способы их решения.

🚩 Переполнение памяти (Out of Memory)

🟠Почему это происходит: Списки с большим количеством элементов, особенно если элементы содержат изображения или другие ресурсоемкие данные, могут вызывать переполнение памяти.
🟠Решение: Использовать RecyclerView вместо ListView. RecyclerView более эффективно управляет памятью за счет повторного использования представлений. Кроме того, стоит использовать библиотеки для загрузки изображений, такие как Glide или Picasso, которые обеспечивают кэширование и оптимизацию памяти
// Пример использования Glide для загрузки изображений
Glide.with(context)
.load(imageUrl)
.into(imageView)


🚩Медленная прокрутка (Lagging)

🟠Почему это происходит: Прокрутка может быть медленной, если элементы списка отрисовываются или загружаются слишком долго.
🟠Решение: Оптимизировать адаптер списка. Использовать ViewHolder паттерн, чтобы избежать ненужных вызовов findViewById. Также стоит выполнять тяжелые операции, такие как загрузка изображений, асинхронно.
   class MyAdapter(private val itemList: List<Item>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.textView)
val imageView: ImageView = itemView.findViewById(R.id.imageView)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = itemList[position]
holder.textView.text = item.text
Glide.with(holder.itemView.context).load(item.imageUrl).into(holder.imageView)
}

override fun getItemCount() = itemList.size
}


🚩Неправильное отображение данных (Data Inconsistency)

🟠Почему это происходит: Если адаптер списка неправильно управляет обновлением данных, элементы могут отображать неправильные или устаревшие данные.
🟠Решение: Обеспечить корректное обновление данных в адаптере и использовать DiffUtil для вычисления изменений в данных и обновления только необходимых элементов.
class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}

// Использование в адаптере
val diffCallback = ItemDiffCallback()
val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(myAdapter)


🚩Многопоточность (Concurrency Issues)

🟠Почему это происходит: Если обновление данных списка происходит из разных потоков без должной синхронизации, это может привести к ошибкам.
🟠Решение: Обеспечить синхронизацию обновлений данных. Использовать подходы, такие как LiveData или Flow из библиотеки Jetpack, чтобы обновления происходили в главном потоке.
val liveData = MutableLiveData<List<Item>>()

liveData.observe(viewLifecycleOwner, Observer { items ->
myAdapter.submitList(items)
})


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Что такое JIT?

JIT (Just-In-Time compilation) — технология, при которой байткод компилируется в машинный код прямо во время выполнения.
Плюсы:
- Повышение производительности на часто вызываемом коде.
- Гибкая оптимизация под конкретное устройство.
Используется в Java, Dalvik и некоторых JavaScript-движках.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
🤔 Какие знаешь особенности системы типов в 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


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🤔1
🤔 Зачем нужен контекст и каковы его функции?

Контекст (Context) предоставляет доступ к системным ресурсам и функциям:
- доступ к Resources, Assets,
- запуск Activity, Service,
- доступ к SharedPreferences,
- получение SystemService.
Это один из ключевых элементов Android, передающий «среду выполнения».


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 Вставка значения быстрее в Linked list или в Array list?

🚩Вставка в `ArrayList`

ArrayList реализован на основе массива.

🟠Вставка в конец
Амортизированное O(1). Если массив не заполнен, элемент добавляется в конец списка. В случае, если массив заполнен, требуется выделение нового массива и копирование элементов, что занимает O(n) времени.
val arrayList = ArrayList<Int>()
arrayList.add(1) // Быстрая вставка в конец


🟠Вставка в начало или середину
O(n). Необходимо сдвинуть все элементы, начиная с позиции вставки, чтобы освободить место для нового элемента. Это требует времени, пропорционального количеству элементов после позиции вставки.
arrayList.add(0, 2) // Медленная вставка в начало
arrayList.add(1, 3) // Медленная вставка в середину


🚩Вставка в LinkedList

LinkedList реализован на основе узлов, где каждый узел содержит ссылку на следующий и/или предыдущий узел (в случае двусвязного списка).

🟠Вставка в начало: O(1). Для добавления элемента в начало достаточно изменить ссылки первого узла и нового узла.
val linkedList = LinkedList<Int>()
linkedList.addFirst(1) // Быстрая вставка в начало


🟠Вставка в конец
O(1) (если есть ссылка на последний узел) или O(n) (если необходимо пройти весь список). Если список двусвязный и хранится ссылка на последний элемент, вставка в конец осуществляется за O(1). В противном случае, требуется пройти весь список до конца.
linkedList.addLast(2) // Быстрая вставка в конец, если есть ссылка на последний узел


🟠Вставка в середину
O(n). Необходимо пройти список до нужной позиции и изменить ссылки узлов.
linkedList.add(1, 3) // Медленная вставка в середину


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Чем отличаются inner классы от nested?

Nested class — статический, не имеет доступа к экземпляру внешнего класса.
Inner class — нестатический, может обращаться к полям и методам внешнего класса, включая приватные.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6
🤔 Как LeakCanary находит ссылку на уничтоженную Activity?

Использует комбинацию слабых ссылок (WeakReference), анализ кучи и цепочек удержания, чтобы найти ссылки на уничтоженные Activity или другие объекты, которые должны были быть освобождены сборщиком мусора. Вот более подробное объяснение того, как это работает:

🚩Шаги

1⃣Создание слабой ссылки (WeakReference)
Когда Activity (или другой компонент) уничтожается, LeakCanary создает слабую ссылку на этот объект.
val weakReference = WeakReference(activity)


2⃣Ожидание сборки мусора (Garbage Collection)
LeakCanary инициирует сборку мусора, чтобы попытаться освободить уничтоженный объект.
System.gc()
System.runFinalization()


3⃣Проверка слабой ссылки
LeakCanary проверяет, освобождена ли слабая ссылка. Если объект не был освобожден, это указывает на возможную утечку памяти.
if (weakReference.get() != null) {
// Объект все еще удерживается в памяти, вероятна утечка
}


4⃣Снимок кучи (Heap Dump)
Если слабая ссылка не была освобождена, LeakCanary создает снимок кучи (heap dump) для дальнейшего анализа.
val heapDumpFile = createHeapDump()


5⃣Анализ кучи с помощью Shark
LeakCanary использует библиотеку Shark для анализа снимка кучи. Shark проверяет все объекты в куче и их удерживающие ссылки.
val heapAnalyzer = HeapAnalyzer()
val analysis = heapAnalyzer.analyze(heapDumpFile)


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
🤔 Что произойдёт, если вместо ключевого слова class использовать object?

object в Kotlin создаёт singleton — единственный экземпляр. Нельзя создавать новые экземпляры, он инициализируется при первом обращении.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 Что происходит когда делаешь ==?

В Kotlin оператор == используется для структурного сравнения объектов.

🚩Что делает `==` в Kotlin?

Оператор == вызывает метод equals(), чтобы проверить содержимое объектов, а не их ссылки.
data class User(val name: String)

fun main() {
val user1 = User("Alice")
val user2 = User("Alice")

println(user1 == user2) // true (структурное сравнение)
println(user1 === user2) // false (сравнение ссылок)
}


🚩Чем `==` отличается от `===`?

== проверяет, равны ли данные объектов (equals()).
=== проверяет, ссылаются ли объекты на один и тот же участок памяти.
val str1 = "Hello"
val str2 = "Hello"
println(str1 == str2) // true (содержимое одинаковое)
println(str1 === str2) // true (Kotlin кеширует строки)

val obj1 = String("Hello".toCharArray())
val obj2 = String("Hello".toCharArray())
println(obj1 == obj2) // true (содержимое одинаковое)
println(obj1 === obj2) // false (разные объекты в памяти)


🚩Как работает `==` для классов?

Если equals() не переопределён, то сравниваются ссылки (как ===).
class Person(val name: String)

fun main() {
val p1 = Person("Bob")
val p2 = Person("Bob")

println(p1 == p2) // false (equals() не переопределён, сравниваются ссылки)
}


Чтобы == работал по содержимому, нужно переопределить equals()
class Person(val name: String) {
override fun equals(other: Any?): Boolean {
return other is Person && other.name == this.name
}
}

fun main() {
val p1 = Person("Bob")
val p2 = Person("Bob")

println(p1 == p2) // true (equals() сравнивает содержимое)
}


🚩Как `==` работает с `null`?

Kotlin предотвращает NullPointerException при сравнении с null:
val a: String? = null
val b: String? = "Hello"

println(a == b) // false (без NPE)
println(a == null) // true


Когда a == b, на самом деле выполняется:
a?.equals(b) ?: (b === null)


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Какие готовые компоненты с готовым скоупом есть в Dagger Hilt и как модуль добавить к этой компоненте?

Hilt предоставляет компоненты для приложения, активностей, фрагментов, ViewModel и других уровней. Модуль подключается с помощью аннотации
@InstallIn, где указывается конкретный компонент, в рамках которого он будет использоваться.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 В каком потоке запускается самый обычный сервис?

Самый обычный сервис, который наследуется от класса Service, по умолчанию запускается в главном потоке приложения, который также называется UI-потоком (User Interface Thread). Это означает, что все операции, выполняемые в сервисе, включая методы onStartCommand(), onCreate(), и onBind(), выполняются в главном потоке. Если в сервисе будут выполняться длительные или ресурсоемкие операции, такие как сетевые запросы, обработка больших данных или выполнение сложных вычислений, это может привести к "зависанию" пользовательского интерфейса и появлению сообщений о том, что приложение не отвечает (ANR — Application Not Responding).

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Из каких более важных компонентов состоит Compose?

Compose состоит из компонентов:
- Composable функции: описывают UI.
- State: управляет состоянием компонентов.
- Modifiers: применяются для настройки внешнего вида и поведения.
- Layouts: определяют структуру расположения элементов на экране.
- Recomposition: механизм обновления интерфейса при изменении состояния.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍1
🤔 Какие способы навигации знаешь?

В Android есть несколько способов навигации между экранами. Давай разберём основные.

🟠Activity-based навигация
Каждый экран – это отдельная Activity, переход осуществляется с помощью Intent.
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)


🟠Fragment-based навигация
Один Activity содержит несколько Fragment, навигация через FragmentManager.
supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment())
.addToBackStack(null)
.commit()


🟠Navigation Component (Jetpack)
Google рекомендует Navigation Component для удобной работы с Fragment.
findNavController().navigate(R.id.action_firstFragment_to_secondFragment)


🟠Bottom Navigation / Tab Navigation
Используется BottomNavigationView или TabLayout для переключения между экранами.
bottomNavigationView.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home -> replaceFragment(HomeFragment())
R.id.profile -> replaceFragment(ProfileFragment())
}
true
}


🟠Drawer Navigation (Navigation Drawer)
Боковое меню (DrawerLayout) с пунктами навигации.
drawerLayout.openDrawer(GravityCompat.START)


🟠Deep Links и App Links
Навигация через ссылки (например, myapp://profile/123).
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="myapp" android:host="profile"/>
</intent-filter>


🟠Navigation в Jetpack Compose
Используется NavHost и NavController.
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile") { ProfileScreen(navController) }
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Знаешь, чем MVVM отличается от MVP?

В MVVM (Model-View-ViewModel) ViewModel отвечает за обработку данных и предоставление их View, не зная о её реализации, тогда как в MVP (Model-View-Presenter) Presenter активно управляет интерфейсом и логикой. В MVVM связь между View и ViewModel осуществляется через привязку данных, что делает интерфейс более реактивным и минимизирует прямую логику в View. MVP требует больше ручного связывания компонентов и имеет более явное разделение обязанностей между Presenter и View. MVVM часто предпочтительнее для приложений с богатым пользовательским интерфейсом, так как поддерживает более чистую архитектуру.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍1
🤔 В андроиде существуют мэпы, в которые можно положить примитивные типы?

Да! В Android есть специальные Map-коллекции, которые позволяют хранить примитивные типы (int, long, boolean и т. д.) без автоупаковки (autoboxing).

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

Обычные HashMap<Int, Int> в Kotlin используют автоупаковку (Integer вместо int), что:
Увеличивает потребление памяти (из-за объектов Integer, Long и т. д.).
Замедляет работу (из-за ненужного создания объектов).

Решение? Использовать специализированные мэпы из android.util!

🚩`SparseArray` (замена `HashMap<Int, Any?>`)

Хранит пары Int → Any, но без автоупаковки.
import android.util.SparseArray

val sparseArray = SparseArray<String>()
sparseArray.put(1, "Привет")
sparseArray.put(2, "Мир")

println(sparseArray[1]) // Привет
println(sparseArray[2]) // Мир


🚩`SparseIntArray` (замена `HashMap<Int, Int>`)

Хранит пары Int → Int без автоупаковки.
import android.util.SparseIntArray

val sparseIntArray = SparseIntArray()
sparseIntArray.put(1, 100)
sparseIntArray.put(2, 200)

println(sparseIntArray[1]) // 100
println(sparseIntArray[2]) // 200


🚩`SparseBooleanArray` (замена `HashMap<Int, Boolean>`)

Оптимизирован для Int → Boolean пар.
import android.util.SparseBooleanArray

val sparseBooleanArray = SparseBooleanArray()
sparseBooleanArray.put(1, true)
sparseBooleanArray.put(2, false)

println(sparseBooleanArray[1]) // true
println(sparseBooleanArray[2]) // false


🚩`LongSparseArray<T>` (замена `HashMap<Long, T>`)

Оптимизирован для Long → Any?, аналог SparseArray, но с Long ключами.
import android.util.LongSparseArray

val longSparseArray = LongSparseArray<String>()
longSparseArray.put(10000000000L, "Длинный ключ")

println(longSparseArray[10000000000L]) // Длинный ключ


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8