Что будет во входном параметре getInfo?
Anonymous Poll
10%
Базовый enum для Planet и Meteor
9%
Базовый класс для Planet и Meteor
50%
Общий интерфейс для Planet и Meteor
6%
Композитный класс, в который входит Planet и Meteor
4%
Вопрос некорректен, в енумах нельзя определять методы
21%
Это невозможно, нужны отдельные методы getInfo(Planet) и getInfo(Meteor)
Enumerations, часть 1: обзор
Внутри JVM нет такого понятия как enum. Енумы компилируются в обычный класс, а значения - в статические экземпляры:
Но есть нюансы:
🔸 Суперкласс Enum
От суперкласса Enum наследуются методы name(), ordinal() и статический метод values().
name() возвращает имя переменной, ordinal() - порядковый номер в списке. На практике эти методы достаточно бесполезны.
Метод values() используется чаще и возвращает массив всех объектов. Можно пройтись по нему в цикле:
Enum реализует три интерфейса: Comparable, Serializable, Constable.
Первые два всем знакомы. Интерфейс Constable определяет методы для размещения объектов в пуле констант внутри JVM.
🔸 Создание экземпляров
Этим занимается JVM на старте приложения. Экземпляры енума создаются через приватный конструктор, недоступный вне енума.
🔸 Поля
Указываются для каждого экземпляра, инициализируются в конcтрукторе:
▫️Обычные
В теории можно сделать set* метод и поменять поле у любого экземпляра. Но на практике так никто не делает. Когда объект доступен из любого места системы, то проще жить, если он неизменяемый.
▫️Абстрактные
Каждый экземпляр определяет свою реализацию:
Любой enum - это final класс с уже определённым суперклассом. Единственный шанс встроить enum в иерархию - добавить для него интерфейс.
Иногда это удобно. Если у енумов и классов один интерфейс, то с ними можно работать через один метод:
Внутри JVM нет такого понятия как enum. Енумы компилируются в обычный класс, а значения - в статические экземпляры:
public final class Planet extends Enum {
public static final Planet EARTH;
public static final Planet MARS;
}
Полноценные классы c конструкторами, методами, полями и статическими элементами. У экземпляров есть состояние и определённое поведение.Но есть нюансы:
🔸 Суперкласс Enum
От суперкласса Enum наследуются методы name(), ordinal() и статический метод values().
name() возвращает имя переменной, ordinal() - порядковый номер в списке. На практике эти методы достаточно бесполезны.
Метод values() используется чаще и возвращает массив всех объектов. Можно пройтись по нему в цикле:
for (Planet p : Planet.values()) {…}
или через Stream API:Arrays.stream(Planet.values()).forEach(…)🔸 Интерфейсы суперкласса
Enum реализует три интерфейса: Comparable, Serializable, Constable.
Первые два всем знакомы. Интерфейс Constable определяет методы для размещения объектов в пуле констант внутри JVM.
🔸 Создание экземпляров
Этим занимается JVM на старте приложения. Экземпляры енума создаются через приватный конструктор, недоступный вне енума.
🔸 Поля
Указываются для каждого экземпляра, инициализируются в конcтрукторе:
public enum Planet {
MARS(3389),
EARTH(6371);
int radius;
Planet(int radius) {
this.radius = radius;
}
}
🔸 Два типа методов▫️Обычные
public int getRadius()
Используется для геттеров и простых вычислений. В теории можно сделать set* метод и поменять поле у любого экземпляра. Но на практике так никто не делает. Когда объект доступен из любого места системы, то проще жить, если он неизменяемый.
▫️Абстрактные
Каждый экземпляр определяет свою реализацию:
public enum Planet {
MARS {
int distanceFrom(int) {…}
},
EARTH {
int distanceFrom(int) {…}
};
abstract int distanceFrom(int);
}
🔸 НаследованиеЛюбой enum - это final класс с уже определённым суперклассом. Единственный шанс встроить enum в иерархию - добавить для него интерфейс.
Иногда это удобно. Если у енумов и классов один интерфейс, то с ними можно работать через один метод:
interface SpaceObject
enum Planet implements SpaceObject
class Meteor implements SpaceObject
public void getInfo(SpaceObject so)👍9🔥1
Вопрос
Enum Formatter занимается форматированием строк в заданные форматы. Во время работы было выполнено 50 форматирований в XML, в JSON - 30, в YAML - 5.
Что вернёт метод Formatter.JSON.getCount()?
Enum Formatter занимается форматированием строк в заданные форматы. Во время работы было выполнено 50 форматирований в XML, в JSON - 30, в YAML - 5.
Что вернёт метод Formatter.JSON.getCount()?
Что вернёт метод Formatter.JSON.getCount()?
Anonymous Quiz
36%
30
3%
80
43%
85
14%
Код не скомпилируется
4%
Ошибка времени выполнения
Enumerations, часть 2: практика
В прошлом посте разобрали, из чего сделан enum. В этом - разберёмся, как его использовать.
1️⃣ Набор констант
Самый простой и популярный случай. Используем enum как поле класса или локальную переменную. Проверяем значение через if / switch и делаем ветвления в коде:
Вообще синглтон - антипаттерн и антитренд. Если без него никак - рассмотрите вариант с enum. Если вы склонны к риску, можно сделать изменяемые поля.
✅ Может быть несколько объектов в рамках одного enum
✅ Доступ из любого места в коде
✅ Ленивая инициализация
3️⃣ Паттерн State
Более продвинутый вариант, чем "набор констант". Здесь фокус на данных: используются связанные значения, а не элемент enum сам по себе.
Пример: в статус пользователя вносим дополнительные поля - необходимость подтверждения личности и максимальный размер скидки:
Здесь фокус на разнице в поведении. Абстрактный метод переопределяется для каждого экземпляра:
✅ Простая иерархия, всё в одном классе
✅ Список всех значений доступен через values()
😐 Весь код в одном файле. Не всегда удобно
❌ Сложно связать с другими объектами
❌ Нарушается принцип open for extension but closed for modification
Получается специфическая альтернатива полиморфизму, но иногда ок.
Enum - не просто набор констант. Инструмент своеобразный, но полезно знать и такие реализации паттернов. Чем шире арсенал разработчика, тем выше шанс найти подходящее решение.
В прошлом посте разобрали, из чего сделан enum. В этом - разберёмся, как его использовать.
1️⃣ Набор констант
Самый простой и популярный случай. Используем enum как поле класса или локальную переменную. Проверяем значение через if / switch и делаем ветвления в коде:
if (state == UserState.NEW) {…}
2️⃣ Паттерн SingletonВообще синглтон - антипаттерн и антитренд. Если без него никак - рассмотрите вариант с enum. Если вы склонны к риску, можно сделать изменяемые поля.
✅ Может быть несколько объектов в рамках одного enum
✅ Доступ из любого места в коде
✅ Ленивая инициализация
3️⃣ Паттерн State
Более продвинутый вариант, чем "набор констант". Здесь фокус на данных: используются связанные значения, а не элемент enum сам по себе.
Пример: в статус пользователя вносим дополнительные поля - необходимость подтверждения личности и максимальный размер скидки:
enum UserState {
NEW(true, 10),
VALIDATED(false, 25),
FRAUD(true, 0);
boolean needConfirmation;
int maxDiscount;
}
4️⃣ Паттерн Strategy Здесь фокус на разнице в поведении. Абстрактный метод переопределяется для каждого экземпляра:
NEW { boolean check(Order){…}},
VALIDATED { boolean check(Order){…}};
abstract boolean check(Order order)
✅ Подойдёт для алгоритмов, конвертеров, сортировок✅ Простая иерархия, всё в одном классе
✅ Список всех значений доступен через values()
😐 Весь код в одном файле. Не всегда удобно
❌ Сложно связать с другими объектами
❌ Нарушается принцип open for extension but closed for modification
Получается специфическая альтернатива полиморфизму, но иногда ок.
Enum - не просто набор констант. Инструмент своеобразный, но полезно знать и такие реализации паттернов. Чем шире арсенал разработчика, тем выше шанс найти подходящее решение.
👍5❤1
Вопрос
В классе User хранится статус в виде enum State.
В каком порядке и как будут создаваться объекты при первом вызове User user = new User()?
В классе User хранится статус в виде enum State.
В каком порядке и как будут создаваться объекты при первом вызове User user = new User()?
В каком порядке и как будут создаваться объекты при первом вызове new User()?
Anonymous Poll
5%
А. enum VALIDATED(Init-Constr-Static), объект User (Constr-Init)
29%
B. enum VALIDATED(Static-Init-Constr), объект User (Init-Constr)
9%
C. Объект User(Init-Constr), enum VALIDATED(Static-Init-Constr)
7%
D. Объект User(Constr-Init), enum VALIDATED(Init-Constr-Static)
30%
E. enum NEW(Static-Init-Constr), enum VALIDATED(Static-Init-Constr), объект User (Init-Constr)
20%
G. enum NEW(Init-Constr), enum VALIDATED(Init-Constr), enum-Static, объект User (Init-Constr)
Enumerations, часть 3: загрузка классов
Мы уже разобрали, из чего состоит enum и как его использовать. Сегодня разберём, как загружаются классы и экземпляры enum. Поможет нам в этом душноватый вопрос с собеседований, который находится выше.
Классы в JVM загружаются по мере необходимости. Что происходит при первом вызове new User():
🔹 Загрузка класса User, создание экземпляра User.class
🔹 Инициализируются статические переменные и выполняются блоки static {...}
Дальше переходим к созданию объекта:
🔸 Создаются явно указанные поля и выполняются блоки { }
🔸 Вызов конструктора класса и суперклассов
Внутри инициализирующего блока понадобился класс State. Переходим к нему.
Enum State компилируется в такую штуку:
В такой ситуации JVM создаёт экземпляры NEW и VALIDATED перед статическими блоками. Получается такой порядок:
1️⃣ Создаём экземпляр State NEW:
▫️блок инициализации S-Init
▫️конструктор S-Constr
2️⃣ Повторяем тот же процесс для VALIDATED
3️⃣ Выполняем статический блок S-Static
Поэтому правильный ответ - G:
▫️enum NEW(Init-Constr)
▫️enum VALIDATED(Init-Constr)
▫️enum-Static
▫️объект User (Init-Constr)
Мы уже разобрали, из чего состоит enum и как его использовать. Сегодня разберём, как загружаются классы и экземпляры enum. Поможет нам в этом душноватый вопрос с собеседований, который находится выше.
Классы в JVM загружаются по мере необходимости. Что происходит при первом вызове new User():
🔹 Загрузка класса User, создание экземпляра User.class
🔹 Инициализируются статические переменные и выполняются блоки static {...}
Дальше переходим к созданию объекта:
🔸 Создаются явно указанные поля и выполняются блоки { }
🔸 Вызов конструктора класса и суперклассов
Внутри инициализирующего блока понадобился класс State. Переходим к нему.
Enum State компилируется в такую штуку:
public final class State extends Enum {
public static final State NEW;
public static final State VALIDATED;
// init блок
State() {...}
// static блок
}
Возникла проблема. Перед вызовом конструктора надо закончить со статическими полями. А статические поля - экземпляры этого же класса. Замкнутый круг.В такой ситуации JVM создаёт экземпляры NEW и VALIDATED перед статическими блоками. Получается такой порядок:
1️⃣ Создаём экземпляр State NEW:
▫️блок инициализации S-Init
▫️конструктор S-Constr
2️⃣ Повторяем тот же процесс для VALIDATED
3️⃣ Выполняем статический блок S-Static
Поэтому правильный ответ - G:
▫️enum NEW(Init-Constr)
▫️enum VALIDATED(Init-Constr)
▫️enum-Static
▫️объект User (Init-Constr)
❤1
Анонс мероприятий
Реклама не за деньги, а по любви. Общалась со всеми организаторами, поэтому рекомендую с чистой совестью.
1️⃣ Integrity Solutions Tech Talks. Митап при участии JUG Ru Group
Когда: 24 мая, 18:00 (по МСК)
Участие: бесплатно
Формат: онлайн
Программа :
▫️ "Java Flight Recorder": что такое, чем полезен и когда его применять. На мой взгляд самый полезный доклад.
▫️"Переезд с PostgreSQL на Elasticsearch для гибкого поиска адресов"
▫️ "Использование Тhymeleaf для динамических SQL-запросов"
Подробности и регистрация
2️⃣ Онлайн-чемпионат от Совкомбанк
Для кого: junior-middle
Зачем: проверить свои знания и найти пробелы. Если у вас в резюме нет пет-проджекта, можно сделать на основе заданий чемпа. Есть шанс выиграть деньги и мерч.
Когда: 22-23 мая
Формат: онлайн
Подача заявки: сегодня последний день😨
Что будет:
22 мая: вопросы по Java core, Collections, веб-сервисы, очереди, БД и ORM, Maven, git
23 мая: сделать REST сервис с авторизацией и с кем-то интегрироваться. Чьё приложение пройдёт больше тестов и лучше выдержит нагрузку - тот победит.
Подробности и регистрация
3️⃣ Ночной онлайн-чемпионат Tech Monsters Night от М.Видео
Для кого: middle-senior
Зачем: проверить себя и побороться за мерч и технику. Организаторы обещают доставку пиццы всем участникам🍕
Когда: 4 июня, с 22 до 3 ночи
Формат: онлайн
Подача заявки: до 3 июня
Подробности и регистрация
Реклама не за деньги, а по любви. Общалась со всеми организаторами, поэтому рекомендую с чистой совестью.
1️⃣ Integrity Solutions Tech Talks. Митап при участии JUG Ru Group
Когда: 24 мая, 18:00 (по МСК)
Участие: бесплатно
Формат: онлайн
Программа :
▫️ "Java Flight Recorder": что такое, чем полезен и когда его применять. На мой взгляд самый полезный доклад.
▫️"Переезд с PostgreSQL на Elasticsearch для гибкого поиска адресов"
▫️ "Использование Тhymeleaf для динамических SQL-запросов"
Подробности и регистрация
2️⃣ Онлайн-чемпионат от Совкомбанк
Для кого: junior-middle
Зачем: проверить свои знания и найти пробелы. Если у вас в резюме нет пет-проджекта, можно сделать на основе заданий чемпа. Есть шанс выиграть деньги и мерч.
Когда: 22-23 мая
Формат: онлайн
Подача заявки: сегодня последний день😨
Что будет:
22 мая: вопросы по Java core, Collections, веб-сервисы, очереди, БД и ORM, Maven, git
23 мая: сделать REST сервис с авторизацией и с кем-то интегрироваться. Чьё приложение пройдёт больше тестов и лучше выдержит нагрузку - тот победит.
Подробности и регистрация
3️⃣ Ночной онлайн-чемпионат Tech Monsters Night от М.Видео
Для кого: middle-senior
Зачем: проверить себя и побороться за мерч и технику. Организаторы обещают доставку пиццы всем участникам🍕
Когда: 4 июня, с 22 до 3 ночи
Формат: онлайн
Подача заявки: до 3 июня
Подробности и регистрация
👍4
Ресурсы, идиома RAII и Java 9
Разработка - сложная штука c множеством идей и концептов. В этом посте я расскажу об идиоме RAII, разнице при работе с ресурсами в java и C++, блоке try-with-resources и изменениях java 9.
Начнём с основ. Ресурс - это сущность, для которой нужен эксклюзивный доступ. Их количество ограничено. Большинство ресурсов находятся за пределами JVM: файлы, соединения с БД, сокеты и т.д
Для корректной работы только один поток должен работать с ресурсом. Для этого нужно принять дополнительные меры. Обозначим их как "открыть" и "закрыть" ресурс:
🔸Открыть = получить эксклюзивный доступ.
🔸Закрыть = закончить работу. Теперь другой поток может работать с ресурсом.
Такой вот нехитрый жизненный цикл(ЖЦ).
RAII
или Resource Acquisition Is Initialization - одна из техник по работе с ресурсами. Другое название: SBRM - Scope-Bound Resource Management.
Суть: ассоциируем ресурс с объектом. Тогда ЖЦ ресурса совпадает с ЖЦ объекта. В идеале это выглядит так:
В java так не получится. Деструктор выполнится неизвестно когда, а ресурс - штука ценная. Поэтому в java нужно явно вызвать метод закрытия. В стандартных библиотеках это close().
В java 7 появилась специальная конструкция для ресурсов:
Но такой формат не идеален. Внутри try всегда определяется новый объект. Когда ресурсов два и больше, код становится громоздким:
С другой - после try остаются переменные с закрытыми ресурсами. Теперь нельзя сказать, что java реализует RAII.
Ну и ладно, никто не расстроился🙂 Языки разные, и классно замечать, как по-разному в них реализуются идеи, и как они меняются с течением времени.
Разработка - сложная штука c множеством идей и концептов. В этом посте я расскажу об идиоме RAII, разнице при работе с ресурсами в java и C++, блоке try-with-resources и изменениях java 9.
Начнём с основ. Ресурс - это сущность, для которой нужен эксклюзивный доступ. Их количество ограничено. Большинство ресурсов находятся за пределами JVM: файлы, соединения с БД, сокеты и т.д
Для корректной работы только один поток должен работать с ресурсом. Для этого нужно принять дополнительные меры. Обозначим их как "открыть" и "закрыть" ресурс:
🔸Открыть = получить эксклюзивный доступ.
🔸Закрыть = закончить работу. Теперь другой поток может работать с ресурсом.
Такой вот нехитрый жизненный цикл(ЖЦ).
RAII
или Resource Acquisition Is Initialization - одна из техник по работе с ресурсами. Другое название: SBRM - Scope-Bound Resource Management.
Суть: ассоциируем ресурс с объектом. Тогда ЖЦ ресурса совпадает с ЖЦ объекта. В идеале это выглядит так:
public void do() {
File f=new File("t.txt");
// Создаём объект File, ресурс "t.txt" теперь наш
// что-то делаем
}
// вышли из метода: объект f уже не нужен, ресурс "t.txt" освобождается
RAII легко реализовать в С++. В конструкторе пишем логику "открытия" ресурса, а в деструкторе - "закрытия". Конструктор и деструктор явно прописываются, поэтому ЖЦ объекта и ресурса под контролем.В java так не получится. Деструктор выполнится неизвестно когда, а ресурс - штука ценная. Поэтому в java нужно явно вызвать метод закрытия. В стандартных библиотеках это close().
В java 7 появилась специальная конструкция для ресурсов:
try (PrintWriter writer = new PrintWriter(…)) {
// что-то делаем
}
Ресурс реализует интерфейс AutoCloseable, и после завершения блока для него вызовется close(). try-with-resources считается java реализацией RAII.Но такой формат не идеален. Внутри try всегда определяется новый объект. Когда ресурсов два и больше, код становится громоздким:
try (DatagramChannel udpServer = …;В java 9 в блок try можно передать переменные:
Selector selector = …) {
// …
}
DatagramChannel udpServer = …;С одной стороны это удобно. Код выглядит приятно.
Selector selector = …;
try (udpServer;selector) {
// …
}
С другой - после try остаются переменные с закрытыми ресурсами. Теперь нельзя сказать, что java реализует RAII.
Ну и ладно, никто не расстроился🙂 Языки разные, и классно замечать, как по-разному в них реализуются идеи, и как они меняются с течением времени.
👍4
Почему на канале так много Java Core? Потому что это база для ежедневной работы, которую хорошо знать вдоль и поперёк. Но лично мой интерес лежит в другом. Мне нравится разбирать дизайн и разбираться, почему сделано так, а не иначе.
Тема этой недели - дженерики.
Часть 1: посмотрим реализацию в JVM
Часть 2: углубимся в wildcards и работу с подтипами
Тема этой недели - дженерики.
Часть 1: посмотрим реализацию в JVM
Часть 2: углубимся в wildcards и работу с подтипами
👍4
Generics, часть 1: история и корпоративные ценности
Вернёмся в 1998 год.
В java 1.2 появились интерфейсы Map, Set, List и основные реализации. В те времена они хранили только объекты:
❌ Можно запросто получить ClassCastException
❌ Надо помнить тип объектов
❌ Ошибки возникают только в рантайме
В java 5 появилась возможность указать тип через дженерики:
✅ Хорошо читается
✅ Компилятор проверяет типы
Как реализованы дженерики?
Примерим на себя должность архитектора java и прикинем, что можно сделать с ArrayList<T>:
1️⃣ Скомпилировать в класс с типом String
Все T заменяем на String:
2️⃣ Добавить в JVM параметризованный тип
<T> остаётся на уровне байт-кода и определяется для каждого объекта в рантайме.
3️⃣ Оставить на уровне JVM работу с объектами. Проверить все типы во время компиляции и привести один к другому там, где нужно.
Получаем один ArrayList.class и множество дополнительных конструкций по всему коду.
Для ArrayList<String> программист напишет:
Почти в каждом IT офисе на стенах висят слоганы и ценности компании. Что-то про качество работы, развитие сотрудников, гибкость и довольных клиентов. Из этого получается классный корпоративный мерч.
Ценности нормального человека определяют стратегию и дают ориентир для решения сложных ситуаций. "Ценности" java явно нигде не определены, но направление работы задано чётко. Например, кроссплатформенность и слоган Write Once, Run Anywhere заставляют разработчиков JDK поддерживать фичи даже для древних процессоров.
Бинарная совместимость - ещё один ориентир java. Поэтому для дженериков используется третий вариант: компилятор добавляет необходимые приведения типов в код и стирает информацию о дженериках. И код на java 5 теперь совместим с предыдущими версиями.
Ответ на вопрос перед постом
В консоли напечатаются оба значения. У списка tmp тип не задан, поэтому компилятор не мешает добавлять строки и передавать объекты в метод println.
Если в println передать список intList.get(0), где тип указан явно, то мы получим ошибку компиляции.
Вернёмся в 1998 год.
В java 1.2 появились интерфейсы Map, Set, List и основные реализации. В те времена они хранили только объекты:
List values = new ArrayList()Чтобы нормально работать, приходилось использовать явное приведение:
values.add("value");
String s = (String) values.get(0);
Главная проблема с коллекциями тех лет - type loss, потеря типа при компиляции и написании кода:❌ Можно запросто получить ClassCastException
❌ Надо помнить тип объектов
❌ Ошибки возникают только в рантайме
В java 5 появилась возможность указать тип через дженерики:
List<String> values = new ArrayList<String>();✅ Удобно пользоваться
values.add("p");
String v = values.get(0);
✅ Хорошо читается
✅ Компилятор проверяет типы
Как реализованы дженерики?
Примерим на себя должность архитектора java и прикинем, что можно сделать с ArrayList<T>:
1️⃣ Скомпилировать в класс с типом String
Все T заменяем на String:
void add(String str)В итоге создаём отдельный класс для каждого типа данных.
String get(int index)
2️⃣ Добавить в JVM параметризованный тип
<T> остаётся на уровне байт-кода и определяется для каждого объекта в рантайме.
3️⃣ Оставить на уровне JVM работу с объектами. Проверить все типы во время компиляции и привести один к другому там, где нужно.
Получаем один ArrayList.class и множество дополнительных конструкций по всему коду.
Для ArrayList<String> программист напишет:
String value = list.get(0)Компилятор преобразует это в
String value = (String) list.get(0);🤔Все варианты адекватные и имеют право на жизнь. Что же выбрать?
Почти в каждом IT офисе на стенах висят слоганы и ценности компании. Что-то про качество работы, развитие сотрудников, гибкость и довольных клиентов. Из этого получается классный корпоративный мерч.
Ценности нормального человека определяют стратегию и дают ориентир для решения сложных ситуаций. "Ценности" java явно нигде не определены, но направление работы задано чётко. Например, кроссплатформенность и слоган Write Once, Run Anywhere заставляют разработчиков JDK поддерживать фичи даже для древних процессоров.
Бинарная совместимость - ещё один ориентир java. Поэтому для дженериков используется третий вариант: компилятор добавляет необходимые приведения типов в код и стирает информацию о дженериках. И код на java 5 теперь совместим с предыдущими версиями.
Ответ на вопрос перед постом
В консоли напечатаются оба значения. У списка tmp тип не задан, поэтому компилятор не мешает добавлять строки и передавать объекты в метод println.
Если в println передать список intList.get(0), где тип указан явно, то мы получим ошибку компиляции.
👍3🔥1
Какой результат выполнения этого кода?
Anonymous Poll
34%
Ошибка на этапе компиляции
4%
RuntimeException
12%
Напечатается Parent
50%
Напечатается Child
Generics, часть 2: вопросики
В этой части разберём wildcards, и как дженерики работают с иерархиями.
Начнём с ответа на вопрос перед постом. Компилятор выдаст ошибку.
Почему так?
Пойдём от обратного и представим, что метод принимает List<Child>.
Внутри метода мы работаем со ссылкой на List<Parent>, значит в список можно добавить объект Parent. Если метод примет List<Child>, значит List<Child> тоже принимает объекты Parent. Что противоречит контракту, поэтому List<Child> никак не связан с List<Parent>.
Здесь разработчики дженериков учли неудачный опыт с массивами. Для массивов возможна такая ситуация:
🔸 Создаём переменную arr с массивом Parent:
Wildcards
Для ситуаций, когда мы точно-точно не будем менять список, и нам только посмотреть, можно использовать wildcards:
void print(List<?> list)
Теперь метод принимает списки любых типов. Конкретный тип неизвестен, поэтому доступны только методы класса Object. Можно получить размер списка или вызвать для элементов toString.
❓Чем List<?> отличается от List?
В List<?> компилятор не даст ничего добавить, а для List ограничений нет.
❓Как вызвать нужные методы для List<?>?
Тип в списке можно уточнить:
Буквы и вопросики
Есть два способа ограничить типы списков:
T - это конкретный тип элементов списка, поэтому в список можно добавить объекты типа Т. В листе с вопросиком конкретный тип НЕ известен, и компилятор НЕ даст добавить новые элементы.
extends и super
В иерархии Object - Parent - Child можно ограничить типы с разных сторон.
extends - ограничение иерархии "сверху"
super - ограничение типов "снизу"
В этой части разберём wildcards, и как дженерики работают с иерархиями.
Начнём с ответа на вопрос перед постом. Компилятор выдаст ошибку.
Почему так?
Пойдём от обратного и представим, что метод принимает List<Child>.
Внутри метода мы работаем со ссылкой на List<Parent>, значит в список можно добавить объект Parent. Если метод примет List<Child>, значит List<Child> тоже принимает объекты Parent. Что противоречит контракту, поэтому List<Child> никак не связан с List<Parent>.
Здесь разработчики дженериков учли неудачный опыт с массивами. Для массивов возможна такая ситуация:
🔸 Создаём переменную arr с массивом Parent:
Parent[] arr;🔸 Присваиваем массив Child:
arr = new Child[2];🔸 Добавляем в arr экземпляры Child и Parent:
arr[0] = new Child();Вторая строка в рантайме бросит ArrayStoreException. Дженерики обрабатываются при компиляции, поэтому ошибок на этапе выполнения нет.
arr[1] = new Parent();
Wildcards
Для ситуаций, когда мы точно-точно не будем менять список, и нам только посмотреть, можно использовать wildcards:
void print(List<?> list)
Теперь метод принимает списки любых типов. Конкретный тип неизвестен, поэтому доступны только методы класса Object. Можно получить размер списка или вызвать для элементов toString.
❓Чем List<?> отличается от List?
В List<?> компилятор не даст ничего добавить, а для List ограничений нет.
❓Как вызвать нужные методы для List<?>?
Тип в списке можно уточнить:
void print(List<? extends Parent> list)Теперь метод принимает только List<Parent> и List<Child>, плюс есть доступ к методам Parent. Но менять список всё ещё нельзя.
Буквы и вопросики
Есть два способа ограничить типы списков:
▫️ <T extends Parent> void test(List<T> list)Чем они отличаются?
▫️ void test(List<? extends Parent> list)
T - это конкретный тип элементов списка, поэтому в список можно добавить объекты типа Т. В листе с вопросиком конкретный тип НЕ известен, и компилятор НЕ даст добавить новые элементы.
extends и super
В иерархии Object - Parent - Child можно ограничить типы с разных сторон.
extends - ограничение иерархии "сверху"
? extends Parent: допустимы Parent, Child и наследники Childsuper - ограничение типов "снизу"
? super Parent принимает Parent и родителей Parent вплоть до Object❤2
JVM Report 2020
Израильская компания Snyk каждый год опрашивает разработчиков о проектах и технологиях. В этом году в выборке 60% участников из Европы, 19% из США, на Россию и Азию пришлось 12%. Хороший шанс посмотреть, что там у других и на картину в целом
Полный отчёт доступен тут, а я расскажу самое интересное
1️⃣ Самый популярный JVM язык:
Однозначно, java. Используется на 91% проектов
31% используют в проектах несколько языков. Среди них:
17% Kotlin
13% Groovy
10% Scala
8,4% Closure
2️⃣ Версия java в продакшене:
61% Java 11
59% Java 8
11% Java 15
40% опрошенных используют несколько версий
30% используют Java 8 и Java 11
3️⃣ Какая JDK используется в продакшене:
44% AdoptOpenJDK
28% Oracle OpenJDK
23% Oracle JDK
15% Azul Zulu
37% участников используют 2 JDK, а 12.5% - три и больше.
4️⃣ Как собирается приложение:
76% Maven
38% Gradle
12% Ant
5️⃣ Основной фреймворк разработки:
58% Spring Boot
29% Spring MVC
24% Java EE
13% Jakarta EE
11% Quarkus
5% Micronaut
16% не пользуются фреймворками вообще😱
6️⃣ Самая популярная IDE:
72% IDEA
24% Eclipse
23% Visual Studio Code (2% в прошлом году)
13% Apache NetBeans (1% в прошлом году)
51% ответили, что пользуются несколькими IDE
По сравнению с прошлым годом Java 11 используется на 40% чаще, а IDEA и Kotlin уверенно укрепляют позиции💪
Израильская компания Snyk каждый год опрашивает разработчиков о проектах и технологиях. В этом году в выборке 60% участников из Европы, 19% из США, на Россию и Азию пришлось 12%. Хороший шанс посмотреть, что там у других и на картину в целом
Полный отчёт доступен тут, а я расскажу самое интересное
1️⃣ Самый популярный JVM язык:
Однозначно, java. Используется на 91% проектов
31% используют в проектах несколько языков. Среди них:
17% Kotlin
13% Groovy
10% Scala
8,4% Closure
2️⃣ Версия java в продакшене:
61% Java 11
59% Java 8
11% Java 15
40% опрошенных используют несколько версий
30% используют Java 8 и Java 11
3️⃣ Какая JDK используется в продакшене:
44% AdoptOpenJDK
28% Oracle OpenJDK
23% Oracle JDK
15% Azul Zulu
37% участников используют 2 JDK, а 12.5% - три и больше.
4️⃣ Как собирается приложение:
76% Maven
38% Gradle
12% Ant
5️⃣ Основной фреймворк разработки:
58% Spring Boot
29% Spring MVC
24% Java EE
13% Jakarta EE
11% Quarkus
5% Micronaut
16% не пользуются фреймворками вообще😱
6️⃣ Самая популярная IDE:
72% IDEA
24% Eclipse
23% Visual Studio Code (2% в прошлом году)
13% Apache NetBeans (1% в прошлом году)
51% ответили, что пользуются несколькими IDE
По сравнению с прошлым годом Java 11 используется на 40% чаще, а IDEA и Kotlin уверенно укрепляют позиции💪
👍2
Mutation testing: тесты на тесты
Фраза "нужны тесты на тесты" на проектах встречается часто. Звучит как шутка, но проблема за этим стоит серьёзная.
Тесты есть, но насколько хорошо они тестируют приложение? Когда что-то поменяется и сломается, узнаем ли мы об этом? Нет ли ошибок в самих тестах? Чем больше проект, тем насущнее эта проблема.
Test Coverage - сомнительная метрика. Высокий test coverage означает только, что в тестах участвует много кода.
В этом посте я расскажу о mutation testing. В чём суть, возможные минусы и как использовать подход mutation testing уже сейчас.
Идея
Фреймворк делает небольшое изменение в коде и запускает тесты. Тесты упали - значит кейс проверяется. Тесты прошли - значит тестов не хватает.
Пример: тестируется метод
▫️
▫️
Фреймворк поменяет код на
Для JVM языков по факту есть только один фреймворк Pitest с двумя опциями - Gregor and Descartes.
Как работает:
1️⃣ Запускает тесты и анализирует test coverage. Если код не покрыт тестами, то нет смысла мутировать его.
2️⃣ Составляет список мутаций. Здесь два варианта:
🔸 Gregor тестирует по максимуму, в том числе геттеры и сеттеры
🔸 Descartes использует меньше вариаций, но работает быстрее
3️⃣ Для каждого пункта фреймворк меняет байт-код и запускает тесты. Все изменения, для которых тесты прошли, попадают в итоговый репорт
Минусы
❌ Проверяются только несложные изменения бизнес-логики
❌ Долго выполняется: от 10 минут до 6 часов. Для популярных библиотек результаты такие
❌ Сложно читать результаты и понять, каких тестов не хватает. Связано это с тем, что фреймворк работает не с исходным кодом, а с байт-кодом
❌ Gregor плохо совместим с Kotlin и проверяет конструкции, недопустимые на уровне языка. Приходится либо терпеть множество предупреждений, либо исключать некоторые проверки и рисковать бизнес-логикой. Либо использовать Descartes
❌ Слабо развивается и выглядит как pet project
❌ Непонятно как встроить в CI. Так как возможны ложноположительные результаты, репорты нужно проверять вручную. Можно оставить как необязательную опцию для самых мнительных, но проверка занимает много времени и вряд ли будет востребована
Итог: идея интересная и перспективная, но слабо развивается на практике.
Хозяйке на заметку
Подход mutation testing подойдёт для проверки тестов сложного или критичного участка кода:
▫️Поменяйте условие
▫️Верните константу в метод
▫️Удалите тело в void методе
▫️Поменяйте методы местами
▫️Уберите инкремент
▫️Верните null
▫️Поменяйте бизнес-логику
И запустите тесты. Если падают - значит код протестирован хорошо. Если нет - допишите тесты. Главное - вернуть потом изменения обратно🙂
Фраза "нужны тесты на тесты" на проектах встречается часто. Звучит как шутка, но проблема за этим стоит серьёзная.
Тесты есть, но насколько хорошо они тестируют приложение? Когда что-то поменяется и сломается, узнаем ли мы об этом? Нет ли ошибок в самих тестах? Чем больше проект, тем насущнее эта проблема.
Test Coverage - сомнительная метрика. Высокий test coverage означает только, что в тестах участвует много кода.
В этом посте я расскажу о mutation testing. В чём суть, возможные минусы и как использовать подход mutation testing уже сейчас.
Идея
Фреймворк делает небольшое изменение в коде и запускает тесты. Тесты упали - значит кейс проверяется. Тесты прошли - значит тестов не хватает.
Пример: тестируется метод
return m > 10;Написали для него тесты:
▫️
m = 0, ожидаем false▫️
m = 100, ожидаем trueФреймворк поменяет код на
m ≥ 10, и ни один тест не упадёт. Значит, текущих тестов недостаточно, нужно проверить условие m = 10
ПрактикаДля JVM языков по факту есть только один фреймворк Pitest с двумя опциями - Gregor and Descartes.
Как работает:
1️⃣ Запускает тесты и анализирует test coverage. Если код не покрыт тестами, то нет смысла мутировать его.
2️⃣ Составляет список мутаций. Здесь два варианта:
🔸 Gregor тестирует по максимуму, в том числе геттеры и сеттеры
🔸 Descartes использует меньше вариаций, но работает быстрее
3️⃣ Для каждого пункта фреймворк меняет байт-код и запускает тесты. Все изменения, для которых тесты прошли, попадают в итоговый репорт
Минусы
❌ Проверяются только несложные изменения бизнес-логики
❌ Долго выполняется: от 10 минут до 6 часов. Для популярных библиотек результаты такие
❌ Сложно читать результаты и понять, каких тестов не хватает. Связано это с тем, что фреймворк работает не с исходным кодом, а с байт-кодом
❌ Gregor плохо совместим с Kotlin и проверяет конструкции, недопустимые на уровне языка. Приходится либо терпеть множество предупреждений, либо исключать некоторые проверки и рисковать бизнес-логикой. Либо использовать Descartes
❌ Слабо развивается и выглядит как pet project
❌ Непонятно как встроить в CI. Так как возможны ложноположительные результаты, репорты нужно проверять вручную. Можно оставить как необязательную опцию для самых мнительных, но проверка занимает много времени и вряд ли будет востребована
Итог: идея интересная и перспективная, но слабо развивается на практике.
Хозяйке на заметку
Подход mutation testing подойдёт для проверки тестов сложного или критичного участка кода:
▫️Поменяйте условие
▫️Верните константу в метод
▫️Удалите тело в void методе
▫️Поменяйте методы местами
▫️Уберите инкремент
▫️Верните null
▫️Поменяйте бизнес-логику
И запустите тесты. Если падают - значит код протестирован хорошо. Если нет - допишите тесты. Главное - вернуть потом изменения обратно🙂
👍2
Вам в какую очередь?
В пакете java.util.concurrent доступно 7 очередей. Самые простые из них это
Часть 1: внутреннее устройство
По названию класса легко предположить, что одна реализация сделана на основе массива, а две - с помощью связного списка.
Что это даёт? По сути - ничего.
Вспомним 2 списка с похожим строением - ArrayList и LinkedList. В первом легко искать элемент по индексу, во втором - вставлять и удалять элемент из середины списка.
Для очереди это не важно, потому что нас интересует только работа с началом и концом очереди.
Часть 2: механизм синхронизации
А здесь уже интереснее:
🔹 ArrayBQ использует один ReentrantLock
🔹 LinkedBQ - два ReentrantLock: один для начала очереди, другой - для конца
🔹 ConcurrentLQ использует CAS операции для обновления крайних элементов
Давайте попарно сравним их между собой
▪️ArrayBQ и LinkedBQ
Отдельные локи для начала и конца очереди в LinkedBQ хорошо работают, когда начало очереди не совпадает с концом. То есть в очереди всегда что-то есть. С двумя локами с очередью в каждый момент времени могут работать два потока.
Если размер колеблется около нуля, то поддерживать два лока слишком затратно, гораздо проще работать с одним локом на всю очередь. Это вариант ArrayBQ.
Поэтому первый критерий, по которому выбираем экземпляр очереди: сколько элементов она содержит большую часть времени.
▪️LinkedBQ и ConcurrentLQ
Допустим, наша очередь никогда не пустует. Что лучше - два ReentrantLock или две CAS операции?
Здесь разница в методах, которые нам нужны.
CAS операции обновляют только сами элементы - начало и конец очереди.
ReentrantLock ограждает критическую секцию, внутри которой
▫️ Проверяется текущий размер и сама возможность добавить в очередь
▫️ Обновляется начало или конец очереди
▫️ Уведомляются потоки, которые, возможно ждут на другом конце очереди
Добавление и удаление в ConcurrentLQ происходит очень быстро. Если нужны дополнительные опции - фиксированный размер, блокирующие вызовы, то лучше подойдёт LinkedBQ.
Поэтому критерий выбора очереди №2: необходимые методы и ограничения на размер
В пакете java.util.concurrent доступно 7 очередей. Самые простые из них это
🔸 ArrayBlockingQueueВ этом посте кратко расскажу, чем они отличаются и как выбрать подходящую.
🔸 LinkedBlockingQueue
🔸 ConcurrentLinkedQueue
Часть 1: внутреннее устройство
По названию класса легко предположить, что одна реализация сделана на основе массива, а две - с помощью связного списка.
Что это даёт? По сути - ничего.
Вспомним 2 списка с похожим строением - ArrayList и LinkedList. В первом легко искать элемент по индексу, во втором - вставлять и удалять элемент из середины списка.
Для очереди это не важно, потому что нас интересует только работа с началом и концом очереди.
Часть 2: механизм синхронизации
А здесь уже интереснее:
🔹 ArrayBQ использует один ReentrantLock
🔹 LinkedBQ - два ReentrantLock: один для начала очереди, другой - для конца
🔹 ConcurrentLQ использует CAS операции для обновления крайних элементов
Давайте попарно сравним их между собой
▪️ArrayBQ и LinkedBQ
Отдельные локи для начала и конца очереди в LinkedBQ хорошо работают, когда начало очереди не совпадает с концом. То есть в очереди всегда что-то есть. С двумя локами с очередью в каждый момент времени могут работать два потока.
Если размер колеблется около нуля, то поддерживать два лока слишком затратно, гораздо проще работать с одним локом на всю очередь. Это вариант ArrayBQ.
Поэтому первый критерий, по которому выбираем экземпляр очереди: сколько элементов она содержит большую часть времени.
▪️LinkedBQ и ConcurrentLQ
Допустим, наша очередь никогда не пустует. Что лучше - два ReentrantLock или две CAS операции?
Здесь разница в методах, которые нам нужны.
CAS операции обновляют только сами элементы - начало и конец очереди.
ReentrantLock ограждает критическую секцию, внутри которой
▫️ Проверяется текущий размер и сама возможность добавить в очередь
▫️ Обновляется начало или конец очереди
▫️ Уведомляются потоки, которые, возможно ждут на другом конце очереди
Добавление и удаление в ConcurrentLQ происходит очень быстро. Если нужны дополнительные опции - фиксированный размер, блокирующие вызовы, то лучше подойдёт LinkedBQ.
Поэтому критерий выбора очереди №2: необходимые методы и ограничения на размер
👍4
IDEA: магия вне Хорвартса
Обычно я пишу о полезных фичах IDEA, которые помогают писать код быстрее и проще.
Но не сегодня. Сегодня расскажу о фиче, которая впечатлит ваших коллег.
Чтобы они такие: "вау, как ты это делаешь???", а вы так загадочно улыбаетесь.
Отлично подойдёт для обсуждения кода при личном общении и при демонстрации экрана на онлайн-встречах.
Как вставить один и тот же код в несколько мест? Обычные разработчики использую
🔸 Добавляете каретку через Shift + Alt + Левый клик
🔸 Начинаете печатать
Вместо клика можно выделить часть текста и одновременно работать с несколькими фрагментами кода.
Чтобы вернуться в нормальный мир с одной кареткой, нажмите Esc.
Выглядит как магия, честное слово🔮
Обычно я пишу о полезных фичах IDEA, которые помогают писать код быстрее и проще.
Но не сегодня. Сегодня расскажу о фиче, которая впечатлит ваших коллег.
Чтобы они такие: "вау, как ты это делаешь???", а вы так загадочно улыбаетесь.
Отлично подойдёт для обсуждения кода при личном общении и при демонстрации экрана на онлайн-встречах.
Как вставить один и тот же код в несколько мест? Обычные разработчики использую
Ctrl+C и Ctrl+V, а настоящие шоумены - мультикаретки.🔸 Добавляете каретку через Shift + Alt + Левый клик
🔸 Начинаете печатать
Вместо клика можно выделить часть текста и одновременно работать с несколькими фрагментами кода.
Чтобы вернуться в нормальный мир с одной кареткой, нажмите Esc.
Выглядит как магия, честное слово🔮
❤2