Kotlin | Вопросы собесов
2.56K subscribers
28 photos
972 links
Download Telegram
🤔 Можно ли переопределить конструкторы в Java?

Конструкторы не переопределяются, потому что они не наследуются. Однако можно перегружать их в одном классе (разные параметры), и вызывать конструкторы суперкласса через super().


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

В 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
👍4
🤔 Что такое multibinding в Dagger?

Multibinding позволяет создать коллекции зависимостей одного типа — например, Set или Map. Это особенно полезно для регистрации нескольких обработчиков событий, плагинов или реализаций одного интерфейса.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Для проектов в которых есть несколько модулей, там может быть много Android Manifest'ов, для чего это делается?

В многомодульных проектах каждый модуль может иметь свой AndroidManifest.xml, чтобы:
Задавать зависимости (uses-permission, uses-feature) для конкретного модуля.
Определять компоненты (Activity, Service, BroadcastReceiver) для каждого модуля.
Автоматически объединять манифесты всех модулей в AndroidManifest.xml главного (app) модуля.

🚩Как объединяются манифесты в многомодульном проекте?

При сборке Gradle автоматически сливает (merge) все AndroidManifest.xml в один итоговый файл.
app/  
├── src/main/AndroidManifest.xml ← Главный манифест
├── java/com/example/MainActivity.kt
├── res/
├── build.gradle
feature_login/
├── src/main/AndroidManifest.xml ← Манифест модуля `login`
├── java/com/example/login/LoginActivity.kt
├── res/
├── build.gradle
feature_chat/
├── src/main/AndroidManifest.xml ← Манифест модуля `chat`
├── java/com/example/chat/ChatActivity.kt
├── res/
├── build.gradle


🚩Как работает объединение манифестов?

При сборке манифесты модулей сливаются в манифест главного модуля.
<manifest package="com.example.app">
<application>
<activity android:name=".MainActivity" />
</application>
</manifest>


feature_login (feature_login/src/main/AndroidManifest.xml)
<manifest>
<application>
<activity android:name=".login.LoginActivity" />
</application>
</manifest>


feature_chat (feature_chat/src/main/AndroidManifest.xml)
<manifest>
<application>
<activity android:name=".chat.ChatActivity" />
</application>
</manifest>


После объединения итоговый AndroidManifest.xml выглядит так
<manifest package="com.example.app">
<application>
<activity android:name=".MainActivity" />
<activity android:name=".login.LoginActivity" />
<activity android:name=".chat.ChatActivity" />
</application>
</manifest>


🚩Зачем модулям свой `AndroidManifest.xml`?

🟠Разные разрешения (`uses-permission`) для каждого модуля
Например, модуль camera требует CAMERA, но другие модули — нет.
<uses-permission android:name="android.permission.CAMERA" />


🟠Разные `Activity`, `Service`, `BroadcastReceiver` в каждом модуле
Каждый модуль добавляет только свои компоненты (например, LoginActivity в feature_login).

🟠Разные зависимости для разных модулей
Например, модуль feature_map использует Google Maps, но feature_login — нет.
<uses-library android:name="com.google.android.maps" />


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 Библиотека для нахождения утечек памяти

На Android популярная библиотека для выявления утечек памяти — LeakCanary от Square. Она отслеживает потенциальные утечки (Activity, Fragment, Context, View) и уведомляет разработчика при обнаружении утечки с подробной трассировкой.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍2
🤔 Зачем нужен 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 работает внутри 🚀


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 Какие есть особенности использования Nothing в дженериках

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

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

В Kotlin для работы с многопоточностью можно использовать atomic переменные или synchronized блоки. Они помогают избежать гонок данных (race conditions), когда несколько потоков одновременно изменяют одну и ту же переменную.

🚩Когда использовать `atomic` переменные?

Atomic переменные (AtomicInteger, AtomicLong, AtomicReference и др.) используются, когда нужно быстро и безопасно обновлять одно значение без блокировки потока.
Высокая производительность – атомарные операции работают быстрее, чем synchronized.
Потокобезопасность – операции выполняются без блокировок (lock-free).
Удобство – простые методы incrementAndGet(), compareAndSet() и т. д.
Работает только с одним значением – не подходит для сложных операций.
Не подходит для зависимых изменений (например, если изменение одной переменной зависит от другой).

