Kotlin | Вопросы собесов
2.57K subscribers
28 photos
965 links
Download Telegram
🤔 Какой архитектурный паттерн реализуется благодаря ViewModel?

Благодаря ViewModel реализуется паттерн MVVM (Model-View-ViewModel). ViewModel отвечает за управление данными и бизнес-логикой, изолируя их от View, что упрощает тестирование и обеспечивает разделение ответственности между слоями.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
🤔 Как бы ты сохранял структуру Markdown в базу данных/на диск?

Сохранение структуры Markdown в базу данных или на диск зависит от целей и требований приложения. Рассмотрим несколько вариантов.

🟠Сохранение Markdown в виде строки (Plain Text)
Самый простой способ — хранить 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 в виде JSON (Парсинг в структуру)
Если нужно анализировать структуру 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 и хранить в базе/файлах.
Конвертация 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
👍1
🤔 После получения результата внутри Presenter, как сообщить об этом View?

1. Через интерфейс: Presenter вызывает методы интерфейса, который реализует View.
2. С помощью callback-ов: View предоставляет Presenter-у callback, который вызывается при обновлении данных.
3. Через LiveData (если используется MVVM): Presenter может обновить данные, которые View наблюдает.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
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
👍6
🤔 Из каких основных элементов состоит Dagger?

1. Component: Интерфейс, определяющий, какие зависимости могут быть предоставлены.
2. Module: Класс, предоставляющий зависимости через аннотированные методы
@Provides.
3. Scope: Аннотации для управления временем жизни объектов (например,
@Singleton).
4. Inject: Аннотация для указания точек внедрения зависимостей.


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

1. Используйте аннотацию @Inject:
- На конструкторе класса, чтобы указать, что он должен быть создан Dagger-ом.
- На полях или методах, чтобы внедрить зависимости.
2. Добавьте класс или его фабрику в Module, если требуется кастомная логика создания.


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

1. Создайте ViewModel с зависимостями в конструкторе, аннотированном @Inject.
2. Используйте ViewModelProvider.Factory, чтобы Dagger мог создать экземпляр ViewModel с учетом всех зависимостей.
3. Инжектируйте Factory через Dagger и используйте её для получения ViewModel.

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

В MVI (Model-View-Intent) есть проблема: события, которые не нужно хранить в State, могут повторно отобразиться при пересоздании экрана (например, при повороте экрана или навигации назад).
Показ Toast / Snackbar
Навигация (openScreen())
Ошибки, которые нужно показать один раз

🟠Используем `SingleLiveEvent` (для ViewModel)
Это 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()
}


🟠Используем `SharedFlow` (современный подход)
В 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.iss.onessage, Toast.LENGTH_SHORT).show()
is UiEvent.NavigateToNextScreen -> findNavController().navigate(R.id.nextFragment)
}
}
}


🟠Используем `EventWrapper` (обходной путь для StateFlow)
Если в 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
👍9🤔2
🤔 Что делает аннотация Singleton?

Аннотация @Singleton указывает, что объект должен быть создан в одном экземпляре для заданного компонента. Это позволяет реализовать паттерн одиночки, обеспечивая единый экземпляр зависимости.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Forwarded from easyoffer
Привет, ребята!
1,5 года я учился на программиста, а сайт easyoffer.ru стал моим пет-проектом. Я создавал его, потому что:
а) нужно было добавить хоть какой-нибудь проект в резюме
б) подготовиться к прохождению собесов

И всё получилось! Благодаря еasyoffer я успешно прошёл собеседование и устроился Python Junior-разработчиком на удаленку с зарплатой 115 тысяч рублей.

Однако ещё во время разработки я понял, что у этого проекта есть потенциал. Казалось, что сайт может стать популярным и, возможно, превратиться в стартап.

По-этому я с самого начала заложил в проект минимальную бизнес-модель, на случай, если сайт начнёт набирать трафик. Я предложил пользователям полный доступ к сайту в обмен на подписку на Telegram-каналы. Это позволяло развивать аудиторию, а в будущем — зарабатывать на рекламе.

Результат превзошёл ожидания!
С момента запуска easyoffer посетило 400 тысяч человек. А когда доход с рекламы превысил мою зарплату программиста, я принял решение уйти с работы и полностью посвятить себя разработке новой версии сайта.

