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

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

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Изучаете новую технологию? Не забывайте про контекст.

В интернете полно статей о языках программирования, фреймворках и библиотеках. Большинство из них показывают синтаксис на простых, но часто нереалистичных примерах. Этой информации хватает для собеседований, докладов и расширения кругозора. Однако для продуктивной работы важен не только синтаксис, но и контекст использования. Он включает в себя ответы на вопросы:
🔷Какую проблему решает Х?
🔷Какие альтернативы для решения этой же проблемы?
🔷В каких условиях Х лучше, чем альтернативы?
🔷За счёт чего Х лучше?

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

Рассмотрим пример. Любой курс по многопоточности начинается с понятия потока и изучения класса Thread. Обычно автор описывает жизненный цикл потока, доступные методы и показывает пример создания потока с интерфейсами Runnable и Callable.

Можно ли на основе только этих знаний решать задачи? Можно, но вряд способ будет оптимальным. Посмотрим на класс Thread поближе и попробуем ответить на вопросы выше:

Какие задачи можно решать в отдельных потоках?
🔸Обработка большого количества данных
🔸Слабосвязанные подзадачи

Есть ли альтернативы для этих задач?
Обрабатывать данные можно с помощью ForkJoinPool. Для выполнения подзадачи в другом потоке есть ExecutorService. Можно использовать виртуальные потоки: Kotlin continuations, библиотеку Quasar и другие.

Когда ручное управление потоком лучше, чем варианты выше?
Сложно сказать. ForkJoinPool и ExecutorService эффективнее используют ресурсы и предоставляют высокоуровневый интерфейс, поэтому ими легко пользоваться. Виртуальные потоки лучше работают с маленькими задачами и блокирующими вызовами.

Класс Thread появился в java 1.2. Это низкоуровневый инструмент, в 2020 году с ним решаются только очень специфичные задачи. Для популярных задач появилось много специализированных классов и фреймворков.

Обзорные статьи и доклады - это стартовые точки при изучении технологий. Нужно двигаться к пониманию, как и когда это применять на практике. Как это сделать? Лёгкого пути нет, нужно по крупицам собирать общую картину из разных источников и, конечно, читать посты на @java_fillthegaps💪
👍6
Что из нижеперечисленного вызовет ошибку компиляции?
Интерфейсы. Часть 1: поля и методы.

Интерфейс — инструмент поддержки инкапсуляции. Что можно добавить в интерфейс в разных версиях java?

Java 7
Только нестатические public методы:
interface Интерфейс {
public void метод();
}

Классы реализуют эти методы под угрозой ошибки компиляции:
class M implements Интерфейс {
public void метод() {…};
}

Java 8
Появились 2 новых возможности:
1️⃣ Метод по умолчанию с заданной реализацией:
default void метод() {…};
Конкретный класс переопределяет его при необходимости.

Зачем они нужны?
Дефолтные методы уместны в трёх случаях, об этом будет статья в среду.

2️⃣ Статические методы с реализацией:
static void метод() {…};
Статические методы не наследуются и не переопределяются. Вызвать такой метод можно только через имя интерфейса:
Интерфейс.метод()

Зачем они нужны?
В интерфейсе определяется необходимый минимум методов. Например, в Collection это методы add(), remove() и другие. Чем меньше методов, тем больше свободы действий у конкретных классов.
Эти базовые методы комбинируются между собой в другие полезные методы:
▪️Поиск элемента,
▪️Поиск минимального элемента,
▪️Скопировать коллекцию,

Каждый из этих методов - всего лишь комбинация базовых, которая не зависит от конкретного класса. Чтобы не копировать код в каждый класс, сделайте такой метод статическим методом в интерфейсе:
static E min() {…};

До java 8 статические методы объединяли в утилитный класс с похожим именем. Например, методы binarySearch, copy, min реализованы в классе Collections.

Java 9
Можно добавить private и private static методы с реализацией:
private void m() {…};
private static void m() {…};
Эти методы недоступны для классов, реализующих интерфейс.

Зачем они нужны?
Чтобы выделить под-методы в дефолтных и статических методах, таким образом улучшить читаемость и избежать дублирования кода.

С методами разобрались, а какие поля можно добавлять в интерфейс?
Только константы с модификаторами public static. Из-за того, что других вариантов нет, в Intellij IDEA модификаторы public и static подсвечиваются как избыточные и вместо
public static int NUM = 1;
допустима запись:
int NUM = 1;
Такие поля компилируются в статические!