import java.util.concurrent.atomic.AtomicInteger

val counter = AtomicInteger(0)

fun increment() {
counter.incrementAndGet() // Увеличиваем значение безопасно
}


🟠Когда использовать `synchronized`?
Используется, когда нужно защитить сразу несколько связанных операций от одновременного выполнения в нескольких потоках.
Подходит для сложных операций с несколькими переменными.
Работает с любыми типами данных.
Снижает производительность – потоки ждут, пока один поток закончит работу.
Может вызывать deadlock (взаимную блокировку).
val lock = Any()
var count = 0

fun increment() {
synchronized(lock) {
count++ // Только один поток может изменять `count` одновременно
}
}


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

– Сохранить строку как *.svg в файл и загрузить через SvgDecoder,
– Использовать библиотеки (Glide, Coil, androidsvg),
– Преобразовать в Drawable и отобразить через ImageView.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1💊1
🤔 Что при создании классов в Kotlin изменились по сравнению с Java?

При создании классов по сравнению с Java произошли несколько значительных изменений и упрощений. Kotlin предлагает более лаконичный и выразительный синтаксис, что делает код более читаемым и удобным.

🚩Объявление классов и конструкторов

В Kotlin объявление классов и их конструкторов значительно упрощено.
В Java
public class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}


В Kotlin
class Person(val name: String, val age: Int)


🟠Статические члены
В Java для объявления статических членов используется ключевое слово static. В Kotlin вместо этого используются companion object.
В Java
public class MyClass {
public static final String CONSTANT = "constant";

public static void staticMethod() {
// Some code
}
}


В Kotlin
class MyClass {
companion object {
const val CONSTANT = "constant"

@JvmStatic
fun staticMethod() {
// Some code
}
}
}


🟠Data классы
Kotlin предоставляет специальный тип классов — data классы, которые автоматически генерируют методы equals(), hashCode(), toString(), copy(), и componentN().
В Java
public class User {
private String name;
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public boolean equals(Object o) {
// Implementation
}

@Override
public int hashCode() {
// Implementation
}

@Override
public String toString() {
// Implementation
}
}


В Kotlin
data class User(val name: String, val age: Int)


🟠Свойства и методы доступа
В Kotlin свойства объявляются напрямую, и методы доступа (геттеры и сеттеры) генерируются автоматически.
В Java
public class Rectangle {
private int width;
private int height;

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}
}


В Kotlin
class Rectangle(var width: Int, var height: Int)


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Как заинжектить ViewModel в Dagger Hilt?

В Hilt ViewModel помечается аннотацией
@HiltViewModel, а зависимости внедряются через конструктор. ViewModel создаётся с помощью by viewModels() в Activity или Fragment, автоматически подставляя зависимости.

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

Модификации могут означать разные вещи в зависимости от контекста. В Android-разработке чаще всего речь идёт о следующих типах модификаций:

Модификация данных (изменение состояния)
Модификация UI (Compose Modifiers)
Модификация кода (рефакторинг, оптимизация)
Модификация системы (кастомные прошивки, рут-изменения)

🟠Модификация данных (изменение состояния)
Это изменение переменных, объектов и структур данных во время работы приложения.
class ViewModel : ViewModel() {
private val _state = MutableStateFlow("Привет, мир!")
val state: StateFlow<String> = _state

fun updateText(newText: String) {
_state.value = newText // Модификация состояния
}
}


🟠Модификация UI (Jetpack Compose Modifiers)
В Jetpack Compose модификации (Modifier) используются для изменения внешнего вида и поведения UI-элементов.
Text(
text = "Привет!",
modifier = Modifier
.padding(16.dp) // Добавляем отступ
.background(Color.Blue) // Меняем фон
.clickable { println("Нажали!") } // Добавляем клик
)


🟠Модификация кода (рефакторинг, оптимизация)
В программировании модификация кода означает его улучшение без изменения основной логики.
fun sum(a: Int, b: Int): Int {
return a + b
}


