В Java и Kotlin существуют различные типы данных, которые можно разделить на две основные категории:
Примитивные типы данных (primitive types)
Ссылочные типы данных (reference types)
Ссылочные типы данных указывают на объекты. Они хранятся в куче (heap) и содержат ссылку (адрес) на объект.
Примеры ссылочных типов
String str = "Hello";MyClass obj = new MyClass();int[] arr = {1, 2, 3};List<Integer> list = new ArrayList<>();В Kotlin типы данных делятся на nullable и non-nullable (значения, которые могут быть
null, и те, которые не могут). Kotlin избегает примитивных типов напрямую, но на уровне JVM использует их для оптимизации. Kotlin предоставляет высокоуровневые обёртки для примитивных типов. Например:
Int вместо intDouble вместо doubleВ Kotlin все данные — объекты.
Сюда входят:
Строки (
String): val str: String = "Hello"Коллекции:
val list: List<Int> = listOf(1, 2, 3)Классы:
val obj = MyClass()Nullable-типизация: В Kotlin можно указать, что переменная может быть
null. Для этого используется ?. val nullableString: String? = null
val nonNullableString: String = "Hello"
Интерфейсы и абстракции: Kotlin поддерживает классы, интерфейсы и их реализации, например
interface Animal {
fun makeSound()
}
class Dog : Animal {
override fun makeSound() {
println("Woof!")
}
}
Kotlin добавляет уникальные типы, которые отсутствуют в Java:
Unit
Эквивалент
void в Java, но является объектом.Используется, когда функция ничего не возвращает:
fun printMessage(message: String): Unit {
println(message)
}
NothingПредставляет значение, которое никогда не будет существовать (например, функция всегда бросает исключение):
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
associateBy создаёт Map, где ключи берутся из заданной логики, а значениями становятся элементы коллекции. associateWith наоборот — элемент становится ключом, а значение задаётся отдельно.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍2
Когда используется deeplink, может возникнуть проблема, что одно и то же Activity может быть открыто несколько раз в стеке задач. Это происходит, если приложение запускается из внешнего источника (например, из браузера или другого приложения), и Android создает новую задачу или новую копию Activity вместо использования уже существующей. Чтобы справиться с этим, нужно правильно настроить
launchMode, интенты и флаги.При запуске приложения через deep link, система Android может:
1. Создать новый экземпляр вашего Activity (даже если оно уже существует в стеке задач).
2. Поместить новую задачу в стек задач.
Если это не контролировать, пользователь может увидеть много дубликатов одного и того же Activity, что плохо для UX и может вызвать утечку памяти.
В файле
AndroidManifest.xml можно настроить поведение Activity с помощью атрибута launchMode:singleTop: Если Activity уже находится на вершине стека, система не будет создавать новый экземпляр.singleTask: Убедитесь, что только один экземпляр Activity существует в задаче. Если Activity уже существует, система передаст интент в метод onNewIntent().singleInstance: Подходит для случаев, когда Activity должно быть абсолютно уникальным (используется редко).<activity
android:name=".MyActivity"
android:launchMode="singleTop" />
Если вы используете deep link или запускаете Activity вручную, можно добавить флаги, чтобы управлять созданием экземпляров:
val intent = Intent(this, MyActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
startActivity(intent)
Если используется
singleTop или singleTask, система вызывает метод onNewIntent(Intent intent) вместо создания нового экземпляра Activity. Этот метод можно переопределить для обработки новых данных из интента override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Обработка нового интента
val data = intent?.data
// Используйте данные deeplink
}
Если требуется, чтобы разные deep link открывали одну и ту же задачу, можно настроить
taskAffinity. Это нужно реже, но полезно, если нужно обрабатывать ссылки с разными контекстами.Дополнительно можно вручную проверять, существует ли нужное Activity в текущем состоянии приложения, например, используя LiveData или ViewModel.
Файл
AndroidManifest.xml<activity
android:name=".MyActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:path="/deeplink" />
</intent-filter>
</activity>
Файл
MyActivity.ktclass MyActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// Обработка данных из нового интента (deeplink)
val uri = intent?.data
uri?.let {
// Например, получить параметры из ссылки
val param = it.getQueryParameter("id")
Log.d("Deeplink", "Parameter id: $param")
}
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
- data class — для хранения и работы с данными, удобны при копировании, логическом сравнении, сериализации.
- sealed class — для ограниченного набора подтипов. Удобны при использовании when, так как все случаи должны быть обработаны — это повышает безопасность и читаемость.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В Kotlin есть множество удобных функций для получения одного элемента или списка элементов из коллекций (
List, Set, Map). Какую функцию использовать — зависит от задачи. получение по индексу
Если индекс может выйти за границы массива, лучше использовать
getOrNull(). val list = listOf("A", "B", "C")
println(list[1]) // "B"
println(list.getOrNull(5)) // nullпервый и последний элементы
println(list.first()) // "A"
println(list.last()) // "C"
Используйте
firstOrNull() или lastOrNull(), если список может быть пустымprintln(emptyList<String>().firstOrNull()) // null
поиск по условию Возвращает первый или последний элемент, удовлетворяющий условию.
val numbers = listOf(10, 20, 30, 40)
println(numbers.find { it > 15 }) // 20
println(numbers.findLast { it > 15 }) // 40
println(numbers.find { it > 50 }) // null
если ожидается только один элемент
val oneElementList = listOf(42)
println(oneElementList.single()) // 42
println(oneElementList.singleOrNull()) // 42
val emptyList = emptyList<Int>()
println(emptyList.singleOrNull()) // null
single() бросает исключение, если элементов нет или их больше одного. filter {} – фильтрация элементов по условиюval numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // [2, 4]
take(n) и takeLast(n) – первые/последние n элементов val items = listOf("a", "b", "c", "d", "e")
println(items.take(3)) // [a, b, c]
println(items.takeLast(2)) // [d, e]slice(indices) – получение элементов по индексам val sliced = items.slice(listOf(0, 2, 4))
println(sliced) // [a, c, e]
map {} – преобразование списка val squared = numbers.map { it * it }
println(squared) // [1, 4, 9, 16, 25]Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4💊1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Нет, после
by нельзя вызывать функции или конструкторы. by ожидает готовый объект, который реализует интерфейс или делегирует свойство. В Kotlin
by используется в двух случаях: Делегирование интерфейсов (
class A : Interface by obj) Делегирование свойств (
val x by lazy {}) Но
by не может вызывать функции или конструкторы – ему нужен уже созданный объект. Правильный код (делегируем готовый объект)
interface Printer {
fun printMessage()
}
class RealPrinter : Printer {
override fun printMessage() = println("Печать...")
}
// ✅ Делегируем готовый объект `RealPrinter()`
class MyClass : Printer by RealPrinter()
fun main() {
MyClass().printMessage() // Печать...
}Ошибка, если после
by вызвать конструктор class MyClass : Printer by RealPrinter() // ✅ Работает
class MyClass2 : Printer by RealPrinter() { } // ❌ Ошибка!
Правильный код (
by lazy) val message: String by lazy { "Привет, мир!" }
println(message) // Привет, мир!Ошибка, если после
by вызвать функцию val message: String by getMessage() // ❌ Ошибка!
fun getMessage() = "Привет, мир!"
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊8👍2
- Через .equals() — логическое сравнение содержимого.
- При переопределении .equals() рекомендуется также переопределить .hashCode().
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1💊1
В Java все классы неявно наследуются от класса
Object, если явно не указано другое наследование. class MyClass {
// Неявно наследуется от Object
}
class MyClass2 extends Object {
// То же самое, просто указано явно
}Класс
Object содержит основные методы, доступные во всех классах: class Person {
String name;
Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("Alice");
System.out.println(p.toString()); // Person{name='Alice'}
System.out.println(p.hashCode()); // Хеш-код объекта
System.out.println(p.getClass()); // class Person
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
- Сделать приватный конструктор, чтобы запретить внешнее создание объекта.
- Используется в паттерне Singleton или Utility-классах (например, Math).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1💊1
В
interface можно определять свойства (val или var), но их реализация зависит от того, есть ли у них get и set. В интерфейсе можно объявить
val (только для чтения) или var (для чтения и записи), но без инициализации. interface User {
val name: String // Объявляем свойство, но не реализуем
var age: Int // Может изменяться, но без начального значения
}Использование в классе
class Person(override val name: String) : User {
override var age: Int = 25 // Обязательно реализовать
}Можно задать кастомный геттер прямо в интерфейсе.
interface User {
val name: String
val greeting: String
get() = "Привет, $name!" // Реализация внутри интерфейса
}Использование в классе
class Person(override val name: String) : User
fun main() {
val user = Person("Андрей")
println(user.greeting) // Привет, Андрей!
}
Если в интерфейсе объявить
var, то нельзя задать реализацию геттера и сеттера — их должен реализовать класс. interface User {
var age: Int // Только объявление, без реализации
}Использование в классе
class Person : User {
override var age: Int = 30 // Реализуем свойство
}В
interface нельзя использовать field (бэкинг-поле), потому что у интерфейсов нет состояния. interface User {
var age: Int
get() = field // Ошибка! Нельзя использовать `field` в интерфейсе
set(value) { field = value }
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Для этого используется ItemAnimator — по умолчанию это DefaultItemAnimator. Также можно использовать DiffUtil и ListAdapter, чтобы RecyclerView самостоятельно определял изменения и плавно их анимировал.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
При пересоздании
Activity (например, при повороте экрана) состояние ScrollView или RecyclerView сбрасывается. Чтобы этого избежать, можно сохранить и восстановить позицию скролла. Android позволяет сохранять данные в
Bundle перед уничтожением Activity, а затем восстанавливать их. class MainActivity : AppCompatActivity() {
private lateinit var scrollView: ScrollView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
scrollView = findViewById(R.id.scrollView)
// Восстанавливаем позицию скролла
savedInstanceState?.let {
val scrollY = it.getInt("scroll_position", 0)
scrollView.post { scrollView.scrollTo(0, scrollY) }
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("scroll_position", scrollView.scrollY)
}
}У
RecyclerView уже есть встроенный механизм сохранения состояния через LinearLayoutManager. class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var layoutManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
layoutManager = LinearLayoutManager(this)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = MyAdapter()
// Восстанавливаем позицию скролла
savedInstanceState?.let {
val scrollPosition = it.getInt("recycler_position", 0)
recyclerView.post { layoutManager.scrollToPosition(scrollPosition) }
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("recycler_position", layoutManager.findFirstVisibleItemPosition())
}
}Если
EditText находится внутри ScrollView, можно включить android:freezesText="true", чтобы автоматически сохранять данные. <EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:freezesText="true"/>
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Эта функция делит коллекцию на два списка: один содержит элементы, соответствующие условию, другой — не соответствующие. Это удобно для фильтрации без потери невалидных элементов.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍1
Лямбда-выражения представляют собой компактный способ представления анонимных функций, который особенно полезен для создания коротких блоков выполнения кода, предназначенных для последующей передачи другим функциям. Лямбды широко используются для упрощения работы с коллекциями, потоками данных, асинхронным кодом и в ситуациях, где требуется функциональный интерфейс.
Были введены в Java 8 и являются частью усилий по добавлению функциональных возможностей в язык. Лямбды в Java чаще всего используются с функциональными интерфейсами, которые являются интерфейсами с одним абстрактным методом.
(parameters) -> expression
или
(parameters) -> { statements; }Поддерживает более гибкие и выразительные лямбды по сравнению с Java. Лямбды могут использоваться как с функциональными интерфейсами, так и в качестве части синтаксиса языка, благодаря чему Kotlin особенно удобен для функционального программирования.
{ parameters -> code body }Лямбда без параметров
{ println("Hello, World!") } Лямбда с одним параметром
{ a: Int -> a * a } Лямбда с несколькими параметрами
{ a: Int, b: Int -> a + b } Лямбда с телом, содержащим несколько выражений
{ s: String ->
println(s)
s.length
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
1. SAM-конверсии позволяют использовать лямбда-функции вместо объектов классов с одним абстрактным методом.
2. Это делает код более кратким и читабельным при работе с Java API или интерфейсами в Kotlin.
3. Пример: интерфейс с одним методом автоматически превращается в функциональный тип.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Библиотека Lifecycle в Android Jetpack помогает управлять и контролировать жизненный цикл компонентов Android, таких как Activities и Fragments. Она упрощает создание компонентов, которые осведомлены о своем жизненном цикле и могут корректно реагировать на изменения в нем. Это позволяет избегать утечек памяти и некорректной работы компонентов при изменении конфигураций или переходах между состояниями.
Это интерфейс, который определяет класс как владеющий жизненным циклом. Activity и Fragment уже реализуют этот интерфейс.
class MyActivity : AppCompatActivity(), LifecycleOwner {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}Это интерфейс, который позволяет классу наблюдать за изменениями в жизненном цикле компонента. Методы, аннотированные
@OnLifecycleEvent, будут вызываться при соответствующих событиях жизненного цикла.class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
// Код, который выполняется при старте жизненного цикла
Log.d("MyObserver", "onStart called")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {
// Код, который выполняется при остановке жизненного цикла
Log.d("MyObserver", "onStop called")
}
}Это класс, который содержит информацию о текущем состоянии и позволяет другим объектам наблюдать за изменениями в жизненном цикле.
class MyActivity : AppCompatActivity() {
private lateinit var myObserver: MyObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myObserver = MyObserver()
lifecycle.addObserver(myObserver)
}
}dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Log.d("MyObserver", "Activity resumed")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
Log.d("MyObserver", "Activity paused")
}
}class MyActivity : AppCompatActivity() {
private lateinit var myObserver: MyObserver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myObserver = MyObserver()
lifecycle.addObserver(myObserver)
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
2. При восстановлении (в onRestoreInstanceState) установить сохранённую позицию через RecyclerView.scrollToPosition или ScrollView.scrollTo.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍1
Сохранение структуры Markdown в базу данных или на диск зависит от целей и требований приложения. Рассмотрим несколько вариантов.
Самый простой способ — хранить Markdown как обычный текст в базе данных или файле.
База данных (SQLite, Room):
@Entity
data class Note(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String // Здесь хранится Markdown-текст
)
@Dao
interface NoteDao {
@Insert
suspend fun insert(note: Note)
@Query("SELECT * FROM Note WHERE id = :id")
suspend fun getNote(id: Long): Note?
}
Файл на диске
fun saveMarkdownToFile(context: Context, filename: String, content: String) {
val file = File(context.filesDir, filename)
file.writeText(content, Charsets.UTF_8)
}
fun readMarkdownFromFile(context: Context, filename: String): String {
val file = File(context.filesDir, filename)
return if (file.exists()) file.readText(Charsets.UTF_8) else ""
}Если нужно анализировать структуру Markdown (например, извлекать заголовки, ссылки, списки), можно парсить Markdown в JSON и сохранять в базу данных.
{
"title": "Android Markdown Guide",
"content": [
{
"type": "header",
"level": 1,
"text": "Введение"
},
{
"type": "paragraph",
"text": "Markdown — это простой язык разметки..."
},
{
"type": "list",
"items": [
"Простота",
"Гибкость",
"Совместимость"
]
}
]
}Сохранение в базу данных (Room + TypeConverter):
@Entity
data class MarkdownNote(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val jsonContent: String // Храним JSON-структуру
)
class MarkdownConverter {
@TypeConverter
fun fromJson(value: String): List<MarkdownBlock> {
return Gson().fromJson(value, object : TypeToken<List<MarkdownBlock>>() {}.type)
}
@TypeConverter
fun toJson(content: List<MarkdownBlock>): String {
return Gson().toJson(content)
}
}
Парсинг Markdown в JSON с помощью библиотеки (например, flexmark-java)
fun parseMarkdownToJson(markdown: String): String {
val document = Parser.builder().build().parse(markdown)
val blocks = mutableListOf<MarkdownBlock>()
document.children.forEach { node ->
when (node) {
is Heading -> blocks.add(MarkdownBlock("header", node.level, node.text.toString()))
is Paragraph -> blocks.add(MarkdownBlock("paragraph", text = node.text.toString()))
is BulletList -> {
val items = node.children.map { it.text.toString() }
blocks.add(MarkdownBlock("list", items = items))
}
}
}
return Gson().toJson(blocks)
}Если Markdown в основном нужен для отображения, можно сразу конвертировать его в HTML и хранить в базе/файлах.
Конвертация Markdown → HTML с помощью flexmark-java:
fun convertMarkdownToHtml(markdown: String): String {
val renderer = HtmlRenderer.builder().build()
val document = Parser.builder().build().parse(markdown)
return renderer.render(document)
}Сохранение в базу:
@Entity
data class HtmlNote(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val htmlContent: String
)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2💊1
Многопоточность позволяет выполнять тяжёлые задачи без блокировки UI. В Android используют ExecutorService, Thread, Handler, Coroutines, WorkManager, RxJava, LiveData и другие средства в зависимости от задач.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2💊1