Правильный ответ на вопрос:
Ошибку компиляции в последней версии java вызовут варианты:
default int get();
private int get();
public static int get();

У приватных, статических и дефолт методов в интерфейсах должна быть реализация.
1
Что выведется на консоль?
Что выведется на консоль?
Anonymous Poll
32%
А
7%
В
61%
Ошибка компиляции
Интерфейсы, часть 2: методы по умолчанию.

В java 8 появилась новая возможность интерфейсов - методы с заданной реализацией:
interface Adapter {
default int get() {…}
}
Класс, который реализует интерфейс, переопределяет такой метод при необходимости.

Зачем нужны такие методы?
1️⃣ Облегчить изменения API
Во времена java 7 интерфейсы содержали только определения методов:
interface Collection<Т> {
void add(Т);
Т get();
}
Чтобы добавить, удалить или поменять сигнатуру метода нужно одновременно поменять код со стороны пользователей интерфейса и во всех реализациях. В рамках одного проекта это несложно, но для библиотек это сложная задача. Пользователи могут столкнутся с проблемами совместимости при переходе на новую версию.

Задача дефолтных методов - сгладить этот процесс и предоставить приемлемую или временную альтернативу.

2️⃣ Вспомогательные методы
Те методы, которые не входят в прямую функциональность интерфейса - методы для мониторинга, логгирования и т.д. Используйте с осторожностью, этот приём может нарушать принцип Interface segregation.

3️⃣ Комбинации базовых методов
В интерфейсе объявляется минимально необходимый набор методов, а некоторые методы являются просто их комбинациями. Мы обсуждали этот случай в прошлом посте. Такой "удобный" метод можно добавить в интерфейс как статический.

Статические методы вызываются через имя интерфейса и недоступны у экземпляров. Если это неудобно, оформите метод как метод по умолчанию.

Пример: интерфейс Comparator.
Основная функция — сравнение объектов через метод compare, индивидуальный для каждого класса. Цепочка из 2 компараторов - это 2 вызова compare и объединение результатов. Логика всегда одинакова, и нет смысла дублировать её в каждом подклассе:
default Comparator<T> thenComparing(Comparator) {…};

Что если класс реализует 2 интерфейса с методами по умолчанию?
Для разрешения конфликта используются 2 правила:
1️⃣ Если в классе переопределён метод по умолчанию — используется метод класса.
2️⃣ В иерархии интерфейсов используется метод наследника:

interface Child extends Parent
Если класс реализует интерфейс Child, то будет использоваться дефолтный метод интерфейса Child.

В опросе перед постом ни одно правило не подходит, поэтому будет ошибка компиляции. Чтобы разрешить конфликт, класс должен явно реализовать метод get();
1
Что выведется в консоль? Обратите внимание, интерфейс В теперь расширяет интерфейс А
Что выведется в консоль?
Anonymous Quiz
25%
А
42%
В
33%
Ошибка компиляции
👍3
Интерфейсы, часть 3: различия с абстрактным классом.

Отличия интерфейса и абстрактного класса — популярный вопрос на собеседовании. Во времена java 7 ответ был простой: «В интерфейсе нет реализаций методов».

В java 9 в интерфейс можно добавить приватные, статические и дефолтные методы с готовой реализацией. Это сближает интерфейсы с абстрактными классами.

В чём теперь разница интерфейса и абстрактного класса?
Сравним по нескольким критериям:

1️⃣ Абстрактный класс.
Можно добавить:
▪️Конструкторы.
▪️Реализацию экземплярных методов.
▪️Нестатические поля.
▪️private static поля.
▪️Модификаторы final, synchronized, protected.

🔨Использование:
Класс реализует не больше одного абстрактного класса через ключевое слово extends. В имени часто содержится Abstract, Template, Base.

✴️ Назначение:
Шаблон класса. Вспомогательная структура, чтобы не дублировать код в классах одной иерархии.

⭐️Репутация:
Средняя. Абстрактный класс сокращает объем необходимого кода, когда иерархия классов конечна или известна заранее. Большое количество абстрактных классов считается анти-паттерном, у такой системы плохая читаемость, и в неё сложно вносить изменения.

2️⃣ Интерфейс
▪️Только статические поля.
▪️Методам с реализацией недоступны экземплярные поля, поэтому их возможности слабее, чем у абстрактных методов.

🔨Использование:
Класс реализует несколько интерфейсов через ключевое слово implements. 5 лет назад интерфейсам было модно добавлять суффикс able: Iterable, Comparable. Норма сегодняшнего дня - называть интерфейс по тем же правилам, что и класс.

