en
Feedback
Javanese Online

Javanese Online

Open in Telegram

Статьи и новости, наблюдения и советы. Кодревью: http://javanese.online/%D1%80%D0%B0%D0%B7%D0%B1%D0%BE%D1%80_%D0%BA%D0%BE%D0%B4%D0%B0/ Обсуждение в @javanese_questions Материалы пишет @Harmonizr

Show more
723
Subscribers
No data24 hours
+37 days
+530 days
Posts Archive
Throwable — волшебный класс! fillInStackTrace(): запомнить текущий стек. Некоторые создают эксепшон и сразу же вызывают этот метод. Это бесполезно, он ведь и так вызывается в конструкторе. А вот в каком-нибудь другом месте можно навязать любому эксепшону текущий стек. Удачной отладки! Метод, кстати, нефинальный. Можно переопределить его и сделать no-op, получив дешёвый эксепшон без стека. getMessage(), getCause(), printStackTrace(), toString() тоже нефинальные. Если хочется пострелять с двух рук, можно переопределить их и бросить оттуда новый exception. setStackTrace(): вообще вседозволенность. Да, можно использовать его в благих целях, дабы прятать интринсики языка, а можно приколоться над коллегами!

Сравнительная характеристика JDBC и Android SQLite API SQLite в Android сильно отличается от привычного для джавистов JDBC. Вот краткий справочник, что как называется и каким образом себя ведёт. Помощник для миграции В Android мы получаем базу данных от SQLiteOpenHelper, где есть коллбэки для миграции. В JDBC такой штуки нет, зато есть flywaydb.org и liquibase.org. База данных Отправная точка в Android называется SQLiteDatabase, в JDBC — DataSource, зачастую из HikariCP. Соединение JDBC: DataSource#getConnection() → Connection. В Android же соединения спрятаны от нас, под капотом реализован пул. Готовимся выполнить запрос JDBC: Connection#createStatement() → Statement Android: неть, не готовимся. Выполнить запрос JDBC: Statement#execute(String) → ResultSet Android: SQLiteDatabase#(raw)Query(…) → Cursor Таким образом, Android избавляет нас от получения коннекшона и создания стейтмента, мы получаем курсор на руки буквально вызовом одного метода.
// JDBC
hikariPool.connection.use { conn ->
    conn.createStatement().use { stmt ->
        stmt.execute("SELECT …").use { rs ->
            TODO()
        }
    }
}

// Android SQLite
db.rawQuery("SELECT …").use { cur ->
    TODO()
}

Чтение ResultSet и Cursor очень похожи, но в деталях есть разница. Например, ResultSet не знает своего размера. И нужно не забыть закрыть Statement заодно с ResultSet. Транзакции Android SQLiteDatabase:
beginTransaction(NonExclusive)()
setTransactionSuccessful()
endTransaction()
JDBC Connection:
setAutoCommit(false)
commit() || rollback()
setAutoCommit(true)
Да, слово «транзакция» здесь вообще не фигурирует. Кстати, в одном из предыдущих постов у меня потекла транзакция именно потому что я забыл вернуть autoCommit. Мутации Для них удобнее всего подходят подготовленные запросы. JDBC: Connection#prepareStatement(SQL) → PreparedStatement Android: SQLiteDatabase#compileStatement(SQL) → SQLiteStatement Тут принципиальной разницы нет. Вре́менные таблицы и триггеры Вот здесь спряталась хитрость. В любой СУБД временные таблицы существуют только в пределах соединения, их создавшего. Именно в SQLite есть ещё один нюанс — триггеры тоже локальны для соединения ввиду отсутствия сервера. В Android же это ограничение не действует, как если бы SQLiteDatabase оперировала лишь одним соединением. InvalidationTracker в Room тоже написан таким образом, будто этого органичения не существует. Почему оно так работает? Для меня это открытый вопрос.