Вот так, зайдя в IT, через 4 месяца вышел через свой же пет-проект. Мне очень повезло

Уже год я работаю над easyoffer 2.0.
Это будет более масштабный и качественной новый проект:
– Появится тренажер
– Появятся задачи из собесов
– Фильтрация контента по грейдам
и еще очень много фич, о которых я расскажу позже.

Хочу, довести easyoffer до ума, чтобы сайт стал настоящим помощником для всех, кто готовится к собеседованиям.
По этому в ближайшее время я объявлю о старте краудфандинговой кампании, чтобы ускорить разработку и я готов щедро отблагодарить всех, кто поддержит проект.

А те, кто поддержат проект первыми, получат специальные лимитированные выгодные вознаграждения. Следите за этим телеграм каналом, если хотите стать первыми сапортерами.
👍5
🤔 Как в Ретрофите изменять все запросы?

В Retrofit можно изменять все запросы глобально с помощью Interceptor (перехватчика) в OkHttp. Это позволяет добавлять или изменять заголовки, параметры запроса, авторизацию, логирование и многое другое.

🟠Изменение всех запросов с помощью `Interceptor`
Перехватчик (Interceptor) позволяет модифицировать запрос перед его отправкой.
class AuthInterceptor(private val tokenProvider: TokenProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${tokenProvider.getToken()}")
.addHeader("Accept", "application/json")
.build()
return chain.proceed(request)
}
}


Добавляем перехватчик в OkHttpClient:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor(tokenProvider))
.build()

val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()


🟠Добавление общих Query параметров
Иногда нужно добавлять общие GET-параметры (например, API-ключ).
class QueryInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val originalUrl = originalRequest.url

val newUrl = originalUrl.newBuilder()
.addQueryParameter("api_key", "YOUR_API_KEY")
.build()

val newRequest = originalRequest.newBuilder()
.url(newUrl)
.build()

return chain.proceed(newRequest)
}
}


Добавляем в OkHttpClient:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(QueryInterceptor())
.build()


🟠Автоматическая повторная авторизация (Refresh Token)
Если сервер возвращает 401 Unauthorized, можно обновить токен и повторить запрос.
class AuthenticatorInterceptor(private val tokenProvider: TokenProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
var response = chain.proceed(request)

if (response.code == 401) {
// Получаем новый токен
val newToken = tokenProvider.refreshToken()

// Делаем новый запрос с обновлённым токеном
val newRequest = request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()

response.close() // Закрываем старый ответ
response = chain.proceed(newRequest) // Повторяем запрос
}

return response
}
}


Добавляем в OkHttpClient:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(AuthenticatorInterceptor(tokenProvider))
.build()


🟠Логирование всех запросов
Для отладки удобно логировать все запросы и ответы.
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()


🟠Полностью менять `baseUrl` (динамический URL)
Если в приложении нужно менять baseUrl, можно изменять его перед каждым запросом.
class BaseUrlInterceptor(private val urlProvider: UrlProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newUrl = urlProvider.getBaseUrl() + originalRequest.url.encodedPath

val newRequest = originalRequest.newBuilder()
.url(newUrl)
.build()

return chain.proceed(newRequest)
}
}


Используем в OkHttpClient:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(BaseUrlInterceptor(urlProvider))
.build()


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
🤔 К какому scope привязан Singleton?

@Singleton привязан к времени жизни компонента, в котором он используется. Например:
- В контексте Dagger AppComponent — объект существует, пока существует AppComponent.
- В приложении Android — часто привязывается к жизненному циклу приложения.


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

Используйте оператор flatMap:
- Преобразует элементы из одного потока в другой поток.
- Пример: из потока URL в поток содержимого страниц.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Forwarded from easyoffer
Ищу работу пол года

Практически под каждым постом в этом канале я вижу комментарии от людей, которые ищут работу по полгода. Это перерастает в обсуждение того, как нужно (или не нужно) искать работу, почему процесс найма сломан и как они откликались на фейковые вакансии.

Честно говоря, искать работу полгода — это нонсенс. Очевидно, что человек делает что-то не так. Главная ошибка, которую совершают многие, — это создание иллюзии поиска работы.

То есть человек вроде бы ищет работу, но делает это неэффективно, тратя время на нецелевые действия. Например:

Просматривает вакансии перед откликом.
Пытается понять, подходит ли он под вакансию. Если считает, что не подходит — не откликается.
Пишет сопроводительные письма (иногда даже уникальные под каждую вакансию).
Заполняет анкеты, проходит тесты.

Все эти действия отнимают время, но не приводят к результату.

Почему это не работает?

HR-менеджер не может вручную отсмотреть 2000 откликов, оценить каждое резюме и прочитать сопроводительные письма. Поэтому компании используют ATS-системы (системы автоматического подбора), которые анализируют резюме и определяют процент его соответствия вакансии.

Что делать, чтобы повысить шансы?

1️⃣ Добавить ключевые навыки в резюме — и в основной текст, и в теги. Возьмите их с easyoffer.ru

2️⃣ Убрать нерелевантный опыт, оставить только подходящий.

3️⃣ Оформить опыт так, чтобы он выглядел релевантным. Если у вас его нет, укажите проекты, стажировки или другой опыт, который можно представить как работу от 1 года. Если опыт слишком большой, сузьте его до 6 лет.

4️⃣ Откликаться на все вакансии без разбору. Если вы Junior, не ищите только стажер или Junior-вакансии — пробуйте везде. Не отказывайте себе сами, пусть это решит HR

5️⃣ Сделать резюме публичным, потому что HR-менеджеры часто ищут кандидатов не только среди откликов, но и в базе резюме.

6️⃣ Используйте ИИ по минимуму – ATS-системы считывают это и помечают "сгенерировано ИИ"

‼️ Главное правило: чем больше откликов — тем выше шанс получить оффер. Делайте резюме удобным для ATS-систем, и вас заметят.

1. Посмотрите видео о том как я вывел свою резюме в Топ1 на HH
2. Посмотрите видео как я нашел первую работу
3. Прочитайте этот кейс про оптимизацию резюме

Если прям вообще тяжело.

Создайте несколько разных резюме. Создайте 2, 3 да хоть 10 резюме. Настройте авто-отлики и ждите приглашения на собесы.

Не нужно создавать иллюзию поиска работы, сделайте несколько простых и актуальных действий.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие есть методы у класса Object?

В Java класс Object является корневым классом для всех остальных классов. Это значит, что все классы в Java неявно наследуются от Object, если явно не указано другое.

🚩Основные методы класса `Object`

🟠`equals(Object obj)`
Определяет, равны ли два объекта. По умолчанию использует сравнение по ссылке (==), но может быть переопределён для логического сравнения.
     class Person {
String name;

Person(String name) {
this.name = name;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name);
}
}

public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
System.out.println(p1.equals(p2)); // true
}
}


🟠`hashCode()`
Возвращает числовой хеш-код объекта, используемый, например, в HashMap. Если переопределяем equals(), нужно переопределить и hashCode().
     @Override
public int hashCode() {
return Objects.hash(name);
}


🟠`toString()`
Возвращает строковое представление объекта. По умолчанию – имя класса + хеш-код, но лучше переопределять.
     @Override
public String toString() {
return "Person{name='" + name + "'}";
}


🟠`getClass()`
Возвращает объект Class, описывающий класс объекта.
     System.out.println(p1.getClass().getName()); // Person


🟠`clone()`
Создаёт копию объекта (поверхностное клонирование). Объект должен реализовать Cloneable, иначе будет CloneNotSupportedException.
     class Person implements Cloneable {
String name;

Person(String name) {
this.name = name;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}


🟠`finalize()` *(устарел, не рекомендуется использовать)*
Вызывается перед удалением объекта сборщиком мусора. Лучше использовать try-with-resources и close().
     @Override
protected void finalize() throws Throwable {
System.out.println("Object is being garbage collected");
}


🟠`wait()`, `notify()`, `notifyAll()`
Методы для работы с многопоточностью. wait() – приостанавливает поток до вызова notify(). notify() – пробуждает один поток. notifyAll() – пробуждает все потоки.
     class SharedResource {
synchronized void doWait() throws InterruptedException {
wait();
}

synchronized void doNotify() {
notify();
}
}


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

1. Observable:
- Генерирует последовательность данных (0, 1 или больше элементов).
- Используется для стримов данных.
2. Single:
- Генерирует только один элемент данных или ошибку.
- Используется для операций, которые возвращают один результат (например, HTTP-запрос).


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