✴️ Назначение:
Описывает методы для верхнеуровнего взаимодействия с классом, модулем или системой.

⭐️Репутация:
Высокая. Широко используется в большинстве паттернов GoF, принципах SOLID. Поддерживает инкапсуляцию.
👍7
Технологии в вакансиях HeadHunter.

Позиция: Java-разработчик
Город: Санкт-Петербург
Вакансий: 309

Собрала статистику вакансий HeadHunter для разработчиков с разным уровнем опыта. В списке указана технология и % упоминания в вакансиях. Это значит, что технология находится либо в требованиях, либо в описании проекта. Иногда категории объединены, а в скобках указаны самые распространенные варианты. Списки получились очень скучные, но хорошо отражают стек большинства java проектов🧐

Разработчики без опыта:
60▫️SQL (PostgreSQL, Oracle)
50▫️Spring (MVC, Boot, Security, Data)
45▫️Hibernate
40▫️Git
30▫️Maven
25▫️Apache Tomcat
25▫️Docker
20▫️Linux
15▫️Jenkins

Опыт от года до 3х лет:
66🔸SQL
61🔸Spring (MVC, Boot, Security, Data, Cloud)
60🔸Git
34🔸Maven
34🔸Docker
30🔸Hibernate
21🔸Jenkins
17🔸Linux
17🔸Big Data (Hadoop)
17🔸Kotlin
11🔸Cloud платформы (AWS, GCP)

Опыт от 3х до 6 лет:
69▪️SQL
67▪️Spring (MVC, Boot, Security, Data, Cloud)
40▪️Git
37▪️Docker
28▪️Maven
26▪️Hibernate
25▪️Apache Kafka
23▪️Kubernetes
21▪️Kotlin
18▪️jenkins
17▪️Linux
17▪️Cloud провайдеры
16▪️noSql (Redis, MongoDB, Cassandra, CouchBase)

Опыт более 6 лет:
65🔹SQL
59🔹Spring (Boot, Data, Cloud)
35🔹noSql (Redis, MongoDB, Cassandra, CouchBase)
35🔹Big Data (Hadoop)
35🔹Docker
35🔹Cloud провайдеры (AWS, GCP)
29🔹Apache Kafka
24🔹Kotlin
24🔹Git
24🔹ElasticSearch
24🔹RabbitMQ
18🔹Maven
18🔹ELK
18🔹Kubernetes
12🔹SAP
HeadHunter: 3 совета по поиску вакансий.

1️⃣ Не пользуйтесь фильтрами.
Фильтрация скрывает вакансии, у которых параметры не выставлены или выставлены неверно:
🔸У 10% вакансий выставлен неподходящий опыт работы. Например, в Спб для Senior разработчиков есть 14 вакансий с тегом "опыт не требуется" или "1-3 года".
🔸Адрес стоит только у 53% вакансий. Иногда у компании есть несколько зданий в разных районах города, а указан адрес только одного из них.
🔸Уровень заработной платы указан в 17% вакансий. Это слабый ориентир, реальный диапазон зарплат часто шире указанного. Оффер зависит от опыта, соответствия проекту и наличию других кандидатов.

2️⃣ Пробуйте разные запросы в строке поиска.
Границы Junior/Middle, Middle/Senior, Senior/Lead нечёткие, поэтому имеет смысл откликнуться на вакансии уровнем выше или ниже желаемой должности. Некоторые компании не акцентируют внимание на иерархии и называют все вакансии Java Developer. Технический специалист смотрит резюме и соотносит его с задачами проекта. Если Вы интересный кандидат, то без проблем получите подходящую должность и зарплату.

Если Вы чётко уверены в своём уровне, пишите желаемую позицию на разных языках:
Senior java / старший java
Junior java / младший java

3️⃣ Исключите неподходящие варианты c помощью NOT.
Это сокращает количество вариантов в 3-4 раза.
Пример для бэкэнд java разработчика:
java NOT QA NOT Android NOT Junior NOT Архитектор NOT DevOps
2
Есть ли у Вас в проекте свои функциональные интерфейсы?
Anonymous Poll
36%
Да
56%
Нет, пользуемся стандартными
7%
Нет, у нас java 6/7
Функциональные интерфейсы, часть 1.

Java 8 добавила много возможностей писать короткий и выразительный код. Одна из них — функциональный интерфейс (ФИ).