О сколько нам открытий чудных готовит спека DNS! Держу в курсе: доменное имя без точки в конце считается неполным, относительным. В эпоху умерших интранетов и локальных сетей об этом уже никто не вспомнит… Обнаружено в процессе чтения исходников OkHttp. https://webmasters.stackexchange.com/questions/73934/how-can-urls-have-a-dot-at-the-end-e-g-www-bla-de

OOM Resurrector У меня на сервере постгрес раз в неделю съедает всю доступную память и своп. Настройки крутил, всё уменьшал,
OOM Resurrector У меня на сервере постгрес раз в неделю съедает всю доступную память и своп. Настройки крутил, всё уменьшал, но нет никакого аналога нашего -Xmx, поэтому и нет гарантии, что постгря не падёт жертвой ООМ-киллера. В интернете есть скрипт OOM-воскресителя, прикладываю свою версию, которая ориентируется на RAM+swap.
#!/bin/bash
THRESHOLD=1000 # MB
INTERVAL=60 # s

while :
do
    free=$(free -tm | awk '/^Total:/{print $4}')
    echo "Free $free""MB"

    if [ "$free" -lt $THRESHOLD ]
    then
        systemctl restart postgresql@14-main
        echo '$ systemctl restart postgresql@14-main'
    fi

    sleep $INTERVAL
done

Команда: nohup ./pg-resurrect.sh > pg-resurrect.log &

Какой алгоритм решит мою задачу? У нас есть один язык, который самостоятельно выбирает наиболее подходящие алгоритмы и структуры данных из имеющихся. Этим студентом был SQL. Если повезёт, задача выбора алгоритма сведётся к написанию SQL-запроса, а вывод EXPLAIN прямым текстом назовёт нужный алгоритм.

Пол раза в жизни встречал ViewModel + MutableStateFlow. Попросили объяснить, как использовать. Чем дальше в лес, тем меньше понимания, как и зачем этим вообще пользоваться и под какими веществами это придумали 🤦 Ожидание: val vm by viewModels(factory = { MyCoolViewModel(args) }) Реальность:
val vm: MyCoolViewModel by viewModels(factoryProducer = {
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(...): T =
            MyCoolViewModel(args) as T
    }
})


Ожидание: val stateSmth = savedStateHandle.getMutableStateFlow("smth", defaultSmth) Реальность: отрицание, гнев, торг, пост в канал, кастомный флоу.

А правильно ответил @dimitreuz: если ответ от неудачного запроса нам ценен, нужно его вычитать и закрыть, освободив сокет для
А правильно ответил @dimitreuz: если ответ от неудачного запроса нам ценен, нужно его вычитать и закрыть, освободив сокет для следующего запроса.

IllegalStateException: cannot make a new request because the previous response is still open: please call response.close() За
IllegalStateException: cannot make a new request because the previous response is still open: please call response.close() Занятный крэш меня сегодня посетил. Типовое обновление токена. Нетрудно понять, где именно баг, но как именно вы бы его чинили?

Вопрос, который мне не задали на конференции: является ли SVG path-data Тьюринг-полным языком?
Anonymous voting

За три минуты и с первой попытки побеждает @italankin! Прикрепляю фикс.
За три минуты и с первой попытки побеждает @italankin! Прикрепляю фикс.

Немного баготворительности. В каком сценарии сеттер сработает неверно?
Немного баготворительности. В каком сценарии сеттер сработает неверно?

Курс по ФП на Java Знаю, что «восьмёрке» уже восемь лет, но этот курс мне скинули буквально месяц назад. Функциональная композиция, стримы-коллекторы, CompletableFuture и прочие монадки в задачах и примерах. https://stepik.org/course/91497/syllabus