После рефакторинга
fun sum(a: Int, b: Int) = a + b


🟠Модификация системы (кастомные прошивки, рут-изменения)
Это изменения в самой операционной системе Android, например:
- Рут-доступ (root), позволяющий изменять системные файлы.
- Кастомные прошивки (например, LineageOS).
- Изменение build.prop для скрытия рут-доступа.
ro.build.version.sdk=33


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

Ссылочные типы в Kotlin:
- неизменяемые, если это требуется по архитектуре;
- nullable или not-null — в зависимости от требований;
- должны реализовывать equals, hashCode и toString, особенно если используются в коллекциях;
- быть легковесными, если часто копируются;
- желательно — final или sealed, если поведение фиксировано.


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

dp означает "density-independent pixels" или "пиксели, не зависящие от плотности". Это виртуальная единица измерения, которая используется для обеспечения одинакового визуального размера элементов пользовательского интерфейса на экранах с различной плотностью пикселей. Это важно, поскольку устройства Android могут иметь различные размеры и разрешения экранов, и использование dp помогает создать консистентный пользовательский интерфейс на всех этих устройствах.

🚩Как он работает?

🟠Базовый расчёт
Базовая логика такова, что 1 dp равен одному пикселю на экране с плотностью 160 точек на дюйм (dpi). Это плотность соответствует "среднему" уровню, который называется "mdpi" в терминах Android. На таком экране:
1 dp = 1 px
🟠Масштабирование на других устройствах
На устройствах с более высокой или ниже плотностью экрана dp масштабируются соответственно:
На устройстве с плотностью 320 dpi (high density, hdpi), 1 dp = 2 px.
На устройстве с плотностью 240 dpi (high density, hdpi), 1 dp = 1.5 px.
И так далее.

🚩Зачем его использовать?

Обеспечить, чтобы элементы интерфейса (как размеры, так и отступы) выглядели одинаково на разных устройствах, несмотря на физические различия в размерах экранов и их разрешениях. Это позволяет создавать приложения, которые хорошо выглядят и функционируют на множестве устройств без необходимости перепроектирования интерфейса под каждое конкретное устройство.
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me!"
android:padding="16dp" />


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

– Хранить выбранную тему в SharedPreferences,
– При старте приложения (или Activity) применять тему до setContentView,
– В случае использования DayNight — можно использовать AppCompatDelegate.setDefaultNightMode().


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

Jetpack Compose – это декларативный UI-фреймворк от Google для создания интерфейсов в Android. Вместо традиционных XML + View в Compose используется функции-компоненты, которые описывают UI.

🟠Главные принципы работы Jetpack Compose
Декларативный подход – UI создаётся через функции, без XML.
еактивность – UI автоматически обновляется, если данные изменились.
Компонентный подход – UI состоит из маленьких, переиспользуемых функций.
Composable-функции (@Composable) – основной строительный блок Compose.

🟠Как создаётся UI в Compose?
Вместо Activity и Fragment с findViewById() используется @Composable-функции.
@Composable
fun Greeting(name: String) {
Text(text = "Привет, $name!")
}

@Composable
fun MyScreen() {
Column {
Greeting("Андрей")
Greeting("Мария")
}
}

@Preview
@Composable
fun PreviewMyScreen() {
MyScreen()
}


🟠Реактивность: автоматическое обновление UI
Jetpack Compose использует реактивный подход, где UI обновляется при изменении состояния.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // Реактивное состояние

Column {
Text("Счётчик: $count")
Button(onClick = { count++ }) {
Text("Увеличить")
}
}
}


🟠Основные UI-компоненты в Compose
Текст
Text(text = "Привет, мир!", fontSize = 20.sp)


Кнопка
Button(onClick = { /* действие */ }) {
Text("Нажми меня")
}


Ввод текста (TextField)
var text by remember { mutableStateOf("") }

TextField(value = text, onValueChange = { text = it })


Размещение элементов (вертикально/горизонтально)
Column {
Text("Первый элемент")
Text("Второй элемент")
}

Row {
Text("Слева")
Text("Справа")
}