Интерфейс считается функциональным, если у него только один абстрактный метод. Это единственное требование, даже аннотация FunctionalInterface необязательна. Например, у интерфейса Comparable всего один метод, по определению он функциональный, но аннотации у него нет. Во всём остальном ФИ - это обычный интерфейс. У метода можно объявлять исключения и использовать любые типы параметров:
@FunctionalInterface
public interface Archiver{
void archive(String file)
throws FileNotFoundEx;
}

Экземплярами ФИ могут быть:
🔸Лямбда-выражение
🔸Ссылка на метод
🔸Ссылка на конструктор

Для большинства задач подойдут стандартные интерфейсы из пакета java.util.function. Всего их 43, но можно выделить 5 групп:
1️⃣ Consumer (потребитель)
Метод принимает один параметр и ничего не возвращает.
Consumer<String> c = 
System.out::println;

2️⃣ Supplier (поставщик)
Ничего не принимает, но возвращает значение.
Supplier<List> s = 
ArrayList::new;

3️⃣ Function
Принимает один параметр, возвращает одно значение:
Function<String,Long> map = 
s -> Long.valueOf(s);

4️⃣ UnaryOperator
Метод принимает один параметр и возвращает значение того же типа:
UnaryOperator<Integer> inc = 
i -> i+1;

5️⃣ Predicate
Принимает аргумент и возвращает boolean значение.
Predicate<String> filter = 
s -> !s.isEmpty()
&& !s.contains("q");

У этих ФИ есть удобные вариации:
🔹BiConsumer, BiFunction, BiPredicate, BinaryOperator принимают два аргумента:
BiConsumer<Integer,Integer> b = 
(x,y) -> println(x+y);

🔹LongFunction, LongSupplier , ToLongFunction и т.д позволяют не прописывать типы аргументов или выходных значений:
LongFunction<String> to = 
Long::toString;

Ответ на вопрос перед постом:
Интерфейс становится функциональным если в нём есть один абстрактный метод. Все остальные свойства у него такие же, как у обычного интерфейса. Аннотация FunctionalInterface не обязательна, но желательна для понимания кода.
1
Может ли класс реализовать функциональный интерфейс?
Anonymous Quiz
88%
Да
12%
Нет
Сколько методов может быть в функциональном интерфейсе?
Anonymous Quiz
67%
Один
33%
Сколько угодно
Функциональные интерфейсы, часть 2. Best practices.

В части 1 был обзор функциональных интерфейсов (ФИ), а в этом посте — 4 рекомендации по их использованию.

1️⃣ Не реализуйте ФИ через анонимные классы.
Используйте лямбда-выражения и ссылки на методы. Это короче и понятнее.
Runnable r=() -> …
Runnable r=new Runnable{
@Override
public void run(){…}
}

2️⃣ Определите сценарии использования ФИ.
ФИ — это интерфейс, но с одним абстрактным методом. В него можно добавить сколько угодно методов с реализацией — дефолтных, приватных и статических. Один ФИ может расширять другой ФИ. Класс может реализовать ФИ через implements.

Чтобы код был простым и понятным, нужно разграничивать его компоненты:
🔸Цель обычного интерфейса - обозначить набор методов для будущих классов.
🔸Цель ФИ — передать набор инструкций через лямбды и ссылки на методы.

Один метод в ФИ - оптимальный вариант. Но есть случаи, когда дополнительные методы уместны, так как тесно связаны с самим интерфейсом.
Пример: интерфейс Predicate. Несмотря на то, что он функциональный, в нём есть 4 метода с заданной реализацией: and, or, isEqual и negate.

3️⃣ Используйте аннотацию FunctionalInterface.
Она не обязательна, но облегчает чтение кода.

4️⃣ Если используете ФИ как аргумент, отражайте тип ФИ в названии метода.

Пример: в классе 2 метода с одним именем и разными типами аргументов:
void adapt(Callable)
void adapt(Supplier)

Этот код компилируется, но у него 2 недостатка:
▪️Чтобы понять разницу между методами нужно читать код или документацию.
▪️В качестве аргумента нельзя передать лямбда-выражение, т.к оно подходит под оба типа:
 adapt(str->print(str))

Чтобы избежать этих проблем, используйте разные имена для методов. Хороший пример — класс CompletableFuture для многоэтапных вычислений. В его методах легко ориентироваться благодаря удачным названиям:
▫️thenAccept(Consumer)
▫️thenApply(Function)
▫️thenRun(Runnable)
1
Что из этого напрямую влияет на показатель "сложность кода" (Complexity)?
Anonymous Poll
15%
Количество строк в классе
59%
Глубина наследования
12%
Число методов
80%
Количество ветвлений в методе