Опять про андроид. И снова сохранение состояния Есть паттерн такой — назовём его custom composite view: наследуем любой подходящий layout, инфлейтим внутрь него <merge> и переиспользуем целиком. Описан в этой статье и много где ещё. Но, как говорится, есть один нюанс. Если на экране более одного такого компонента, айдишки внутри начинают дублироваться. В итоге при сохранении состояния побеждает тот, кто сохранялся последним, щедро раздавая всем компонентам своё состояние при восстановлении. Для вьюшек с пользовательским вводом, таких как текстовые поля, слайдеры, чузеры и вот это всё, проблема прям критичная. Нужно переопределить dispatch(Save|Restore)InstanceState, которые отвечают за сохранение-восстановление всего поддерева. Далее — вариации: а) можно вызвать dispatch(Freeze|Thaw)SelfOnly(container), как это делает ресайклер, и в on(Save|Restore)InstanceState сделать всю нужную работу. Придётся унаследовать SavedState, к сожалению; б) а можно взять какой-нибудь ParcelableSparseArray и передать его в super.dispatchSaveInstanceState и положить его в container под своим id, изолировав таким образом состояние всего компонента в отдельное пространство айдишек.

Платформа для донатов Очередная #идея, которую я (да ладно, будем честны, подслушал и) дарю любому банку/финтеху. Организация или отдельно взятый волонтёр открывает специальный счёт для донатов. Для счёта генерируется специальная общедоступная страничка — там эквайринг и история операций, к которой автоматом прикрепляются электронные чеки. Всё, любой благотворительный проект может вылезти из пещерного века, выбросить камень, плуг, соху, десять карт разных банков и систем (потому что эквайринг) и гигабайт фоток чеков на телефоне.

Adapter Delegates Почему я не использую «классические» адаптерДелегаты? Смотрим невооружённым глазом:
val adapter = ListDelegationAdapter<List<Animal>>(
    catAdapterDelegate(...),
А если приподнять завесу «няшного» Kotlin DSL, то вообще увидим ЭТО:
public boolean isForViewType(List<Animal> items, int position) {
    return items.get(position) instanceof Cat;
}
Напрашивается следующий вывод: • можно забыть нужный делегат (крэш в рантайме), • а можно забыть удалить лишний (недостижимый код), • нельзя использовать несколько делегатов для одного типа данных. Первые два пункта — классика: в Retrofit можно переехать с RxJava на suspend и забыть убрать CallAdapter.Factory, а в Gson есть «изкоробочные» адаптеры для, скажем, AtomicIntegerArray, которые психически здоровому человеку не нужны, а больному — вредны. Третий пункт — ваще критикал: я хочу пару разных делегатов-заголовков, оба с типом CharSequence но разными шрифтами, отступами, размерами. Или в вертикальном списке имею горизонтальные списки всякого разного, которые сплошь instanceof List. Короче, я создал, хоть и столько лет спустя. Под капотом там просто сдвоенный List из элементов и их делегатов. В комплекте валяется ItemDecoration, который могёт… а, не буду пересказывать скриншот, он в конце readme. Лучи добра всем трудящимся!

Итак, $company хочет вас схантить
Anonymous voting

Gson 2.9.0 Пару месяцев назад тихой сапой вышло обновление объекта обожания всех рефлектологов и ансейфоведов. Моё внимание привлекли эти два изменения: • Support EnumMap deserialization (#2071) • Add GsonBuilder.disableJdkUnsafe() (#1904) Вот и отлично! Не прошло и десяти л… а, нет, прошло :) Того гляди, скоро и EnumSet начнёт поддерживать. https://github.com/google/gson/blob/master/CHANGELOG.md

Пять стадий написания LayoutManager 1. 😨 Быть такого не может, чтобы ранее такую штуку никто не делал! 2. 😡 Нет, ну почему никто не запилил?! 3. 🤬 Может, как-нибудь попроще, без менеджера? 4. 😢 *гуглинг в гитхабе* 5. 😌 Ладно-ладно, пойду писать. Итого: Flow (раскладывает в строчку, переносит на новую при необходимости) с возможностью ограничить количество строк и показать специальную вьюшку «ещё 100500». https://github.com/Miha-x64/FlowLayoutManager/