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

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

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Интерфейсы. Часть 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%
Количество ветвлений в методе
Статический анализ кода, часть 1. Основные метрики

Главная цель принципов разработки и паттернов проектирования — снизить стоимость разработки. Хорошим считается код, который легко:
🔸читать
🔸менять
🔸тестировать
🔸переиспользовать

Простота - субъективное понятие. Разработчику, который в проекте со дня основания, легко ориентироваться в коде и исправлять ошибки. Джуниор, который недавно пришёл и получил первое задание, вряд ли с ним согласится. К счастью, есть объективные показатели качества кода, которые можно отслеживать и анализировать. Их получают с помощью статического анализа на основе исходного и байт-кода. Базовых метрик больше 50, и обычно они группируются в 4 категории:

1️⃣ Связность (Cohesion)
Показывает, насколько сильно внутренние элементы взаимодействуют друг с другом, насколько сфокусирован класс.
В классе с низкой связностью много методов, которые мало взаимодействуют между собой:
class Программист {
void написатьКод()
void помытьПосуду()
void погладитьКота()
}
Идеальная сущность с точки зрения Cohesion - лямбда-выражение. Их легко читать, тестировать и переиспользовать.

2️⃣ Связанность (Coupling)
Показывает, как одна сущность взаимодействует с другими. Класс А связан с классом Б, если в классе А
▫️Поля класса Б
▫️Вызов методов класса Б
▫️Локальные переменные класса Б
▫️Если класс А - подкласс Б

В большинстве проектов есть модуль base или common - часто он является примером сильной связанности. Менять такой модуль - всегда большой риск.

3️⃣ Сложность (Complexity)
На этот показатель влияет:
▪️Сколько сущностей взаимодействуют
▪️Количество операций в методе
▪️Глубина наследования
▪️Количество ветвлений и возможных состояний

Показатель сложности часто идёт рука об руку с размером, но не всегда. Сложный метод может быть небольшим по размеру, но скрывать внутри себя десятки вариантов развития событий:
public void check(long id) {
dao.get(id).findProcessor()
.getAccount().checkBalance();
}

4️⃣ Размер (Size)
Большие сущности сложно поддерживать. В этой категории много метрик, например, количество:
🔹Полей
🔹Методов
🔹Статических методов
🔹Подклассов
🔹Связанных библиотек

А теперь ответы на вопросы перед постом:
Если класс А часто использует класс Б, то это признак высокой связанности. Связанность (coupling) характеризует внешние отношения между сущностями. Связность (cohesion) - про внутреннюю структуру самой сущности.
На сложность влияет количество ветвлений и глубина наследования. Остальные характеристики относятся к категории "размер".
👍3
Статический анализ кода, часть 2. Инструменты

В части 1 обсудили основные метрики: связность, связанность, сложность и размер. Также статический анализ включает проверку форматирования, поиск уязвимостей и возможных ошибок.

Когда и чем можно выполнять статистический анализ?
1️⃣ Во время написания кода.
С помощью встроенных правил IDE и дополнительных плагинов:
▫️Checkstyle проверяет форматирование, возможные ошибки, показывает сложные классы. Самый популярный плагин для Intellij IDEA.
▫️SonarLint и FindBugs ищут возможные ошибки, уязвимости, проверяют код на несколько тысяч ошибочных паттернов.
▫️CodeMR замеряет атрибуты качества кода — связность, сложность и т.д. Считает метрики, рисует картинки, показывает проблемные места с точки зрения дизайна.
▫️PMD ищет ошибочные паттерны в разных категориях — дизайн, кодстайл, производительность. Не знаю, почему этот плагин попадает во все списки анализаторов, у него достаточно скудный список правил по сравнению с аналогами.

2️⃣ Во время сборки.
Анализ редко делают в процессе сборки, но такая опция доступна — с помощью maven или gradle плагинов: Checkstyle Plugin, PMD Plugin.

3️⃣ На этапах CI/CD.
Можно запускать периодически или после определённых событий: создание пул-реквеста, слияние ветки. Результаты могут иметь рекомендательный характер, а могут не пропускать дальше по CI.

Конкретных инструментов много, как платных, так и бесплатных. Самый популярный - SonarQube. Метрики качества не считает, но проверяет на гигантское количество ошибочных паттернов и интегрируется с плагином в IDE.
JArchitect менее распространён и стоит дороже. Но и функций больше — метрики качества и красивые графики.

Статический анализ проводят в большинстве проектов. Обычно это проверка форматирования и поиск частых ошибок. Конкретные метрики считаются редко. И зря, ведь по ним можно объективно оценить:
🔸Текущее состояние кода
🔸Развитие системы от релиза к релизу
🔸Необходимость рефакторинга
🔸Проблемные места системы
1