🟠Взаимодействие с ViewModel
Compose работает с MVVM через ViewModel и LiveData / StateFlow.
class MyViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count

fun increment() {
_count.value++
}
}

@Composable
fun CounterScreen(viewModel: MyViewModel = viewModel()) {
val count by viewModel.count.collectAsState()

Column {
Text("Счётчик: $count")
Button(onClick = { viewModel.increment() }) {
Text("Добавить")
}
}
}


🟠Навигация в Jetpack Compose
Вместо Fragment используется NavController.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details") { DetailsScreen() }
}
}


Теперь можно перейти на другой экран так
navController.navigate("details")


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Для чего нужен Bundle?

Bundle — это контейнер для передачи данных между компонентами Android, такими как Activity, Fragment или Service. Он хранит пары ключ-значение и может сохраняться при пересоздании компонента.


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

Термин "Extensions" (расширения) используется для обозначения функциональности, которая позволяет добавлять новые возможности к существующим классам без изменения их исходного кода. Этот концепт особенно популярен к примеру в Swift и Kotlin, где расширения используются для улучшения читаемости кода, упрощения его структуры и добавления удобных методов для работы с уже существующими типами данных.

🚩Расширения в Kotlin

Расширения позволяют добавить новую функциональность к классам, даже если исходный код этих классов недоступен для изменений (например, классы из стандартной библиотеки или сторонних библиотек). Это делается путём объявления функций или свойств, которые могут быть вызваны на экземплярах этих классов, как если бы они были частью оригинальных классов.
// Расширение класса String для проверки, пуста ли строка или состоит из пробелов
fun String.isNullOrBlank(): Boolean {
return this == null || this.trim().isEmpty()
}

// Использование расширения
val myString = " "
println(myString.isNullOrBlank()) // Выведет: true


🚩Расширения в Swift

Использует очень похожий подход, позволяя добавлять новые методы и вычисляемые свойства к существующим типам. Расширения могут даже добавлять новые протоколы к типам, что делает их чрезвычайно мощным инструментом в разработке под iOS и macOS.
// Расширение стандартного типа String для добавления метода инвертирования строки
extension String {
func reversedString() -> String {
return String(self.reversed())
}
}

let myString = "Hello"
print(myString.reversedString()) // Выведет: "olleH"


🚩Плюсы

Улучшение читаемости и организации кода
Расширения позволяют держать методы, связанные с определёнными типами данных, ближе к использованию этих типов.

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

Реиспользование кода
Методы, добавленные через расширения, могут быть легко переиспользованы в различных частях приложения.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 Что будет выводиться HashMap.get(), если equals()/hashCode() не переопределены?

Если объект-ключ не переопределяет equals() и hashCode(), то HashMap.get() не сможет найти значение по эквивалентному, но не тому же экземпляру, и вернёт null.


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

data class — класс для хранения данных с автогенерацией equals(), hashCode(), copy().
sealed class — ограниченная иерархия классов, используется для when.

🚩`data class` – для хранения данных

data class автоматически создаёт:
equals() и hashCode() → сравнение объектов по значениям.
copy() → удобное копирование с изменением параметров.
toString() → красивый вывод.
data class User(val id: Int, val name: String)

fun main() {
val user1 = User(1, "Alice")
val user2 = user1.copy(name = "Bob") // Создаём копию с новым именем

println(user1) // User(id=1, name=Alice)
println(user2) // User(id=1, name=Bob)
}


🚩`sealed class` – для ограниченных иерархий классов

sealed class используется, когда есть фиксированное число подклассов.
sealed class NetworkState {
object Loading : NetworkState()
data class Success(val data: String) : NetworkState()
data class Error(val message: String) : NetworkState()
}

fun handleState(state: NetworkState) {
when (state) {
is NetworkState.Loading -> println("Загрузка...")
is NetworkState.Success -> println("Данные: ${state.data}")
is NetworkState.Error -> println("Ошибка: ${state.message}")
}
}


🚩Можно ли использовать `data class` внутри `sealed class`?

Да! Это лучший вариант для управления состояниями:
sealed class Result {
object Loading : Result()
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}


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