Новые методы интерфейса List
Сегодня разберём вопрос с собеседований по новым методам интерфейса List. Подробно рассмотрим каждый вариант ответа, потому что методы не экзотичные и пригодятся в ежедневной работе.
В посте много кода, поэтому для удобства чтения он тут: https://teletype.in/@java_fillthegaps/QN39vs6K0
В конце небольшое лирическое отступление на тему трендов и дизайна методов.
#собеседование
Сегодня разберём вопрос с собеседований по новым методам интерфейса List. Подробно рассмотрим каждый вариант ответа, потому что методы не экзотичные и пригодятся в ежедневной работе.
В посте много кода, поэтому для удобства чтения он тут: https://teletype.in/@java_fillthegaps/QN39vs6K0
В конце небольшое лирическое отступление на тему трендов и дизайна методов.
#собеседование
❤1
Вакансии HeadHunter: Москва
Четыре месяца назад был пост про вакансии java разработчиков в Петербурге. Следим за ситуацией на рынке, и сегодня обзор вакансий для города Москва.
Вакансий в 2 раза больше — 719 против 309 в Петербурге. Интересно, что в Москве разработчиков без опыта ждут в три раза охотнее — таких вакансий 9% против 3% в Спб. BigData и noSQL упоминаются реже, и не попали ни в один топ-10.
Тройка лидеров как в Петербурге — Spring, SQL, git.
Технологии в списках находятся либо в требованиях, либо в описании проекта. Статистика собрана для разработчиков с разным уровнем опыта. Где-то не совсем технологии, например, библиотека JUnit или протокол SOAP.
Разработчики без опыта
Вакансий: 66 (9%)
🔸Spring (MVC, Security, Boot, Data)
🔸SQL (Oracle, Hibernate, Postgre)
🔸git
🔸Maven
🔸Jenkins
🔸OpenShift
🔸Docker
🔸Kafka
🔸JUnit
🔸JavaScript
Опыт от года до 3х лет
Вакансий: 303 (42%)
▪️Spring (Boot, Data, Cloud)
▪️SQL (Hibernate, Postgre, Oracle)
▪️git
▪️Maven
▪️Kafka
▪️Docker
▪️SOAP
▪️JUnit
▪️Jenkins
Опыт от 3х до 6 лет
Вакансий: 325 (45%)
🔹Spring (Boot, Data, Cloud, Security)
🔹SQL (Postgre, Hibernate)
🔹git
🔹Docker
🔹Maven
🔹Kafka
🔹JUnit
🔹Kubernetes
🔹Gradle
🔹Kotlin
🔹Jenkins
Опыт более 6 лет
Вакансий: 25 (4%)
▫️Spring (Boot. Data, Cloud)
▫️SQL (Postgre, Hibernate, mySql)
▫️git
▫️Docker
▫️Kubernetes
▫️Kafka
▫️Cloud провайдеры (AWS, GCP)
▫️Scala
▫️Jenkins
#статистика
Четыре месяца назад был пост про вакансии java разработчиков в Петербурге. Следим за ситуацией на рынке, и сегодня обзор вакансий для города Москва.
Вакансий в 2 раза больше — 719 против 309 в Петербурге. Интересно, что в Москве разработчиков без опыта ждут в три раза охотнее — таких вакансий 9% против 3% в Спб. BigData и noSQL упоминаются реже, и не попали ни в один топ-10.
Тройка лидеров как в Петербурге — Spring, SQL, git.
Технологии в списках находятся либо в требованиях, либо в описании проекта. Статистика собрана для разработчиков с разным уровнем опыта. Где-то не совсем технологии, например, библиотека JUnit или протокол SOAP.
Разработчики без опыта
Вакансий: 66 (9%)
🔸Spring (MVC, Security, Boot, Data)
🔸SQL (Oracle, Hibernate, Postgre)
🔸git
🔸Maven
🔸Jenkins
🔸OpenShift
🔸Docker
🔸Kafka
🔸JUnit
🔸JavaScript
Опыт от года до 3х лет
Вакансий: 303 (42%)
▪️Spring (Boot, Data, Cloud)
▪️SQL (Hibernate, Postgre, Oracle)
▪️git
▪️Maven
▪️Kafka
▪️Docker
▪️SOAP
▪️JUnit
▪️Jenkins
Опыт от 3х до 6 лет
Вакансий: 325 (45%)
🔹Spring (Boot, Data, Cloud, Security)
🔹SQL (Postgre, Hibernate)
🔹git
🔹Docker
🔹Maven
🔹Kafka
🔹JUnit
🔹Kubernetes
🔹Gradle
🔹Kotlin
🔹Jenkins
Опыт более 6 лет
Вакансий: 25 (4%)
▫️Spring (Boot. Data, Cloud)
▫️SQL (Postgre, Hibernate, mySql)
▫️git
▫️Docker
▫️Kubernetes
▫️Kafka
▫️Cloud провайдеры (AWS, GCP)
▫️Scala
▫️Jenkins
#статистика
👍4
Ребята, приходите на онлайн java конференцию jLove 4-5 декабря!
Участие БЕСПЛАТНОЕ, нужно только зарегистрироваться
Будут стримы для всех - от джуниоров до экспертов.
Много звёздных спикеров:
⭐️Trisha Gee - Developer Advocate в JetBrains
⭐️Josh Long - тоже Developer Advocate, только в Spring
⭐️Grace Jansen - развивает облачную архитектуру в IBM
⭐️Emily Jiang, Adam Bien - эксперты в Java EE
И другие ребята из Red Hat, Microsoft, jFrog, Oracle, Confluent и VMware.
Общение со спикерами в чате, группы по интересам, афтепати, призы и подарки.
Регистрация и остальная информация: jlove.konfy.care
Участие БЕСПЛАТНОЕ, нужно только зарегистрироваться
Будут стримы для всех - от джуниоров до экспертов.
Много звёздных спикеров:
⭐️Trisha Gee - Developer Advocate в JetBrains
⭐️Josh Long - тоже Developer Advocate, только в Spring
⭐️Grace Jansen - развивает облачную архитектуру в IBM
⭐️Emily Jiang, Adam Bien - эксперты в Java EE
И другие ребята из Red Hat, Microsoft, jFrog, Oracle, Confluent и VMware.
Общение со спикерами в чате, группы по интересам, афтепати, призы и подарки.
Регистрация и остальная информация: jlove.konfy.care
👍2
Что можно сказать про инкапсуляцию этого класса? (2/2)
Anonymous Poll
28%
Всё ок: все поля объявлены private, доступ к ним только через методы
21%
Не очень: не для всех полей есть set метод
24%
Так себе: внутреннее состояние можно поменять
28%
Плохо: можно создать несколько аккаунтов с одним ID
Что такое инкапсуляция?
Разберём популярный вопрос на собеседовании джуниор разработчика.
В 2013 году я отвечала, что инкапсуляция - это сокрытие деталей реализации. Обращаемся к объекту через методы и получаем ожидаемый результат, не погружаясь в лишние детали. Что там творится внутри метода - неважно.
Это верный ответ, но не полный.
Выделить часть кода в отдельный метод - это ещё не инкапсуляция. Такое можно провернуть в любом языке, это не делает его объекто-ориентированным.
❓Что же такое инкапсуляция?
У каждого объекта есть состояние - внутренние поля.
Иногда с ограничениями:
▪️Возраст - целое число меньше 120
▪️Имя - хотя бы одна буква
Иногда поля связаны между собой:
▪️Статус пользователя зависит от количества заказов
▪️Коэффициент ОСАГО зависит от города
Инкапсуляция - это когда состояние объекта нельзя поменять напрямую, только через методы класса. При этом важно защитить внутреннее состояние от нежелательных изменений. Это ответ на первый вопрос перед постом.
На практике решение обычно простое: сделать поля private и добавить методы get и set. Но иногда этого недостаточно.
❓Что не так с инкапсуляцией в классе Account?
Метод getOrders отдаёт список заказов List<Order>. Подразумевается, что клиент добавит ещё один заказ или обработает список.
По факту возможностей гораздо больше:
❌Удалить элементы
❌Отредактировать текущие заказы
В сложных системах не поможет комментарий "этот список только для добавления". Надёжный способ избежать ошибок - это понятный и ограниченный API.
Правильный ответ на вопрос 2:
Инкапсуляция так себе: внутреннее состояние (список заказов) не защищено.
Возможность создать два аккаунта с одним ID - вопрос дизайна и сценариев работы. С точки зрения инкапсуляции всё ок - ID нельзя поменять.
❓Как исправить ситуацию и защитить список заказов?
1️⃣ Хранить заказы отдельно
2️⃣ Поменять класс:
▫️Добавить orders модификатор final
▫️Добавить метод addOrder
▫️Метод getOrders пусть возвращает неизменяемый список
(возможны другие варианты, зависит от контекста)
#core
Разберём популярный вопрос на собеседовании джуниор разработчика.
В 2013 году я отвечала, что инкапсуляция - это сокрытие деталей реализации. Обращаемся к объекту через методы и получаем ожидаемый результат, не погружаясь в лишние детали. Что там творится внутри метода - неважно.
Это верный ответ, но не полный.
Выделить часть кода в отдельный метод - это ещё не инкапсуляция. Такое можно провернуть в любом языке, это не делает его объекто-ориентированным.
❓Что же такое инкапсуляция?
У каждого объекта есть состояние - внутренние поля.
Иногда с ограничениями:
▪️Возраст - целое число меньше 120
▪️Имя - хотя бы одна буква
Иногда поля связаны между собой:
▪️Статус пользователя зависит от количества заказов
▪️Коэффициент ОСАГО зависит от города
Инкапсуляция - это когда состояние объекта нельзя поменять напрямую, только через методы класса. При этом важно защитить внутреннее состояние от нежелательных изменений. Это ответ на первый вопрос перед постом.
На практике решение обычно простое: сделать поля private и добавить методы get и set. Но иногда этого недостаточно.
❓Что не так с инкапсуляцией в классе Account?
Метод getOrders отдаёт список заказов List<Order>. Подразумевается, что клиент добавит ещё один заказ или обработает список.
По факту возможностей гораздо больше:
❌Удалить элементы
❌Отредактировать текущие заказы
В сложных системах не поможет комментарий "этот список только для добавления". Надёжный способ избежать ошибок - это понятный и ограниченный API.
Правильный ответ на вопрос 2:
Инкапсуляция так себе: внутреннее состояние (список заказов) не защищено.
Возможность создать два аккаунта с одним ID - вопрос дизайна и сценариев работы. С точки зрения инкапсуляции всё ок - ID нельзя поменять.
❓Как исправить ситуацию и защитить список заказов?
1️⃣ Хранить заказы отдельно
2️⃣ Поменять класс:
▫️Добавить orders модификатор final
▫️Добавить метод addOrder
▫️Метод getOrders пусть возвращает неизменяемый список
(возможны другие варианты, зависит от контекста)
#core
❤1
Default методы: неудачный кейс
В чём ценность опытного разработчика? Способность видеть возможные проблемы. Интуиция, чуйка, "что-то мне здесь не нравится, давайте разберёмся".
Чтобы развить этот навык, нужно совершить много ошибок самому и изучать чужие ошибки. Прямо или косвенно, это всё идёт на пользу, и сегодня хочу поделиться одним неудачным случаем.
Default методы появились в java 8, чтобы упростить добавление методов в интерфейс. Подробный обзор можно прочитать в этом посте.
✅ Легко добавить новый метод
✅ Нет ошибок компиляции
✅ Методы при желании переопределяются
C java 8 в интерфейсе Collection появились методы по умолчанию spliterator(), stream(), parallelStream(), removeIf(…).
Какие могут быть проблемы?
❌ Реализация по умолчанию не подходит
❌ Разработчики не узнают, что добавился новый метод, который нужно переопределить. Ошибок компиляции нет, предупреждений тоже
❌ Нет тестов нового метода и интеграционных тестов
От этих проблем пострадали пользователи SynchronizedCollection из библиотеки Apache Commons.
Что произошло?
В SynchronizedCollection каждый метод синхронизирован по объекту lock:
Что делает дефолтный метод removeIf? Берёт итератор, проверяет каждый элемент на соответствие условию и удаляет, если нужно.
Переложим на методы SynchronizedCollection. Синхронизация по lock берётся, отпускается, берётся, отпускается, и так несколько раз. При большой нагрузке управление перехватит другой поток, и произойдёт коллизия. Дефолтный метод не выполнит гарантий, заданных классом.
Ошибку легко исправить - переопределить метод removeIf:
5 лет! Может новым методом никто не пользовался. Может поток данных через коллекцию был небольшим. Может никаких последствий не было. А может были, неизвестно.
Даже такая безобидная фича как "методы по умолчанию" привела к ошибке. Мы можем вынести из неё пару best practices:
▫️Если интерфейс используется только внутри системы, достаточно написать тесты для всех реализаций.
▫️Для общедоступных библиотек по возможности избегать методов по умолчанию.
#core
В чём ценность опытного разработчика? Способность видеть возможные проблемы. Интуиция, чуйка, "что-то мне здесь не нравится, давайте разберёмся".
Чтобы развить этот навык, нужно совершить много ошибок самому и изучать чужие ошибки. Прямо или косвенно, это всё идёт на пользу, и сегодня хочу поделиться одним неудачным случаем.
Default методы появились в java 8, чтобы упростить добавление методов в интерфейс. Подробный обзор можно прочитать в этом посте.
✅ Легко добавить новый метод
✅ Нет ошибок компиляции
✅ Методы при желании переопределяются
C java 8 в интерфейсе Collection появились методы по умолчанию spliterator(), stream(), parallelStream(), removeIf(…).
Какие могут быть проблемы?
❌ Реализация по умолчанию не подходит
❌ Разработчики не узнают, что добавился новый метод, который нужно переопределить. Ошибок компиляции нет, предупреждений тоже
❌ Нет тестов нового метода и интеграционных тестов
От этих проблем пострадали пользователи SynchronizedCollection из библиотеки Apache Commons.
Что произошло?
В SynchronizedCollection каждый метод синхронизирован по объекту lock:
synchronized (lock) {
return coll.remove(object);
}
Изменения последовательны, данные в безопасности и всегда актуальны. Что делает дефолтный метод removeIf? Берёт итератор, проверяет каждый элемент на соответствие условию и удаляет, если нужно.
Переложим на методы SynchronizedCollection. Синхронизация по lock берётся, отпускается, берётся, отпускается, и так несколько раз. При большой нагрузке управление перехватит другой поток, и произойдёт коллизия. Дефолтный метод не выполнит гарантий, заданных классом.
Ошибку легко исправить - переопределить метод removeIf:
synchronized (lock) {
return coll.removeIf(filter);
}
Проблема в том, что такие ошибки сложно обнаружить. Default метод появился в марте 2014, а класс обновили в июле 2019. 5 лет пользователи SynchronizedCollection пользовались ненадёжным методом.5 лет! Может новым методом никто не пользовался. Может поток данных через коллекцию был небольшим. Может никаких последствий не было. А может были, неизвестно.
Даже такая безобидная фича как "методы по умолчанию" привела к ошибке. Мы можем вынести из неё пару best practices:
▫️Если интерфейс используется только внутри системы, достаточно написать тесты для всех реализаций.
▫️Для общедоступных библиотек по возможности избегать методов по умолчанию.
#core
❤2
Пробовали когда-нибудь парное программирование?
Anonymous Poll
5%
Да, часто практикую
24%
Да, один-два раза
41%
Нет, но интересно попробовать
30%
Нет, только традиционный подход
👍2
IDEA: Code with me
Парное программирование - agile практика, когда два человека сидят за одним компьютером и работают над задачей. Один пишет код, другой - наблюдает.
Если партнёры хорошо сработаются, то код получится проще, а ошибок будет меньше.
Intellij IDEA недавно выпустила классный плагин Code with me, и теперь парным программированием можно заняться и на удалёнке.
Найти его легко: File → Settings → Plugins → Marketplace → Code with me
Новая плашка появится рядом с иконками Run/Debug. Можно отправить ссылку коллеге и вместе работать над задачей👯
#idea
Парное программирование - agile практика, когда два человека сидят за одним компьютером и работают над задачей. Один пишет код, другой - наблюдает.
Если партнёры хорошо сработаются, то код получится проще, а ошибок будет меньше.
Intellij IDEA недавно выпустила классный плагин Code with me, и теперь парным программированием можно заняться и на удалёнке.
Найти его легко: File → Settings → Plugins → Marketplace → Code with me
Новая плашка появится рядом с иконками Run/Debug. Можно отправить ссылку коллеге и вместе работать над задачей👯
#idea
По статистике 64% приложений сидят на java 8, а самая популярная фича java 8 - это Stream API.
Самая запутанная часть Stream API - коллекторы, о них я написала лонгрид в 3 частях.
Часть 1: обзор и простые коллекторы
Часть 2: сложные коллекторы
Часть 3: особенности дизайна
Рассмотрим почти все, что есть в JDK. Без внимания останутся collectingAndThen и teeing(Java 12). Не нашла ни одного кейса, где они полезны🤷
Самая запутанная часть Stream API - коллекторы, о них я написала лонгрид в 3 частях.
Часть 1: обзор и простые коллекторы
Часть 2: сложные коллекторы
Часть 3: особенности дизайна
Рассмотрим почти все, что есть в JDK. Без внимания останутся collectingAndThen и teeing(Java 12). Не нашла ни одного кейса, где они полезны🤷
Коллекторы Stream API, часть 1: простые методы
В первой части повторим основы - из чего состоит стрим и что такое коллектор. Каждый код со Stream API состоит из 3х частей:
1️⃣ Получение стрима
2️⃣ Преобразования
3️⃣ Терминальная операция
1️⃣
Коллекторы - статические методы класса Collectors, которые возвращают аргумент для метода collect. Я буду опускать основной класс и вместо Collectors.counting() будут писать counting(). Чтобы было короче.
Чаще всего элементы стрима собирают в обычную коллекцию:
▪️toCollection, toList, toSet
▪️toUnmodifiableSet, toUnmodifiableList
▫️counting
▫️averagingToInt / Long / Double
▫️joining
▫️maxBy, minBy
▫️reducing
▫️summingInt / Long / Double
▫️summarizingInt / Long / Double
Интересны здесь только два метода:
🔸 joining
Соединяет элементы в одну строку:
Возвращает объект IntSummaryStatistics, который содержит минимум, максимум, среднее, количество элементов и их сумму.
Остальные методы сами по себе бесполезны, так как есть простые аналоги:
Они нужны в коллекторах groupingBy и partitioningBy, про них подробно поговорим завтра.
#core
В первой части повторим основы - из чего состоит стрим и что такое коллектор. Каждый код со Stream API состоит из 3х частей:
1️⃣ Получение стрима
2️⃣ Преобразования
3️⃣ Терминальная операция
1️⃣
list.stream()
2️⃣ .filter(e -> e != 3)
3️⃣ .count();
Терминальная операция collect собирает элементы стрима в другую структуру данных. Все подробности передаются через аргумент:collect(Collector collector) Коллекторы - статические методы класса Collectors, которые возвращают аргумент для метода collect. Я буду опускать основной класс и вместо Collectors.counting() будут писать counting(). Чтобы было короче.
Чаще всего элементы стрима собирают в обычную коллекцию:
▪️toCollection, toList, toSet
▪️toUnmodifiableSet, toUnmodifiableList
Set res=students.stream()Ещё одна группа - коллекторы, которые возвращают одно значение:
.filter(…).collect(toSet())
▫️counting
▫️averagingToInt / Long / Double
▫️joining
▫️maxBy, minBy
▫️reducing
▫️summingInt / Long / Double
▫️summarizingInt / Long / Double
Интересны здесь только два метода:
🔸 joining
Соединяет элементы в одну строку:
chars.stream().collect(joining("-"));
// ['a','b','c'] → a-b-c
🔸summarizingIntВозвращает объект IntSummaryStatistics, который содержит минимум, максимум, среднее, количество элементов и их сумму.
Остальные методы сами по себе бесполезны, так как есть простые аналоги:
list.stream().collect(counting())Коллекторы mapping, flatMapping и filtering применяют функцию к элементам перед отправкой в другой коллектор.
// аналог
list.stream().count()
Set<Long> ids = …Использовать их напрямую тоже смысла нет. Проще применить к элементам map, flatmap или filter, а потом собрать результаты:
collect(mapping(Student::id, toSet())
map(Student::id).collect(toSet())❓Зачем нужны эти методы?
Они нужны в коллекторах groupingBy и partitioningBy, про них подробно поговорим завтра.
#core
👍5❤1
Вы поручили джуниору задачу, в которой нужно написать пару строк на Stream API. Джуниор неопытный, но старательный, и принёс 5 вариантов решения. Все, кроме одного, приводят к одному результату. Какой код ошибочный?
Коллекторы Stream API, часть 2: сложные коллекторы
В этой части разберёмся в коллекторах toMap, groupingBy и partitioningBy. Уверена, года через 3 их будут спрашивать на занудных собеседованиях.
Будем тренироваться на классе Student из вопроса перед постом. У него есть id, имя, город и список оценок.
🔸toMap, toConcurrentMap
Цель понятна: уложить элементы стрима в map. Указываем функцию для ключа и для значения:
Если ключи повторяются, то вылетит IllegalStateException. Чтобы этого избежать, укажите третьим параметром, как объединять значения:
Похоже на группировку по городу, но это не она. В результате получается одно значение, а не группа.
🔸groupingBy
Настоящую группировку делает коллектор groupingBy:
▫️Города и количество студентов:
▫️ID студента и средняя оценка за экзамены:
...
Для этой задачи группировка не подходит. У одного студента только одна средняя оценка, количество итоговых элементов = количеству исходных. Группировать нечего, поэтому используем toMap:
🔸partitioningBy
Метод делит элементы на две группы по заданному условию. Результат - map с двумя ключами - true и false.
▫️Делим студентов на москвичей и жителей других городов:
Вы поручили джуниору посчитать среди студентов тех, кто не сдал ни одного экзамена. Вариант 3 предлагает применить к набору студентов метод Integer::longValue, что невозможно. Остальные варианты переводят каждого студента в число 1 и суммируют все значения. Ошибочный ответ - 3.
#core
В этой части разберёмся в коллекторах toMap, groupingBy и partitioningBy. Уверена, года через 3 их будут спрашивать на занудных собеседованиях.
Будем тренироваться на классе Student из вопроса перед постом. У него есть id, имя, город и список оценок.
🔸toMap, toConcurrentMap
Цель понятна: уложить элементы стрима в map. Указываем функцию для ключа и для значения:
collect(toMap(Student::id, Student::name)) Если ключи повторяются, то вылетит IllegalStateException. Чтобы этого избежать, укажите третьим параметром, как объединять значения:
collect(toMap(Student::city,["Москва":"Антон Аня Эдуард"]
Student::name,
(a,b) -> a+" "+b))
Похоже на группировку по городу, но это не она. В результате получается одно значение, а не группа.
🔸groupingBy
Настоящую группировку делает коллектор groupingBy:
Map<String, List<Student>>=…По умолчанию сгруппированные элементы объединяются в список. Чтобы получить что-нибудь другое, передайте в метод другой коллектор:
collect(groupingBy (Student::city))
groupingBy(Student::city, toSet())Здесь нам наконец пригодятся коллекторы из предыдущего поста, которые подставим во второй аргумент.
▫️Города и количество студентов:
groupingBy(Student::city, counting())▫️Города и имена студентов:
groupingBy(Student::city,Передаём в groupingBy функцию для ключей и коллектор mapping для значений. Он же принимает другой коллектор toSet, чтобы было понятно, куда складывать результат.
mapping(Student::name, toSet()))
▫️ID студента и средняя оценка за экзамены:
...
Для этой задачи группировка не подходит. У одного студента только одна средняя оценка, количество итоговых элементов = количеству исходных. Группировать нечего, поэтому используем toMap:
toMap(Student::id,Вместо расчёт() - километр кода. Подсчёт среднего недоступен в интерфейсе Stream, а удобных методов перевода List<Integer> в IntStream нет.
s -> s.marks().stream().расчёт())
🔸partitioningBy
Метод делит элементы на две группы по заданному условию. Результат - map с двумя ключами - true и false.
▫️Делим студентов на москвичей и жителей других городов:
Map<Boolean,Set<Student>>…Вместо toSet можно подставить другой коллектор. Посчитаем количество студентов:
partitioningBy(s-> s.city().equals("Moscow"), toSet())
partitioningBy(s-> s.city().equals("Moscow"), counting())
Не очень понятно, зачем нужен partitioningBy. groupingBy даёт такой же результат, но на 2 символа короче:groupingBy(s-> s.city().equals("Moscow"), counting())
❓Ответ на вопрос перед постомВы поручили джуниору посчитать среди студентов тех, кто не сдал ни одного экзамена. Вариант 3 предлагает применить к набору студентов метод Integer::longValue, что невозможно. Остальные варианты переводят каждого студента в число 1 и суммируют все значения. Ошибочный ответ - 3.
#core
👍3
Коллекторы Stream API, часть 3: дизайн
Вчера мы разбирали groupingBy и partitioningBy. У многих возникла мысль, что лучше держаться от группировок подальше. Коллекторы выглядят неважно относительно других методов Stream API:
▪️Многословные: collect(Collectors.toSet())
▪️Вложенные коллекторы
▪️Дублированные методы map, flatMap, max, min - 18 штук
Итог: плохая читаемость и желание написать всё через цикл for.
Почему так получилось? Разберёмся в этом посте.
Выделим три вопроса:
1️⃣ Почему вместо toSet() такой сложный collect(Collectors.toSet())?
2️⃣ Почему группировка - терминальная операция?
3️⃣ Зачем в классе 18 неполноценных методов и вложенные коллекторы?
Разберём по порядку.
1️⃣ Метод collect и класс Collectors.
Цель Stream API - удобная работа с данными. За интерфейсом Stream может быть любой источник данных: список, строка, коллекция или файл. Сторонние библиотеки могут реализовать свой источник данных и работать с ним стандартными средствами Stream API.
В обратную сторону это тоже работает. Пользователь может преобразовать стрим в свою структуру данных, для этого в интерфейсе Stream метод collect(Collector).
А ещё источник данных и конечная структура данных - разные сущности, поэтому в интерфейсе Stream нет методов toSet(), toList(). Single Responsibility.
2️⃣ Почему группировка - терминальная операция?
С точки зрения пользователя группировка - просто преобразование. После группировки элементы можно фильтровать, менять и так далее.
Дизайн Stream API для этого не подходит. Источник данных делится на части, элементы обрабатываются независимо, иногда в разных потоках.
Терминальная операция - единственное место, где потоки объединяются и вычисляется общий результат. Посчитать количество, найти элемент, объединить в коллекцию - это конечные точки в обработке.
Группировка работает со всеми элементами, и в терминах Stream API это терминальная операция, дальше работать со стримом нельзя.
3️⃣ Вложенные коллекторы и 18 методов для groupingBy
Кстати, разработчики JDK понимают, что группировка - это преобразование данных, а вовсе не финальная точка.
Что может понадобиться программисту?
🔸Указать итоговый тип данных.
🔸Преобразовать итоговые данные. Это сложно сделать напрямую, потому что универсального интерфейса для filter, map и average нет.
Для решения этих задач groupingBy получил аргумент-коллектор, а Collectors пополнился 18 методами-обёртками над map, flatMap, filter и average .
На мой взгляд получилось сложновато, но это спорный вопрос. А какие вам нравятся библиотеки?
💅 - Удобные методы для целевых кейсов, но с ограничениями
💪 - Максимум возможностей и кастомизации
#core
Вчера мы разбирали groupingBy и partitioningBy. У многих возникла мысль, что лучше держаться от группировок подальше. Коллекторы выглядят неважно относительно других методов Stream API:
▪️Многословные: collect(Collectors.toSet())
▪️Вложенные коллекторы
▪️Дублированные методы map, flatMap, max, min - 18 штук
Итог: плохая читаемость и желание написать всё через цикл for.
Почему так получилось? Разберёмся в этом посте.
Выделим три вопроса:
1️⃣ Почему вместо toSet() такой сложный collect(Collectors.toSet())?
2️⃣ Почему группировка - терминальная операция?
3️⃣ Зачем в классе 18 неполноценных методов и вложенные коллекторы?
Разберём по порядку.
1️⃣ Метод collect и класс Collectors.
Цель Stream API - удобная работа с данными. За интерфейсом Stream может быть любой источник данных: список, строка, коллекция или файл. Сторонние библиотеки могут реализовать свой источник данных и работать с ним стандартными средствами Stream API.
В обратную сторону это тоже работает. Пользователь может преобразовать стрим в свою структуру данных, для этого в интерфейсе Stream метод collect(Collector).
А ещё источник данных и конечная структура данных - разные сущности, поэтому в интерфейсе Stream нет методов toSet(), toList(). Single Responsibility.
2️⃣ Почему группировка - терминальная операция?
С точки зрения пользователя группировка - просто преобразование. После группировки элементы можно фильтровать, менять и так далее.
Дизайн Stream API для этого не подходит. Источник данных делится на части, элементы обрабатываются независимо, иногда в разных потоках.
Терминальная операция - единственное место, где потоки объединяются и вычисляется общий результат. Посчитать количество, найти элемент, объединить в коллекцию - это конечные точки в обработке.
Группировка работает со всеми элементами, и в терминах Stream API это терминальная операция, дальше работать со стримом нельзя.
3️⃣ Вложенные коллекторы и 18 методов для groupingBy
Кстати, разработчики JDK понимают, что группировка - это преобразование данных, а вовсе не финальная точка.
Что может понадобиться программисту?
🔸Указать итоговый тип данных.
🔸Преобразовать итоговые данные. Это сложно сделать напрямую, потому что универсального интерфейса для filter, map и average нет.
Для решения этих задач groupingBy получил аргумент-коллектор, а Collectors пополнился 18 методами-обёртками над map, flatMap, filter и average .
На мой взгляд получилось сложновато, но это спорный вопрос. А какие вам нравятся библиотеки?
💅 - Удобные методы для целевых кейсов, но с ограничениями
💪 - Максимум возможностей и кастомизации
#core
👍2❤1
Сегодня начнётся онлайн конференция JLove❤️
Бесплатная!
Расписание уже готово, не забудьте подставить свой часовой пояс.
С удовольствием рассказываю о конфе второй раз: классные эксперты и получасовые доклады на любой вкус. Плюс общение со спикерами в чате, группы по интересам, афтепати, призы и подарки.
Зарегистрироваться: jlove.konfy.care
Бесплатная!
Расписание уже готово, не забудьте подставить свой часовой пояс.
С удовольствием рассказываю о конфе второй раз: классные эксперты и получасовые доклады на любой вкус. Плюс общение со спикерами в чате, группы по интересам, афтепати, призы и подарки.
Зарегистрироваться: jlove.konfy.care
Паттерн проектирования, который в соответствии с принципом единственной обязанности передает другому объекту ответственность построения требуемых ему зависимостей внешнему, специально предназначенному для этого общему механизму
Anonymous Poll
40%
Dependency injection
9%
Dependency invertion
36%
Inversion of Control
16%
Factory Method
👍2
DI vs DI vs IoC
Знаете, почему сложно внедрять всякие принципы и лучшие практики? Они рождаются из конкретных ситуаций и решают конкретные проблемы. Чтобы передать эти ценнейшие знания, ситуацию приходится абстрагировать и в итоге получается набор терминов. Что с ними делать - непонятно, слишком абстрактно.
Сегодня разберём разницу между Dependency injection, Dependency invertion и Inversion of Control. Понимание пригодится на собеседованиях, при чтении статей по дизайну и архитектуре. Плюс поймёте, как хорошо вы программируете и какие проблемы решаете, даже не задумываясь.
Будем разбираться на простом примере.
Точка А: Сервис Service записывает логи в файл с помощью класса FileLogger:
1️⃣ Dependency injection
это когда компоненты создаются не внутри класса, а передаются в конструкторах или сеттерах. Перенесём инициализацию логгера в конструктор:
2️⃣ Dependency invertion
Буква D в аббревиатуре SOLID, формулировка состоит из двух частей:
▫️Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
▫️Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
Суть: пусть сервис работает не с конкретным логгером, а с интерфейсом
✅ Реализацию легко заменить
✅ Оба класса проще тестировать
Почему используется слово "абстракция"? Группу методов можно выделить в интерфейс, в абстрактный класс и даже в обычный класс. Но интерфейс самый подходящий вариант.
3️⃣ IoC - Inversion of Control
В маленьких программах жизнь начинается в методе main(). Программист создаёт объекты, вызывает методы, все шаги явно прописаны.
Inversion of Control - это когда ход выполнения программы задаёт фреймворк. Spring смотрит на классы и аннотации, а затем создаёт объекты, связывает их вместе и не даёт программе завершиться.
✅ Низкая связность - код легко менять, тестировать и переиспользовать
Spring создаёт обёртки классов и работает через Dependency Injection. Можно и по-другому: через паттерн фабричный метод, стратегия или сервис локатор.
⚔️Историческая справка.
Сервис локатор иногда встречается в легаси проектах. Это когда компоненты создаются в классе ServiceLocator, а другие классы получают к ним доступ через статические методы.
🔸Dependency injection - класс не создаёт компоненты напрямую, они передаются через конструктор или сеттер
🔸Dependency invertion - класс работает с другими компонентами через интерфейс
🔸Inversion of Control - ход программы задаёт фреймворк. Соединять компоненты может Dependency injection, фабричный метод, стратегия или сервис локатор.
❗️Ответ на вопрос перед постом:
Это словоблудие относится к Dependency injection
#теория
Знаете, почему сложно внедрять всякие принципы и лучшие практики? Они рождаются из конкретных ситуаций и решают конкретные проблемы. Чтобы передать эти ценнейшие знания, ситуацию приходится абстрагировать и в итоге получается набор терминов. Что с ними делать - непонятно, слишком абстрактно.
Сегодня разберём разницу между Dependency injection, Dependency invertion и Inversion of Control. Понимание пригодится на собеседованиях, при чтении статей по дизайну и архитектуре. Плюс поймёте, как хорошо вы программируете и какие проблемы решаете, даже не задумываясь.
Будем разбираться на простом примере.
Точка А: Сервис Service записывает логи в файл с помощью класса FileLogger:
class FileLogger {…}
class Service {
FileLogger logger=new FileLogger();
}
Сделаем код чуть лучше:1️⃣ Dependency injection
это когда компоненты создаются не внутри класса, а передаются в конструкторах или сеттерах. Перенесём инициализацию логгера в конструктор:
class Service {
FileLogger logger;
Service (FileLogger logger) {
this.logger= logger;
}
}
✅ Класс не занимается инициализацией логгера2️⃣ Dependency invertion
Буква D в аббревиатуре SOLID, формулировка состоит из двух частей:
▫️Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
▫️Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
Суть: пусть сервис работает не с конкретным логгером, а с интерфейсом
interface Logger {…}
class FileLogger implements Logger {…}
class Service {
Logger logger=new FileLogger();
}
✅ В интерфейсе доступно меньше методов, поэтому его проще использовать✅ Реализацию легко заменить
✅ Оба класса проще тестировать
Почему используется слово "абстракция"? Группу методов можно выделить в интерфейс, в абстрактный класс и даже в обычный класс. Но интерфейс самый подходящий вариант.
3️⃣ IoC - Inversion of Control
В маленьких программах жизнь начинается в методе main(). Программист создаёт объекты, вызывает методы, все шаги явно прописаны.
Inversion of Control - это когда ход выполнения программы задаёт фреймворк. Spring смотрит на классы и аннотации, а затем создаёт объекты, связывает их вместе и не даёт программе завершиться.
@Component class FileLogger {…}
@Component class Service {
@Autowired
FileLogger logger;
}
✅ Меньше скучного кода✅ Низкая связность - код легко менять, тестировать и переиспользовать
Spring создаёт обёртки классов и работает через Dependency Injection. Можно и по-другому: через паттерн фабричный метод, стратегия или сервис локатор.
⚔️Историческая справка.
Сервис локатор иногда встречается в легаси проектах. Это когда компоненты создаются в классе ServiceLocator, а другие классы получают к ним доступ через статические методы.
class ServiceLocator {
private static Logger logger=…
public static Logger getLogger() {
return logger;
}
}
public class Service {
private Logger logger = ServiceLocator.getLogger();
}
Резюме:🔸Dependency injection - класс не создаёт компоненты напрямую, они передаются через конструктор или сеттер
🔸Dependency invertion - класс работает с другими компонентами через интерфейс
🔸Inversion of Control - ход программы задаёт фреймворк. Соединять компоненты может Dependency injection, фабричный метод, стратегия или сервис локатор.
❗️Ответ на вопрос перед постом:
Это словоблудие относится к Dependency injection
#теория
👍4
IDEA: 4 метода для рефакторинга
IDEA - очень продвинутая IDE . Методов рефакторинга так много, что у них даже отдельная вкладка в меню. На каждом проекте точно пригодится:
1️⃣ Переименовать класс, метод, переменную или файл
Правой кнопкой по имени → Refactor → Rename
Имя изменится везде, где упоминается сущность.
2️⃣ Выделить константу
"Магические числа" в коде - плохая практика, лучше читаются именованные константы.
Правой кнопкой по числу → Refactor → Introduce Constant
Выделяем нужные строки → правый щелчок мышки → Refactor → Extract Method...
И наоборот
4️⃣ Убрать лишние методы и переменные, "уплотнить" код
Правый щёлк → Refactor → Inline Method/Inline Variable
IDEA - очень продвинутая IDE . Методов рефакторинга так много, что у них даже отдельная вкладка в меню. На каждом проекте точно пригодится:
1️⃣ Переименовать класс, метод, переменную или файл
Правой кнопкой по имени → Refactor → Rename
Имя изменится везде, где упоминается сущность.
2️⃣ Выделить константу
"Магические числа" в коде - плохая практика, лучше читаются именованные константы.
Правой кнопкой по числу → Refactor → Introduce Constant
❌ for(int i=0;i<100;i++)3️⃣ Перенести код в отдельный метод
✅ for(int i=0;i<HLIMIT;i++)
Выделяем нужные строки → правый щелчок мышки → Refactor → Extract Method...
И наоборот
4️⃣ Убрать лишние методы и переменные, "уплотнить" код
Правый щёлк → Refactor → Inline Method/Inline Variable