Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Методы
equals() и hashCode() используются для сравнения объектов и их корректной работы в коллекциях (Set, Map). Метод
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() должен:Согласованность с
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
Чтобы не писать
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Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Dagger — это статически проверяемый фреймворк для внедрения зависимостей. Он генерирует код на этапе компиляции, обеспечивая высокую производительность и удобное масштабирование.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1💊1
Бранчинг (ветвление) — это способ управления кодом в Git, когда разработчики работают в отдельных ветках (
branches). Основные стратегии бранчинга
Git Flow
GitHub Flow
GitLab Flow
Trunk-Based Development
Основные ветки:
main (стабильная версия, релизы). develop (основная ветка разработки). Временные ветки:
feature/* (новые фичи, мерджатся в develop). release/* (готовится релиз, тестирование, фикс багов). hotfix/* (критические фиксы в main). Схема Git Flow:
main ──── hotfix ─▶️ merge ────▶️ main
│
├── develop ─▶️ release ─▶️ merge ─▶️ main
│ │
├── feature/1
├── feature/2
Только две основные ветки:
main (всегда стабильная версия). Фичи разрабатываются в
feature/* и сразу мерджатся в main. Деплой возможен сразу после мерджа в
main. Схема GitHub Flow
main ────▶️ feature/1 ─▶️ merge ─▶️ main ─▶️ deploy
└── feature/2 ─▶️ merge ─▶️ main ─▶️ deploy
main – стабильная ветка (готовая к продакшену). develop (опционально) – если нужно тестирование перед main. feature/* – для разработки новых фич. production, staging – если нужно разделение сред. hotfix/* – фиксы продакшена. main ────▶️ production
│
├── staging ───▶️ merge ─▶️ main
│
├── feature/1 ─▶️ merge ─▶️ staging
├── feature/2 ─▶️ merge ─▶️ staging
Разработчики работают прямо в
main, без feature/* веток. - Коммиты в
main маленькие и частые. - Используются Feature Flags (фичи включаются/выключаются динамически).
Схема Trunk-Based
main ────▶️ commit ─▶️ commit ─▶️ commit ─▶️ deploy
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2💊1
Основные альтернативы — Dagger (ручная конфигурация), Koin (написан на Kotlin, декларативный), Kodein (устаревающий), Service Locator. Выбор зависит от предпочтений и архитектуры проекта.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1💊1
Ключевое слово
object в Kotlin имеет несколько важных применений, и оно является одной из наиболее мощных и уникальных возможностей языка. Kotlin предоставляет встроенную поддержку для создания singleton-объектов. Это объект, который имеет единственный экземпляр в приложении.
object Database {
val name = "MainDatabase"
fun connect() {
println("Подключение к базе данных $name")
}
}
fun main() {
Database.connect() // Подключение к базе данных MainDatabase
}Ключевое слово
object может использоваться для создания анонимных объектов (объектов без имени). Это полезно, если нужно создать временный объект или реализовать интерфейс/абстрактный класс.fun main() {
val listener = object : ClickListener {
override fun onClick() {
println("Кнопка нажата")
}
}
listener.onClick()
}
interface ClickListener {
fun onClick()
}Ключевое слово
object можно использовать внутри класса для объявления компаньон-объекта. Это позволяет создавать статические методы и переменные в классе.class User(val name: String) {
companion object {
fun createGuest() = User("Guest")
}
}
fun main() {
val guest = User.createGuest()
println(guest.name) // Guest
}Ключевое слово
object может использоваться для объявления объектов в коде прямо "на месте".val myObject = object {
val x = 10
fun printX() {
println(x)
}
}
fun main() {
myObject.printX() // 10
}Вместо написания множества шаблонного кода для Singleton, анонимных объектов или статических методов, вы получаете готовую реализацию из коробки.
В случае
object, компилятор Kotlin автоматически гарантирует, что объект создаётся в потокобезопасном режиме.object можно использовать практически везде: глобально, локально, внутри классов и функций.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Гонки происходят, когда два потока одновременно обращаются к общим данным без синхронизации. Решения:
- synchronized,
- ReentrantLock,
- Atomic-типы,
- volatile — только если нужна гарантия видимости, но не атомарности.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Map – это коллекция пар ключ-значение. Каждый ключ уникален, а значения могут повторяться. В Kotlin Map не наследуется от Collection, но является частью стандартных коллекций.В Kotlin есть два основных типа
Map: Map (неизменяемая) – нельзя добавлять/удалять элементы после создания. MutableMap (изменяемая) – можно добавлять, удалять и изменять элементы. Создаётся с помощью
mapOf()val users = mapOf(1 to "Alice", 2 to "Bob", 3 to "Charlie")
println(users[1]) // Выведет: Alice
Создаётся с помощью
mutableMapOf()val users = mutableMapOf(1 to "Alice", 2 to "Bob")
users[3] = "Charlie" // Добавляем элемент
users[1] = "Alex" // Изменяем значение по ключу
users.remove(2) // Удаляем элемент
println(users) // {1=Alex, 3=Charlie}
map[key] – получить значение (или null, если ключа нет). map.getValue(key) – получить значение (или исключение, если ключа нет). map.getOrDefault(key, defaultValue) – вернуть значение или defaultValue, если ключа нет. map.getOrElse(key) { default } – если ключа нет, выполнить лямбда-выражение. val users = mapOf(1 to "Alice", 2 to "Bob")
println(users[1]) // Alice
println(users.getOrDefault(3, "Unknown")) // Unknown
map.containsKey(key) – есть ли ключ? map.containsValue(value) – есть ли значение? println(users.containsKey(2)) // true
println(users.containsValue("Charlie")) // false
Перебор элементов
for ((key, value) in users) {
println("ID: $key, Name: $value")
}Фильтрация
Mapval filtered = users.filter { (key, value) -> key % 2 == 0 }
println(filtered) // {2=Bob}mapOf() – не гарантирует порядок. linkedMapOf() – сохраняет порядок добавления. Обычный
HashMap (mutableMapOf()) – O(1) для поиска по ключу. TreeMap – O(log n), но поддерживает сортировку по ключам. val sortedUsers = sortedMapOf(3 to "Charlie", 1 to "Alice", 2 to "Bob")
println(sortedUsers) // {1=Alice, 2=Bob, 3=Charlie}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В Kotlin инициализаторы используются для выполнения кода при создании экземпляра класса.
Первичный (primary) конструктор
Вторичные (secondary) конструкторы
Инициализационный блок (
init) Это основной способ инициализации класса в Kotlin. Он объявляется в заголовке класса.
class User(val name: String, val age: Int)
Если нужно выполнить дополнительную логику во время создания объекта, используют блок
init: class User(val name: String, val age: Int) {
init {
println("Создан пользователь: $name, возраст: $age")
}
}Они объявляются с помощью
constructor и нужны, если:Нужно несколько способов создания объекта.
Надо вызвать другой конструктор (
this(...)). class User {
var name: String
var age: Int
constructor(name: String) {
this.name = name
this.age = 18 // Значение по умолчанию
}
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}Можно создать объект так
val user1 = User("Алекс") // возраст будет 18
val user2 = User("Иван", 25) Если есть первичный конструктор, вторичный должен его вызывать через
this(...)class User(val name: String, val age: Int) {
constructor(name: String) : this(name, 18)
}init выполняется всегда при вызове первичного конструктора. Вторичный конструктор создаёт альтернативный способ создания объекта.
class User(val name: String, val age: Int) {
init {
println("Создан пользователь: $name, возраст: $age")
}
constructor(name: String) : this(name, 18) {
println("Вызван вторичный конструктор")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Да, существуют специализированные реализации мэп, оптимизированные под примитивы, например:
- SparseIntArray — аналог Map<Int, Int>, но без обёртки Integer.
- SparseBooleanArray, SparseArray<T> и другие.
Они используются для экономии памяти и производительности, избегая автоупаковки (autoboxing).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
data class — класс для хранения данных с автогенерацией equals(), hashCode(), copy(). sealed class — ограниченная иерархия классов, используется для when. 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 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}")
}
}Да! Это лучший вариант для управления состояниями:
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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Это один из ключевых принципов объектно-ориентированного программирования (ООП). Оно позволяет одному классу (наследнику) унаследовать свойства и методы другого класса (родителя). Это мощный инструмент, но использовать его нужно с умом, чтобы избежать лишней сложности и проблем с поддержкой кода.
Если у вас есть несколько классов, которые имеют общие свойства или методы, наследование позволяет вынести эти общие элементы в родительский класс. Это помогает избежать дублирования кода.
open class Animal(val name: String) {
fun eat() {
println("$name ест.")
}
}
class Dog(name: String) : Animal(name) {
fun bark() {
println("$name лает.")
}
}
class Cat(name: String) : Animal(name) {
fun meow() {
println("$name мяукает.")
}
}
// Использование:
val dog = Dog("Бобик")
dog.eat() // "Бобик ест."
dog.bark() // "Бобик лает."
Наследование отлично подходит для задач, связанных с созданием деревьев типов или категорий. Например, у вас есть базовый класс
Shape (Форма), и от него наследуются конкретные фигуры: Circle, Rectangle, Triangle. open class Shape {
open fun draw() {
println("Рисуется форма.")
}
}
class Circle : Shape() {
override fun draw() {
println("Рисуется круг.")
}
}
class Rectangle : Shape() {
override fun draw() {
println("Рисуется прямоугольник.")
}
}
// Использование:
val shapes: List<Shape> = listOf(Circle(), Rectangle())
for (shape in shapes) {
shape.draw()
}
Полиморфизм позволяет использовать родительские классы для работы с объектами наследников. Это полезно, если у вас есть методы, которые работают с разными типами объектов, но с единым интерфейсом. Вызов методов
draw() у всех объектов, не задумываясь об их конкретном типе, благодаря полиморфизму. fun render(shape: Shape) {
shape.draw()
}
render(Circle()) // "Рисуется круг."
render(Rectangle()) // "Рисуется прямоугольник."
Если наследник действительно является вариантом (подтипом) родительского класса, наследование логично. Например: Собака является животным (
Dog is an Animal). Прямоугольник является фигурой (Rectangle is a Shape).Если объекты не имеют строгого отношения "является", наследование использовать нельзя. Например: Класс
Car (Машина) и Boat (Лодка) лучше объединить через общие свойства (интерфейсы или композицию), чем делать лодку наследником машины. open class Bird {
open fun fly() {
println("Птица летит.")
}
}
class Penguin : Bird() {
override fun fly() {
throw UnsupportedOperationException("Пингвин не умеет летать!")
}
}
Если ваш класс должен обладать несколькими несвязанными функциями, лучше использовать композицию или интерфейсы вместо наследования.
class Car : Engine, Wheels
Используйте композицию:
class Car {
private val engine = Engine()
private val wheels = Wheels()
}
Если вы создаете слишком глубокие или разветвленные иерархии, код становится сложным для понимания, тестирования и изменения. Например, изменение в базовом классе может неожиданно затронуть всех наследников.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
При создании классов по сравнению с 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
}
}
}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
👍2
В Kotlin нет checked исключений, все исключения — unchecked. Это уменьшает кодовую нагрузку (throws, catch) и упрощает API. В Java нужно явно обрабатывать checked исключения.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В MVI (Model-View-Intent) есть проблема: события, которые не нужно хранить в
State, могут повторно отобразиться при пересоздании экрана (например, при повороте экрана или навигации назад). Показ
Toast / Snackbar Навигация (
openScreen()) Ошибки, которые нужно показать один раз
Это
LiveData, которая отправляет событие только один раз и не пересылает его новым подписчикам. class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { value ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(value)
}
}
}
override fun setValue(value: T?) {
pending.set(true)
super.setValue(value)
}
}ViewModel с
SingleLiveEvent class MyViewModel : ViewModel() {
val eventShowToast = SingleLiveEvent<String>()
fun onButtonClicked() {
eventShowToast.value = "Привет, это Toast!"
}
}Activity/Fragment подписывается
viewModel.eventShowToast.observe(viewLifecycleOwner) { message ->
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}В
StateFlow все события хранятся в State (что не подходит для одноразовых событий). Вместо этого используем
SharedFlow с replay = 0, чтобы событие не повторялось. ViewModel с
SharedFlow class MyViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun onButtonClicked() {
viewModelScope.launch {
_events.emit(UiEvent.ShowToast("Привет, это Toast!"))
}
}
}
sealed class UiEvent {
data class ShowToast(val message: String) : UiEvent()
object NavigateToNextScreen : UiEvent()
}Подписка в UI:
lifecycleScope.launchWhenStarted {
viewModel.events.collect { event ->
when (event) {
is UiEvent.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
is UiEvent.NavigateToNextScreen -> findNavController().navigate(R.id.nextFragment)
}
}
}Если в
State всё-таки нужно хранить событие, но не показывать его повторно, можно использовать EventWrapper. class EventWrapper<out T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) null else {
hasBeenHandled = true
content
}
}
}ViewModel с
StateFlow: data class UiState(val message: EventWrapper<String>? = null)
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
fun onButtonClicked() {
_state.value = UiState(message = EventWrapper("Привет, это Toast!"))
}
}
UI подписывается и проверяет
EventWrapper: viewModel.state.collect { state ->
state.message?.getContentIfNotHandled()?.let { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Да, все компоненты, которые не должны быть доступны извне, могут не указываться, особенно:
- Activity, используемая только внутри;
- BroadcastReceiver или Service, регистрируемые динамически;
- ContentProvider — если не требуется внешнего доступа.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1🤔1
Подключение
BroadcastReceiver в Android состоит из двух основных шагов: создание самого ресивера и его регистрация. Ресивер можно зарегистрировать как статически в манифесте, так и динамически в коде.Создадим простой
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);
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Главный плюс MVVM — разделение обязанностей:
- UI (View) отделён от логики (ViewModel);
- бизнес-логика тестируется отдельно;
- упрощает масштабирование и поддержку;
- особенно эффективно в Android с LiveData, StateFlow, DataBinding.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1🤔1