Java: fill the gaps
12.9K subscribers
7 photos
215 links
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк

🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Виш-лист разработчика: работа в стартапе и зарплата в долларах.

Пост рекламный, но о важных вещах.

В карьере разработчика сложны только первые шаги — войти в индустрию и набрать 3-4 года опыта. Спрос на программистов сильно превышает предложение, поэтому в IT большие возможности для самореализации.
Можно продвигаться в карьере, увеличивать зарплату или устраивать себе челленджи, например:
🔸️ Пройти путь от джуниора до архитектора за 5 лет.
🔸️ Разобраться в какой-нибудь теме, выступить на конференции и носить футболку с шуткой, которую поймут только 3 человека в мире.
🔸️ Пройти 8 этапов собеседований в Яндексе и отказаться от оффера.

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

Конечно, также велика вероятность:
▪️ ненормированного рабочего дня,
▪️ истеричных коллег,
▪️ закрытия проекта через 3 месяца.

Многие только после работы в стартапе поняли, что значить РАБОТАТЬ. Не закрывать задачи в джире и лениво отчитываться на стендапах, а решать проблемы здесь и сейчас, адаптироваться к изменениям, быстро внедрять новые технологии.

Работа с иностранными заказчиками — тоже специфический опыт. Удалённая работа, высокая зарплата, иностранный язык и иногда совсем другой менталитет.

Главная сложность — найти такие вакансии. На HeadHunter они если и появляются, то под названием «ИП Иванов А.И» или «ООО НьюСофт». Чаще всего их там нет🤷‍♂️

@profunctor_jobs — телеграм канал с супергорячими вакансиями и важной информацией — стек, зарплата, локация, график и контакт. Там ищут не только java разработчиков, но это реальный шанс найти интересные предложения. Среди их партнёров:
🤳Snap - компания владеет мессенджером Snapchat, умными очками Spectacles, сервисами Bitmoji и Zenly.
🚗Bolt – транспортная платформа, работающая в 30 странах.
💲Revolut – мультивалютные карты без процентов за конвертацию.

Рекомендуем подписаться и следить за вакансиями: @profunctor_jobs
Сколько лет Вы пишете на java?
Anonymous Poll
39%
Меньше года
35%
1-2 года
12%
3-4 года
14%
Больше 4 лет
Привет! Чтобы писать понятные и полезные посты, мы хотим знать больше о Вас и о том, что Вам интересно. Ответьте, пожалуйста, на 2 вопроса выше☝️
Ещё 4 коротких вопроса в https://forms.gle/Vqdfpjfm59BmPQBa6
Функциональный стиль в java: когда он нужен?

Большинство задач в энтерпрайзе делятся на две группы:
1️⃣ Одиночный запрос.
Покупка в интернет-магазине, вывод курса доллара — такие запросы затрагивают мало данных и много компонентов. Эти бизнес-процессы хорошо описываются объектами и отношениями между ними.

2️⃣ Обработка данных.
Статистика покупок, поиск рекомендаций, прогноз погоды - в центре внимания обработка данных, а не взаимодействие сущностей. Задача разработчика — чтобы обработка была быстрой. Нужно по максимуму использовать ресурсы процессора и доступную память, снизить время простоя и количество блокировок.

Вот здесь пригодятся некоторые подходы функционального программирования(ФП) :
1️⃣ Понятие чистой функции.
В многопоточном программировании главная проблема — работа с общими переменными. Чтобы безопасно их обновлять и всегда читать актуальные знвчения используются средства синхронизации. Большинство из них снижают общую пропускную способность.
Чистые функции - методы, которые характеризуются только входными и выходными данными. У них нет сайд-эффектов, Они не меняют внешних объектов и не бросают эксепшены. Для одинаковых входных данных результат всегда будет один и тот же.
Нет общих переменных — нет синхронизации.
Можно кэшировать некоторые результаты.

2️⃣ Неизменяемые данные
Исходные данные не должны меняться. Меняться могут только локальные переменные.
Синхронизация не нужна — общие переменные не меняются и всегда актуальны.

Пример: добавление элемента в список.
Подход ООП: добавляем в исходный список - меняем внешний объект.
public void add(List list, Integer value)
{ list.add(value); }
Минимальный расход памяти.
Для запуска в многопоточной среде нужна синхронизация.
Если метод прервался, то нужно выяснять, что он успел изменить, а что нет.

Подход ФП: создаём новый список на основе исходного и добавляем туда элемент.
public List add(List list, Integer value {
List newList=new List();
newList.addAll(list);
newList.add(value);
return list;
}
Можно использовать в многопоточной среде.
Если что-то пошло не так, просто ещё раз вызываем метод.
Большой расход памяти.

3️⃣ Декларативный стиль написания кода.
Обход коллекции в цикле — это императивный подход. Для каждого элемента задаётся чёткая последовательность действий.
Stream API — декларативный. Благодаря большому количеству встроенных функций можно просто описать то, что нужно: отфильтровать, сгруппировать, просуммировать.
Лучшая читаемость - не единственное преимущество такого подхода. Чистые функции и неизменяемые данные сами по себе тоже не сделают программу эффективной. Но их использование в Stream API дают компилятору и JVM большие возможности для оптимизаций. Cамая мощная из них — опция parallel(), она автоматически распределяет данные по параллельным подзадачам.

Начиная с 8 версии java можно применять и другие идеи ФП — функции высших порядков, currying, pattern matching и т.д. Некоторые подходы были доступны и раньше, например, использовать хвостовую рекурсию вместо итерации. Но наибольшую практическую пользу можно извлечь из обработки больших коллекций с помощью Stream API.

Писать программы в функциональном стиле на чистой java можно, но на практике это пока не оптимально. Для обработки потоков данных больше подходят библиотеки для реактивного программирования и стрим-фреймворки.
👍1
Сколько реализаций интерфейса Set в пакете java.util?
Anonymous Poll
25%
2
47%
4
23%
6
5%
8
java.util.Set: сравнение реализаций.

Set - интерфейс для коллекций из уникальных элементов. В стандартной библиотеке java 6 реализаций:
HashSet, TreeSet, LinkedHashSet, CopyOnWriteArraySet, ConcurrentSkipListSet и EnumSet.
В этой статье рассмотрим и сравним первые три из них.

Детали реализации:
1️⃣ TreeSet базируется на TreeMap, а он - на основе красно-чёрного дерева. Элементы коллекции должны реализовать интерфейс Comparable, либо экземпляр Comparable нужно передать в конструкторе, потому что compareTo - основной метод для вставки и поиска элементов.
Отсортированные значения.
Быстрая ребалансировка.
Можно искать по ближайшему ключу - доступны функции lower(e), floor(e), ceiling(e), higher(e).

2️⃣ HashSet внутри себя содержит HashMap. Он реализован так:
Есть массив корневых элементов - их ещё называют бакетами. Размер массива по умолчанию 16.
Если нужно добавить элемент в коллекцию, то сначала вычисляется его хэш. Далее ищется индекс корневого элемента - остаток от деления хэша на размер массива. Если по этому индексу ничего нет, корнем становится новый элемент. Если есть - добавляется в структуру под ним.
В java 7 этой структурой был список, с java 8 - бинарное дерево.
Эффективность HashSet зависит от равномерности распределения хэшей. В энтерпрайз приложениях хэш вычисляется по набору полей, и равномерность часто не достигается. Поэтому на практике HashSet не всегда является оптимальной структурой данных. Если известно примерное количество элементов, можно задать loadFactor и capacity в конструкторе.
Возможность оптимизации.
Быстрый поиск.
Значения не отсортированы.
Долгая перестройка при увеличении размера коллекции.

3️⃣ LinkedHashSet расширяет класс HashSet. Помимо основной структуры новые элемент также добавляются в двусвязный список.
Значения отсортированы по порядку вставки.
Возможность оптимизации.

Знание деталей реализации даёт простые правила выбора:
1️⃣ Частый поиск - выбираем TreeSet.
2️⃣ Порядок не важен - HashSet.
3️⃣ Порядок вставки важен - LinkedHashSet.

Мы проверили, насколько велика разница между реализациями:
1️⃣ Добавить в Set миллион чётных чисел.
2️⃣ Вставить миллион нечётных.
3️⃣ Найти миллион случайных.
Запустили несколько раз для HashSet, TreeSet, LinkedHashSet, а также для HashSet и LinkedHashSet с заранее заданной ёмкостью в 2 миллиона. Проверили на java 7 и 11.
В качестве типов данных использовали Integer(идеальное распределение хэшей) и класс с несколькими полями (неравномерное распределение).

Результаты на картинке внизу поста. Тест не претендует на точность, но теперь можно добавить ещё несколько рекомендаций:
В новых версиях java все операции всех реализаций работают на 5-20% быстрее.
Выбор реализации имеет значение.
Знаете будущий размер коллекции - передавайте его в конструкторе.
Для чисел лучше использовать HashSet.
Для экземпляров классов всё неоднозначно, имеет смысл попробовать разные варианты.
Что выведет следующий код?

List list=new ArrayList();
list.add(1);
list.add(2);
Stream stream=list.stream();
list.remove(0);
println(stream.count());
Stream API: обзор и основные методы.

Stream API - удобный инструмент обработки данных, который появился в java 8. Два принципа, на которых строится Stream API - composition & single responsibility. Действия собираются из простых независимых друг от друга блоков без сайд-эффектов. Этим достигается удобство чтения и поддержки:
Стандартные названия.
Нет локальных переменных.
Линейная последовательность действий.

Стрим состоит из 3 частей:
1️⃣ Источник данных.
2️⃣ Преобразования.
3️⃣ Конечная операция.

Основные способы создать стрим:
🔸 Из готовой коллекции
🔸 Из набора элементов
🔸 Из массива
🔸 Задать первый элемент и правило вывода последующего, получится бесконечный стрим: Stream.iterate(T, BinaryOperator)
🔸 Задать первый элемент, правило вывода последующего и условие остановки: Stream.iterate(T, Predicate, BinaryOperator) (java 9)
🔸 Генерировать независимые друг от друга элементы: Stream.generate(Supplier)
🔸 Из диапазона с включением граничных значений: IntStream.range(..)
🔸 Диапазон без граничных значений: IntStream.rangeClosed(..)
🔸 Из строк: BufferedReader.lines()
🔸 Из символов строки: CharSequence.chars()

Названия большинства методов преобразований говорят сами за себя: filter, distinct, limit, sorted. Есть менее понятные:
🔹 map: применить функцию к каждому элементу.
🔹 flatmap: положить элементы из "списка списков" в единый стрим. Также с его помощью можно уменьшить размерность массива.
🔹 takeWhile(Predicate): брать элементы из стрима, пока выполняется условие. Метод доступен c java 9.
🔹 dropWhile(Predicate): пропускать элементы, пока условие не нарушится. Метод доступен c java 9.
🔹 peek(Consumer): сделать что-то с каждым элементом стрима, не меняя исходный стрим. Часто используется при дебаге, например, вывести в консоль текущие элементы.

Вычисления выполняются не сразу, а только когда вызывается конечная операция. Конечная операция описывает желаемый результат. Результатом может быть:
◾️ Структура данных: toArray, collect.
◾️ Результат поиска: anyMatch, allMatch, nonMatch, findFirst, findAny.
◾️ Результат агрегации: min, max, count, reduce.
◾️ Сайд-эффект: forEach, forEachOrdered.

Стрим - не структура хранения данных. Он обходит источник данных (или сам его генерирует) и сохраняет промежуточные результаты. По этой причине нельзя копировать стримы и исполнять их несколько раз - пришлось бы контролировать этапы исполнения, консистентность данных и добавить синхронизацию для работы в многопоточной среде. При попытке переиспользования стрима выбрасывается IllegalStateException.
Исходные данные не меняются.
Стрим нельзя переиспользовать.
Пока не вызвана конечная операция, стрим можно менять как угодно.

Пример из опроса выше вернёт 1.
list.remove(0) удаляет элемент с индексом 0, поэтому на момент старта вычислений stream.count() в источнике данных остаётся всего один элемент.

Если Вас раздражает, что метод list.remove принимает не сам элемент, а индекс, то некоторые методы Stream API Вас тоже разочаруют. Например, такой код компилируется:
Stream.of(-1, 0, 1).max(Math::max).get();
Но в результате получается -1, потому что входной параметр метода max - Comparator. Math.max по сигнатуре на него похож, поэтому получается неверный результат😒
👍8
Stream API: забудьте про peek()

Один из недостатков Stream API по сравнению с циклами - неудобный дебаг. До сих пор метод peek() был единственным способом посмотреть элементы внутри стрима:
list.stream().filter(..)
.peek(e->println("filt:"+e))
.map(..)
.peek(e->println("map:"+e))
...

Недавно в Intellij IDEA вышел потрясающий апдейт, который выводит дебаг стримов на новый уровень:
🔥6
10 правил безопасных java приложений.

Скорость, масштабируемость, удобство использования — такие требования обычно ставят заказчики. Устойчивость к атакам и защита данных редко проговаривается и подразумевается по умолчанию.
Для кода на java есть много рекомендаций, например, Secure Coding Guidelines for Java SE. На практике большинство ошибок можно избежать очень просто:
1️⃣ Понятный код и однозначная бизнес-логика - уязвимости легко пропустить в чём-то сложном и запутанном. Сужайте и скрывайте доступную функциональность на всех уровнях:
🔸 Модификаторы полей и классов: всё, что может быть private, делайте private. Если для задачи удобно поменять private на public, подумайте о других вариантах.
🔸 Reflection и проверка имён классов: используйте осторожно, т.к такой код не адаптируется к изменениям. Поменяется имя метода, добавится ещё один класс, а рефлекшн останется тем же.
🔸 Внешний API: чем проще, тем лучше.
2️⃣ Избегайте сериализации: чтобы хранить и передавать объекты внутри системы используйте облегчённые протоколы с поддержкой версионирования: gRPC, Avro, Thrift и т.д.
3️⃣ Шифруйте персональную информацию: пароли, номера карточек, кодовые слова. Не записывайте её в лог! В 2019 году перехват незащищённых данных всё ещё входит в топ-3 опасностей по версии OWASP.
4️⃣ Проверяйте входные данные от пользователей и сторонних систем. Даже если похожие проверки есть на фронтенде. Ограничивайте размер пользовательского ввода и результатов: не позволяйте загружать 5 ГБ картинку и запрашивать триллион записей.
5️⃣ Используйте PreparedStatement для запросов к БД. Для большинства NoSQL они тоже есть.
6️⃣ Скрывайте технические детали в сообщениях об ошибке: не показывайте стек трейс конечному пользователю и в консоли браузера.
7️⃣ Обновляйте библиотеки, не игнорируйте авто-апдейты.
8️⃣ Проверяйте open-source библиотеки, которые используются на проекте, например, с помощью OWASP Dependency-Check. Коммит вредоносного кода в open-source - реальная угроза.
9️⃣ Записывайте и анализируйте активность пользователей - как минимум неудачные попытки авторизации.
🔟 Тестируйте методы на переполнение, оно случается.
5
Вопрос #1
Сколько элементов выведется в консоль?
Anonymous Poll
62%
2
38%
3
Вопрос #2
А сколько элементов выведется, если заменить for на forEach?
Anonymous Poll
49%
2
51%
3
Обход коллекций в Java.

Чтобы ответить на вопросы выше, посмотрим на обход более сложных сущностей, например, графа. Граф - структура данных из вершин и рёбер. В графах часто встречаются циклические пути. Найти гамильтонов путь, то есть обойти вершины графа по одному разу - непростая задача. Алгоритм ведёт учёт посещённых вершин и перебирает разные варианты прежде, чем утвердить один из них. В computer science обход структуры данных называется traverse. Основная идея — выводим текущий элемент, следующий пока неизвестен и считается отдельно.

Вернёмся в мир java. Здесь нет зацикленных графов, все просто — списки, множества, очереди, деревья. При обходе следующий элемент всегда однозначен, а его отсутствие означает конец работы. Поэтому обход выглядит так:
Iterator it=list.iterator();
while(it.hasNext())
int result = it.next();

Метод next() возвращает текущий элемент и сразу сдвигает указатель на следующий. Метод hasNext() проверяет, ссылается ли этот указатель куда-нибудь. Этот паттерн повторяется снова и снова и называется Iteration. Важно - указатель на следующий элемент вычисляется заранее. Итератор лежит в основе синтаксиса for (T e: collection).

Так как следующий элемент известен заранее, итератор может показать удалённый элемент. Или не вывести только что добавленный.

ArrayList, HashMap, HashSet и тд. не синхронизированы. Если одновременно итерировать и менять коллекцию разными потоками, можно нарушить целостность данных. Есть два способа этого избежать:
1️⃣ Fail-fast итераторы бросают ConcurrentModificationException при изменениях во время итерации.
2️⃣ Fail-safe итераторы работают с неизменяемой структурой.
Большинство однопоточных коллекций реализуют fail-fast подход.

ConcurrentHashMap потокобезопасен, поэтому изменения во время обхода разрешены. Обход через for реализован через итератор. Указатель на следующий элемент вычисляется заранее. Более подходящий новый элемент не отображается, и выводится 2 элемента.

Метод forEach в ConcurrentHashMap использует подход траверс и вычисляет следующий элемент только когда он запрашивается. Поэтому новый ключ подхватывается и выводится 3 элемента.

Почему нельзя использовать траверс по умолчанию?
➡️ Потому что итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.

Зачем нужно несколько вариантов?
➡️ ConcurrentHashMap может перестраиваться во время обхода. Чтобы во время перестройки не выводить пользователю дубликаты, используется траверс со сложной логикой.

Итого: при выводе элементов ConcurrentHashMap через for и forEach используются разные алгоритмы обхода, поэтому результат вывода тоже разный.

❗️Cинтаксис forEach реализован и для однопоточных коллекций, но там используется итератор, поэтому разницы с for нет.
Intellij IDEA: пишем код быстрее.

1️⃣ Live templates.
Аббревиатуры для популярных синтаксических конструкций. Вводите и нажимаете Enter: 4 символа разворачиваются в 40, а курсор приходит в нужную позицию.
Полный список сокращений в File/Settings/Editor/Live Templates. Есть для Java, Kotlin, JS, Groovy, для разработки под Android и React.

Самые популярные для Java:
▫️St → String
▫️sout
System.out.println();
▫️main
public static void main(String[] args){}
▫️prsf
private static final

Некоторые сокращения разворачиваются в методы с параметрами для автозаполнения. Перемещаться между ними можно с помощью Tab:
▫️ fori
for (int i=0; i< ; i++) {}
▫️ifn
if (args == null) {}
▫️mx
= Math.max(, );
▫️lazy
   if (obj == null) 
{ obj = new Integer(); }

2️⃣ Code completion.
Дополнение имен классов, методов и полей на основе контекста. Варианты появляются в выпадающем списке. Можно писать только начало:
▫️Int → Integer
▫️Cust → Customer
Можно писать заглавные буквы в названиях классов:
▫️NPE → NullPointerException
▫️CHM → ConcurrentHashMap

Можно оборачивать код в синтаксические конструкции:
▫️count == 4.if
if (count == 4) {}
▫️list.for
for(Integer i : list) {}
▫️obj.opt
Optional.of(obj)
▫️answer.switch
switch (answer) {}

Полный список таких дополнений в File/Settings/Editor/General/Postfix Completion. Есть варианты не только для Java, но и для Kotlin и JS.
🔥2