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 вплоть до ObjectJVM 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 уверенно укрепляют позиции💪
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
▫️Поменяйте бизнес-логику
И запустите тесты. Если падают - значит код протестирован хорошо. Если нет - допишите тесты. Главное - вернуть потом изменения обратно🙂
Вам в какую очередь?
В пакете 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: необходимые методы и ограничения на размер
IDEA: магия вне Хорвартса
Обычно я пишу о полезных фичах IDEA, которые помогают писать код быстрее и проще.
Но не сегодня. Сегодня расскажу о фиче, которая впечатлит ваших коллег.
Чтобы они такие: "вау, как ты это делаешь???", а вы так загадочно улыбаетесь.
Отлично подойдёт для обсуждения кода при личном общении и при демонстрации экрана на онлайн-встречах.
Как вставить один и тот же код в несколько мест? Обычные разработчики использую
🔸 Добавляете каретку через Shift + Alt + Левый клик
🔸 Начинаете печатать
Вместо клика можно выделить часть текста и одновременно работать с несколькими фрагментами кода.
Чтобы вернуться в нормальный мир с одной кареткой, нажмите Esc.
Выглядит как магия, честное слово🔮
Обычно я пишу о полезных фичах IDEA, которые помогают писать код быстрее и проще.
Но не сегодня. Сегодня расскажу о фиче, которая впечатлит ваших коллег.
Чтобы они такие: "вау, как ты это делаешь???", а вы так загадочно улыбаетесь.
Отлично подойдёт для обсуждения кода при личном общении и при демонстрации экрана на онлайн-встречах.
Как вставить один и тот же код в несколько мест? Обычные разработчики использую
Ctrl+C
и Ctrl+V
, а настоящие шоумены - мультикаретки.🔸 Добавляете каретку через Shift + Alt + Левый клик
🔸 Начинаете печатать
Вместо клика можно выделить часть текста и одновременно работать с несколькими фрагментами кода.
Чтобы вернуться в нормальный мир с одной кареткой, нажмите Esc.
Выглядит как магия, честное слово🔮
Масштабирование: основные типы
Что такое масштабирование? Какие виды вы знаете? - популярные вопросы на собеседованиях на позицию мидл и выше.
На практике нас волнует не абстрактная стратегия масштабирования, а вполне конкретный вопрос: как обеспечить тот же уровень сервиса, если нагрузка на систему вырастет.
Иногда достаточно просто поменять код. Например, добавить кэш и снизить нагрузку на БД. В итоге сервис обработает в 10 раз больше запросов, и этого улучшения хватит на несколько лет. Но это не масштабирование, а оптимизация.
При масштабировании считается, что с кодом всё отлично и мы упираемся в физические ресурсы. Процессор не справляется, память кончается и так далее. И вопрос становится более конкретным: как добавлять аппаратные ресурсы к текущей системе?
На самом верхнем уровне масштабирование делится на горизонтальное и вертикальное.
Вертикальное - запускаем сервис на более мощной машине. Даже если сервис разворачивается в облаке, этот вариант быстро становится дорогим и неоптимальным.
При горизонтальном нагрузка распределяется на несколько машин
✅ Отказ одного экземпляра не так страшен
✅ Пределы масштабирования гораздо шире
❌ Нужна инфраструктура для поддержки
В книжке Art of Scalability приводится концепт scale cube и три вектора горизонтального масштабирования:
🔸Horizontal duplication: несколько копий одного сервиса
✅ Простая реализация, нам нужен только балансировщик
❌ Запутанный код
❌ Тяжело обновлять версию сервиса
❌ Большие и перегруженные кэши
🔸Functional decomposition: делим монолит на микросервисы
Каждый сервис отвечает за небольшой функционал
✅ Простой и понятный код каждого сервиса
✅ Более специфичные и эффективные кэши
❌ Поделить монолит на отдельные сервисы и наладить общение между ними - чудовищно непросто
❌ Сложная инфраструктура
🔸Data partitioning или шардирование
Сервисы одни и те же, но каждый экземпляр работает на ограниченном количестве данных и своём экземпляре БД
✅ Отказы БД менее критичны
✅ Кэширование работает отлично
❌ Нужен компонент для перенаправления запросов
❌ Сложная логика работы с данными
На практике три подхода комбинируются в разных пропорциях.
Что такое масштабирование? Какие виды вы знаете? - популярные вопросы на собеседованиях на позицию мидл и выше.
На практике нас волнует не абстрактная стратегия масштабирования, а вполне конкретный вопрос: как обеспечить тот же уровень сервиса, если нагрузка на систему вырастет.
Иногда достаточно просто поменять код. Например, добавить кэш и снизить нагрузку на БД. В итоге сервис обработает в 10 раз больше запросов, и этого улучшения хватит на несколько лет. Но это не масштабирование, а оптимизация.
При масштабировании считается, что с кодом всё отлично и мы упираемся в физические ресурсы. Процессор не справляется, память кончается и так далее. И вопрос становится более конкретным: как добавлять аппаратные ресурсы к текущей системе?
На самом верхнем уровне масштабирование делится на горизонтальное и вертикальное.
Вертикальное - запускаем сервис на более мощной машине. Даже если сервис разворачивается в облаке, этот вариант быстро становится дорогим и неоптимальным.
При горизонтальном нагрузка распределяется на несколько машин
✅ Отказ одного экземпляра не так страшен
✅ Пределы масштабирования гораздо шире
❌ Нужна инфраструктура для поддержки
В книжке Art of Scalability приводится концепт scale cube и три вектора горизонтального масштабирования:
🔸Horizontal duplication: несколько копий одного сервиса
✅ Простая реализация, нам нужен только балансировщик
❌ Запутанный код
❌ Тяжело обновлять версию сервиса
❌ Большие и перегруженные кэши
🔸Functional decomposition: делим монолит на микросервисы
Каждый сервис отвечает за небольшой функционал
✅ Простой и понятный код каждого сервиса
✅ Более специфичные и эффективные кэши
❌ Поделить монолит на отдельные сервисы и наладить общение между ними - чудовищно непросто
❌ Сложная инфраструктура
🔸Data partitioning или шардирование
Сервисы одни и те же, но каждый экземпляр работает на ограниченном количестве данных и своём экземпляре БД
✅ Отказы БД менее критичны
✅ Кэширование работает отлично
❌ Нужен компонент для перенаправления запросов
❌ Сложная логика работы с данными
На практике три подхода комбинируются в разных пропорциях.
Как вы оцениваете уровень бюрократии в вашей компании?
Anonymous Poll
38%
Вообще не ощущаю
37%
В целом норм, но иногда раздражает
14%
Всё долго, но я просто адаптирую планы под задержки
11%
Иногда трачу много сил на борьбу с системой
Тест на бюрократию
Недавно закончился набор на курс многопоточки, и захотелось написать пост про бюрократию.
Курс можно было оплатить со стороны работодателя, но в итоге сорвалось 4 заявки: за 2 недели процесс согласования даже не продвинулся. А где-то мы списались с HR и за один(!) день закончили весь процесс.
Откуда вообще берётся бюрократия?
Уровень бюрократии не всегда связан с размером компании и численностью сотрудников. Истоки бюрократии хорошо описаны в книге "Good to Great":
Задача бюрократии — компенсировать отсутствие компетентности и дисциплины; такая проблема не возникает, если с самого начала правильно подобраны кадры. Большинство компаний создают свои бюрократические порядки, чтобы управлять небольшим процентом «неправильных» людей на корабле. Это в свою очередь заставляет лучших специалистов уходить. Увеличивается процент посредственных специалистов, что опять-таки приводит к необходимости создания ещё более жесткой бюрократии, чтобы компенсировать некомпетентность и отсутствие дисциплины, это ещё больше отталкивает лучших и т.д.
По мне слишком категорично, но доля правды есть.
Как оценить уровень бюрократии уже на собеседовании?
Оставим в стороне кухонные разговоры и перейдём к практике. Каждому хочется работать в комфортной среде и не тратить много сил на бумажки и согласования.
Чтобы примерно оценить бюрократический накал, спросите на собеседовании - что нужно сделать, чтобы пройти обучение или поехать на конференцию?
В бюрократических компаниях вам придется:
😐 Писать заявки
😒 Вписаться в лист ожидания где-то в недрах конфлюенс
😠 Обосновать потребность
😤 Самостоятельно следить за процессом
😡 Заключить договор, по которому вы оплатите часть стоимости, если уволитесь в ближайшие два года
Если от вопроса начнут уходить - это плохой знак. В хорошей компании от вас требуется 1-2 пункта. Идеально, если вы просто обращаетесь к специальному человеку, который уведомит вас о результатах в разумные сроки.
Недавно закончился набор на курс многопоточки, и захотелось написать пост про бюрократию.
Курс можно было оплатить со стороны работодателя, но в итоге сорвалось 4 заявки: за 2 недели процесс согласования даже не продвинулся. А где-то мы списались с HR и за один(!) день закончили весь процесс.
Откуда вообще берётся бюрократия?
Уровень бюрократии не всегда связан с размером компании и численностью сотрудников. Истоки бюрократии хорошо описаны в книге "Good to Great":
Задача бюрократии — компенсировать отсутствие компетентности и дисциплины; такая проблема не возникает, если с самого начала правильно подобраны кадры. Большинство компаний создают свои бюрократические порядки, чтобы управлять небольшим процентом «неправильных» людей на корабле. Это в свою очередь заставляет лучших специалистов уходить. Увеличивается процент посредственных специалистов, что опять-таки приводит к необходимости создания ещё более жесткой бюрократии, чтобы компенсировать некомпетентность и отсутствие дисциплины, это ещё больше отталкивает лучших и т.д.
По мне слишком категорично, но доля правды есть.
Как оценить уровень бюрократии уже на собеседовании?
Оставим в стороне кухонные разговоры и перейдём к практике. Каждому хочется работать в комфортной среде и не тратить много сил на бумажки и согласования.
Чтобы примерно оценить бюрократический накал, спросите на собеседовании - что нужно сделать, чтобы пройти обучение или поехать на конференцию?
В бюрократических компаниях вам придется:
😐 Писать заявки
😒 Вписаться в лист ожидания где-то в недрах конфлюенс
😠 Обосновать потребность
😤 Самостоятельно следить за процессом
😡 Заключить договор, по которому вы оплатите часть стоимости, если уволитесь в ближайшие два года
Если от вопроса начнут уходить - это плохой знак. В хорошей компании от вас требуется 1-2 пункта. Идеально, если вы просто обращаетесь к специальному человеку, который уведомит вас о результатах в разумные сроки.
Почему в java нет Elvis оператора?
Элвис оператор ?: — популярная конструкция, которая используется в Groovy, Kotlin, C# и других языках, но не в java. В этом посте мы обсудим, почему так получилось и чем java отличается от Kotlin.
Часть 1: обзор операторов
Элвис оператор ?: используется следующим образом:
🔸 ?. (с точкой) — null-safe member selection operator
Все операторы неплохо соединяются между собой:
В java таких конструкций нет. Иногда это используют как аргумент того, что джава неудобная и несовременная.
На самом деле элвис оператор мог появиться уже в java 7. Пользователи java 5 называли элвис самой ожидаемой фичей. Но он не попал в итоговый релиз.
Давайте разберём, почему в java элвис оператор так и не появился, а в котлине, другом JVM языке, он есть.
Часть 2: зачем нужен null
Цель элвис оператора — облегчить работу с null. Значение null в джава коде выступает в двух ролях:
1️⃣ Ожидаемое значение и часть бизнес-логики
Поле необязательное или ещё не готово. Разработчик знает об этом заранее и учитывает в коде.
Элвис оператор прекрасно подходит для этого сценария.
2️⃣ Индикатор ошибки
Что-то пошло не так: пропущен обязательный параметр, соединение с БД оборвалось, инициализация не завершилась. Получаем NullPointerException, разбираемся с причинами и устраняем ошибку.
Здесь элвис оператор замаскирует проблему и сделает только хуже. Ошибка прикроется значением по умолчанию и пройдёт мимо нас.
Часть 3: Kotlin
Котлин использует другой подход: по умолчанию переменные НЕ могут быть null. Свойство nullability задаётся явно — через вопросик:
Часть 4: Optional
В java 8 появился способ явно указать, что значение может быть пустым — класс Optional. Метод orElse выполняет функции элвис оператора: если внутри Optional ничего нет, то возвращается значение внутри orElse.
Итог
Во времена java 5 ситуация была сложной: null мог означать и ошибку, и нормальное значение. Чтобы не провоцировать скрытые ошибки, комьюнити отклонило добавление элвис оператора.
После появления Optional нулл в коде почти всегда указывает на ошибку. Использовать элвис оператор в такой ситуации - нецелесообразно, поэтому маловероятно, что он появится в будущих версиях java.
Элвис оператор ?: — популярная конструкция, которая используется в Groovy, Kotlin, C# и других языках, но не в java. В этом посте мы обсудим, почему так получилось и чем java отличается от Kotlin.
Часть 1: обзор операторов
Элвис оператор ?: используется следующим образом:
String src = …src равен null, то
String x = src ?: "default";
▫️Если
x = "default"
▫️Если src НЕ равен null, то x = src
По сути это сокращённая форма тернарного оператора:String x = src==null ? src : "default"Есть два оператора, которые похожи на элвис:
🔸 ?. (с точкой) — null-safe member selection operator
str?.toString()= если
str
не равен null, то вызвать метод toString()
🔸 ?[] — null-safe array access lines?[6]= если массив
lines
существует, то прочитать 6 элементВсе операторы неплохо соединяются между собой:
String aMember = g?.members?[0]?.name ?: "nobody";Но элвис оператор это только ?:
В java таких конструкций нет. Иногда это используют как аргумент того, что джава неудобная и несовременная.
На самом деле элвис оператор мог появиться уже в java 7. Пользователи java 5 называли элвис самой ожидаемой фичей. Но он не попал в итоговый релиз.
Давайте разберём, почему в java элвис оператор так и не появился, а в котлине, другом JVM языке, он есть.
Часть 2: зачем нужен null
Цель элвис оператора — облегчить работу с null. Значение null в джава коде выступает в двух ролях:
1️⃣ Ожидаемое значение и часть бизнес-логики
Поле необязательное или ещё не готово. Разработчик знает об этом заранее и учитывает в коде.
Элвис оператор прекрасно подходит для этого сценария.
2️⃣ Индикатор ошибки
Что-то пошло не так: пропущен обязательный параметр, соединение с БД оборвалось, инициализация не завершилась. Получаем NullPointerException, разбираемся с причинами и устраняем ошибку.
Здесь элвис оператор замаскирует проблему и сделает только хуже. Ошибка прикроется значением по умолчанию и пройдёт мимо нас.
Часть 3: Kotlin
Котлин использует другой подход: по умолчанию переменные НЕ могут быть null. Свойство nullability задаётся явно — через вопросик:
var str: String? = "abc"Все null значения под чётким контролем. Поэтому элвис оператор не скрывает ошибок, а помогает писать лаконичный код. Захотели nullability — чётко это обозначили. Хотим избавиться от этого свойства — используем элвис оператор.
Часть 4: Optional
В java 8 появился способ явно указать, что значение может быть пустым — класс Optional. Метод orElse выполняет функции элвис оператора: если внутри Optional ничего нет, то возвращается значение внутри orElse.
Итог
Во времена java 5 ситуация была сложной: null мог означать и ошибку, и нормальное значение. Чтобы не провоцировать скрытые ошибки, комьюнити отклонило добавление элвис оператора.
После появления Optional нулл в коде почти всегда указывает на ошибку. Использовать элвис оператор в такой ситуации - нецелесообразно, поэтому маловероятно, что он появится в будущих версиях java.
Теперь заменим for на forEach. Сколько элементов выведется в консоль?
Разница между for и forEach
довольно проста. for использует для обхода итератор, а forEach - траверс.
В этом посте разберём разницу между ними. Цель — чисто познавательная: как компьютер саенс проявляется в языке программирования.
Сразу скажу: всё это имеет смысл только для обхода многопоточных структур данных. Если мы попробуем изменить ArrayList, HashMap и т.д во время обхода, то получим ConcurrentModificationException.
Начнём издалека и посмотрим на обход более сложной сущности — графа. Граф состоит из вершин и рёбер, в нём могут быть циклы.
Обойти все вершины графа по одному разу (найти гамильтонов путь) — непростая задача. Алгоритм ведёт учёт посещённых вершин и перебирает множество вариантов. В computer science обход структуры данных называется traverse.
🔸Самое важное: следующий элемент вычисляется только, когда понадобится.
Вернёмся в мир java. В стандартных структурах царят списки, множества, очереди и деревья. Циклов здесь нет, следующий элемент при обходе однозначен, а его отсутствие означает конец работы. Поэтому обход выглядит так:
Итератор лежит в основе синтаксиса for (T e: collection)
🔸Самое важное: указатель на следующий элемент вычисляется заранее.
Возможный минус: если мы удалили или поменяли элемент, который должен вывестись следующим, то итератор не подхватит изменений и выведет старое значение.
Что по задаче:
Обход через for использует итератор. Указатель на следующий элемент вычисляется заранее. Более подходящий новый элемент не отображается, и выводится 2 элемента.
Метод forEach использует траверс и вычисляет следующий элемент только когда он запрашивается. Поэтому новый ключ подхватывается, и в консоль попадают 3 элемента.
❓Почему нельзя использовать траверс по умолчанию?
— Итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.
❓Зачем нужно несколько вариантов?
— ConcurrentHashMap может перестраиваться во время обхода. Чтобы во время перестройки не выводить дубликаты, используется траверс со сложной логикой.
Итого: при выводе элементов ConcurrentHashMap через for и forEach используются разные алгоритмы обхода, поэтому результат вывода тоже разный.
❗️Для однопоточных коллекций между for и forEach нет никакой разницы, в обоих случаях используется итератор.
довольно проста. for использует для обхода итератор, а forEach - траверс.
В этом посте разберём разницу между ними. Цель — чисто познавательная: как компьютер саенс проявляется в языке программирования.
Сразу скажу: всё это имеет смысл только для обхода многопоточных структур данных. Если мы попробуем изменить ArrayList, HashMap и т.д во время обхода, то получим ConcurrentModificationException.
Начнём издалека и посмотрим на обход более сложной сущности — графа. Граф состоит из вершин и рёбер, в нём могут быть циклы.
Обойти все вершины графа по одному разу (найти гамильтонов путь) — непростая задача. Алгоритм ведёт учёт посещённых вершин и перебирает множество вариантов. В computer science обход структуры данных называется traverse.
🔸Самое важное: следующий элемент вычисляется только, когда понадобится.
Вернёмся в мир java. В стандартных структурах царят списки, множества, очереди и деревья. Циклов здесь нет, следующий элемент при обходе однозначен, а его отсутствие означает конец работы. Поэтому обход выглядит так:
Iterator it=list.iterator();Метод next возвращает текущий элемент и сдвигает указатель на следующий. Метод hasNext проверяет, ссылается ли этот указатель куда-нибудь. Этот паттерн повторяется снова и снова и называется Iteration.
while(it.hasNext())
int result = it.next();
Итератор лежит в основе синтаксиса for (T e: collection)
🔸Самое важное: указатель на следующий элемент вычисляется заранее.
Возможный минус: если мы удалили или поменяли элемент, который должен вывестись следующим, то итератор не подхватит изменений и выведет старое значение.
Что по задаче:
Обход через for использует итератор. Указатель на следующий элемент вычисляется заранее. Более подходящий новый элемент не отображается, и выводится 2 элемента.
Метод forEach использует траверс и вычисляет следующий элемент только когда он запрашивается. Поэтому новый ключ подхватывается, и в консоль попадают 3 элемента.
❓Почему нельзя использовать траверс по умолчанию?
— Итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.
❓Зачем нужно несколько вариантов?
— ConcurrentHashMap может перестраиваться во время обхода. Чтобы во время перестройки не выводить дубликаты, используется траверс со сложной логикой.
Итого: при выводе элементов ConcurrentHashMap через for и forEach используются разные алгоритмы обхода, поэтому результат вывода тоже разный.
❗️Для однопоточных коллекций между for и forEach нет никакой разницы, в обоих случаях используется итератор.
Кого вы в вашей команде добавляете в ревьюеры?
Anonymous Poll
19%
Всю команду
29%
Всех разработчиков, в том числе джуниоров и новичков
30%
Разработчиков, которые могут оставить дельные комментарии
5%
Коллег, которые точно поставят аппрув
17%
Я один / у нас post-commit-review / необязательное ревью / другой экзотичный вариант
Надо ли добавлять джуна в ревьюеры?
Во многих командах такой вопрос даже не стоит. Понятно, что новичок
▪️ мало разбирается в предметной области
▪️ плохо ориентируется в коде проекта
А значит ничего не поймёт, ничего не скажет, и нет смысла его добавлять в ревьюеры.
Даже если вы сеньор, дать джуниору посмотреть пул-реквест - хорошая идея. И тому есть четыре причины:
1️⃣ Проверка читаемости
Применяете SOLID, следуете принципам clean code, но понять вас способен только разработчик с 10-летним опытом? Что-то тут не так🙂
Возможно вы пишете запутанную дичь в стиле 2000-х, пул-реквест занимает 50 файлов, вы вышли за пределы задачи и заодно провернули сложный рефакторинг. Это сильно усложняет понимание кода. Но коллеги-сеньоры привыкли к вашему стилю, поэтому не жалуются.
А вот если изменения понятны даже новичку, значит вы написали действительно читаемый и понятный код. Чтобы помочь начинающим ревьюерам, напишите саммари пул-реквеста. 3-5 предложений: в чём задача, главная проблема и суть решения.
2️⃣ Быстрее получаете фидбэк
Не только джуниоры, но и другие коллеги быстрее поймут контекст и суть изменений. Быстрее дадут фидбэк или поставят аппрув.
3️⃣ Многоуровневый фидбэк
Каждый сделает ваш код лучше:
🔸 Джуниор заметит опечатку в названии переменной
🔸 Мидл подскажет удобный метод из java 11
🔸 Сеньор укажет на непокрытый кейс
🔸 Тимлид заметит несоответствие корпоративным стандартам
4️⃣ Обмен знаниями
Материалы в разделе онбординга устаревают. Пул-реквесты показывают то, что происходит здесь и сейчас. Код-ревью быстро вводит нового сотрудника в курс дела:
🔹 Над чем вообще работает команда
🔹 Как писать код и как его оформлять
🔹 Какие тесты и в каком объёме писать
🔹 Как работать с БД и накатывать апдейты
🔹 Что обновлять в документации и CI
Чем быстрее новички вольются в проект, тем быстрее возьмут на себя ваши задачки.
Во многих командах такой вопрос даже не стоит. Понятно, что новичок
▪️ мало разбирается в предметной области
▪️ плохо ориентируется в коде проекта
А значит ничего не поймёт, ничего не скажет, и нет смысла его добавлять в ревьюеры.
Даже если вы сеньор, дать джуниору посмотреть пул-реквест - хорошая идея. И тому есть четыре причины:
1️⃣ Проверка читаемости
Применяете SOLID, следуете принципам clean code, но понять вас способен только разработчик с 10-летним опытом? Что-то тут не так🙂
Возможно вы пишете запутанную дичь в стиле 2000-х, пул-реквест занимает 50 файлов, вы вышли за пределы задачи и заодно провернули сложный рефакторинг. Это сильно усложняет понимание кода. Но коллеги-сеньоры привыкли к вашему стилю, поэтому не жалуются.
А вот если изменения понятны даже новичку, значит вы написали действительно читаемый и понятный код. Чтобы помочь начинающим ревьюерам, напишите саммари пул-реквеста. 3-5 предложений: в чём задача, главная проблема и суть решения.
2️⃣ Быстрее получаете фидбэк
Не только джуниоры, но и другие коллеги быстрее поймут контекст и суть изменений. Быстрее дадут фидбэк или поставят аппрув.
3️⃣ Многоуровневый фидбэк
Каждый сделает ваш код лучше:
🔸 Джуниор заметит опечатку в названии переменной
🔸 Мидл подскажет удобный метод из java 11
🔸 Сеньор укажет на непокрытый кейс
🔸 Тимлид заметит несоответствие корпоративным стандартам
4️⃣ Обмен знаниями
Материалы в разделе онбординга устаревают. Пул-реквесты показывают то, что происходит здесь и сейчас. Код-ревью быстро вводит нового сотрудника в курс дела:
🔹 Над чем вообще работает команда
🔹 Как писать код и как его оформлять
🔹 Какие тесты и в каком объёме писать
🔹 Как работать с БД и накатывать апдейты
🔹 Что обновлять в документации и CI
Чем быстрее новички вольются в проект, тем быстрее возьмут на себя ваши задачки.
IDEA training
Я часто пишу посты про фичи Intellij IDEA, и такие посты всегда пользуются бешеной популярностью. IDEA классная, но мало кто знает обо всех возможностях.
Я уже писала, как выучить полезные именно вам шорткаты через Productivity Guide или плагин Key Promoter X.
Более основательный способ - это плагин IDE Features Trainer.
Это целый мини-курс, который охватывает написание кода, рефакторинг, навигацию и тд. Для каждой темы открывается учебный проект, и вам по шагам рассказывают, что и куда нажимать🥰
Как поставить: File → Settings → Plugins → MarketPlace → IDE Features Trainer → Install
Потом идёте в Help → IDE Features Trainer
и следуете инструкциям.
Если вы новичок или редко пользуетесь горячими клавишами, но очень хотите начать, то это лучший вариант🔥
Я часто пишу посты про фичи Intellij IDEA, и такие посты всегда пользуются бешеной популярностью. IDEA классная, но мало кто знает обо всех возможностях.
Я уже писала, как выучить полезные именно вам шорткаты через Productivity Guide или плагин Key Promoter X.
Более основательный способ - это плагин IDE Features Trainer.
Это целый мини-курс, который охватывает написание кода, рефакторинг, навигацию и тд. Для каждой темы открывается учебный проект, и вам по шагам рассказывают, что и куда нажимать🥰
Как поставить: File → Settings → Plugins → MarketPlace → IDE Features Trainer → Install
Потом идёте в Help → IDE Features Trainer
и следуете инструкциям.
Если вы новичок или редко пользуетесь горячими клавишами, но очень хотите начать, то это лучший вариант🔥
Лямбды и разрыв шаблона
Лямбда-выражение - анонимная функция, которую можно передавать в методы как параметр:
Очевидный ответ - в краткости. Раньше чтобы "передать поведение" нужен был отдельный или анонимный класс, а теперь есть переменная с анонимной функцией. Удобно и понятно.
Но у лямбд есть ещё одно интересное свойство. Посмотрим на него на примере обхода коллекций.
В далёкие времена обойти коллекцию можно было двумя способами:
▫️С помощью цикла
▫️Через итератор
Этот подход называется external iteration или внешний обход. Последовательно идём по элементам коллекции и применяем к ним некоторую логику. С коллекцией общаемся через интерфейс. Главный путь оптимизации - оптимизация логики обработки.
Лямбда-выражения поменяли сценарий работы. Теперь мы как бы передаём функцию внутрь структуры данных. Структура данных сама решает, как применить функцию исходя из деталей своей реализации.
Теперь уже логика обработки скрыта за функциональным интерфейсом, а структура данных ей пользуется. Это называется internal iteration или внутренний обход.
Итого у нас разрыв шаблона. Двадцать лет обход коллекций шёл по сценарию
🔸Логика обработки - ведущий игрок, все оптимизации здесь
🔸Структура данных - абстракция, скрыта за интерфейсом
С появлением лямбда выражений и Stream API субъект и объект поменялись местами:
🔹Логика обработки доступна через интерфейс
🔹Структура данных - главная, оптимизации происходят на этом уровне
Теперь работу над коллекцией легко делить между потоками. Раньше это было невозможно - логика обработки могла быть любой и параллелить в каждом случае нужно по-разному. Деление на подзадачи на уровне структуры данных гораздо проще, и теперь у нас есть библиотечный метод parallel() в Stream API.
Понятно, что такой подход применим не только к обходу элементов в коллекции, но и к взаимодействию любых компонентов в целом. Смена ролей часто приводит к свежему взгляду на привычные действия. И в жизни, и в написании кода🙂
Лямбда-выражение - анонимная функция, которую можно передавать в методы как параметр:
Function<Integer,Integer> sum = (a,b) → a+b;В чём преимущество лямбда-выражений?
Очевидный ответ - в краткости. Раньше чтобы "передать поведение" нужен был отдельный или анонимный класс, а теперь есть переменная с анонимной функцией. Удобно и понятно.
Но у лямбд есть ещё одно интересное свойство. Посмотрим на него на примере обхода коллекций.
В далёкие времена обойти коллекцию можно было двумя способами:
▫️С помощью цикла
▫️Через итератор
Этот подход называется external iteration или внешний обход. Последовательно идём по элементам коллекции и применяем к ним некоторую логику. С коллекцией общаемся через интерфейс. Главный путь оптимизации - оптимизация логики обработки.
Лямбда-выражения поменяли сценарий работы. Теперь мы как бы передаём функцию внутрь структуры данных. Структура данных сама решает, как применить функцию исходя из деталей своей реализации.
Теперь уже логика обработки скрыта за функциональным интерфейсом, а структура данных ей пользуется. Это называется internal iteration или внутренний обход.
Итого у нас разрыв шаблона. Двадцать лет обход коллекций шёл по сценарию
🔸Логика обработки - ведущий игрок, все оптимизации здесь
🔸Структура данных - абстракция, скрыта за интерфейсом
С появлением лямбда выражений и Stream API субъект и объект поменялись местами:
🔹Логика обработки доступна через интерфейс
🔹Структура данных - главная, оптимизации происходят на этом уровне
Теперь работу над коллекцией легко делить между потоками. Раньше это было невозможно - логика обработки могла быть любой и параллелить в каждом случае нужно по-разному. Деление на подзадачи на уровне структуры данных гораздо проще, и теперь у нас есть библиотечный метод parallel() в Stream API.
Понятно, что такой подход применим не только к обходу элементов в коллекции, но и к взаимодействию любых компонентов в целом. Смена ролей часто приводит к свежему взгляду на привычные действия. И в жизни, и в написании кода🙂
Во что превращаются лямбда-выражения при компиляции?
Anonymous Poll
71%
В объекты анонимного класса
7%
В объекты внутреннего класса
9%
Инлайнятся в код
8%
В статические методы
6%
В обычные методы
Как НЕ реализованы лямбда-выражения
Давно хотела написать о том, как выглядят лямбды внутри JVM.
Во-первых, мало кто об этом знает. Есть несколько статей и видео с конференций, но они достаточно сложные.
Во-вторых, это интересно. Для ответа на вопрос нужно представлять, как работает JVM и собрать вместе несколько концептов.
В-третьих, время не стоит на месте. Java развивается, а многие до сих пор не в курсе изменений 10летней давности.
Как устроены лямбды - вопрос непростой, поэтому будем идти небольшими шагами. В этом посте посмотрим на возможные реализации лямбд и обсудим, что с ними не так.
Часть 1: внешний вид. Как лямбды будут выглядеть в коде?
Первая догадка - использовать опыт других языков и ввести в JVM тип function:
Второй вариант: пусть лямбда реализует интерфейс с одним методом:
Но какой объект будет присвоен переменной p?
Часть 2: реализация
Здесь тоже два варианта.
Первый - сделать внутренний класс с одним методом и создать его экземпляр.
✅ Простая реализация
❌ Придётся компилировать и загружать класс для каждой лямбды, а при каждом вызове создавать новый объект.
С учётом того, что лямбды станут основой Stream API и использоваться для обработки данных - решение ужасное и непроизводительное.
Второй вариант - компилировать код лямбды в отдельный метод и подставлять в исходный код MethodHandle.
Мы рассмотрим MethodHandle на следующей неделе, но если кратко - это указатель на метод. Компилируем исходный пример в:
❌ Теряется информация о входных и выходных типах, приходится проверять их в рантайме
❌ Библиотечные методы не принимают на вход MethodHandle
Как же сделаны лямбды?
Об этом поговорим на следующей неделе:
▫️ Сначала вернёмся во времена java 6 и посмотрим как вызываются методы в JVM
▫️ Изучим новую байткод инструкцию java 7 и чуть глубже обсудим MethodHandle
После этого говорить про лямбды будет легко и понятно.
Давно хотела написать о том, как выглядят лямбды внутри JVM.
Во-первых, мало кто об этом знает. Есть несколько статей и видео с конференций, но они достаточно сложные.
Во-вторых, это интересно. Для ответа на вопрос нужно представлять, как работает JVM и собрать вместе несколько концептов.
В-третьих, время не стоит на месте. Java развивается, а многие до сих пор не в курсе изменений 10летней давности.
Как устроены лямбды - вопрос непростой, поэтому будем идти небольшими шагами. В этом посте посмотрим на возможные реализации лямбд и обсудим, что с ними не так.
Часть 1: внешний вид. Как лямбды будут выглядеть в коде?
Первая догадка - использовать опыт других языков и ввести в JVM тип function:
fun(s: Integer): boolean { return s<3; }Легко для понимания, сложно для реализации. Java - ООП язык, всё в JVM заточено под работу объектов и примитивов. Добавить новый тип данных - чудовищно огромная работа. Как функция будет хранится, вызываться и взаимодействовать с другими объектами? Будет ли она работать с дженериками? Тысячи вопросов и сложностей.
Второй вариант: пусть лямбда реализует интерфейс с одним методом:
Predicate p = i -> i<3;Выглядит по-джавовски и отлично впишется в текущую систему. Существующие библиотеки будут работать с лямбдами по умолчанию.
Но какой объект будет присвоен переменной p?
Часть 2: реализация
Здесь тоже два варианта.
Первый - сделать внутренний класс с одним методом и создать его экземпляр.
✅ Простая реализация
❌ Придётся компилировать и загружать класс для каждой лямбды, а при каждом вызове создавать новый объект.
С учётом того, что лямбды станут основой Stream API и использоваться для обработки данных - решение ужасное и непроизводительное.
Второй вариант - компилировать код лямбды в отдельный метод и подставлять в исходный код MethodHandle.
Мы рассмотрим MethodHandle на следующей неделе, но если кратко - это указатель на метод. Компилируем исходный пример в:
public boolean lambda(Integer i) { return i<3; }И подставляем mh в исходный код:
MethodHandle mh = LDC["lambda"];
...stream().filter(mh)...В теории решение отличное, на практике - неподходящее.
❌ Теряется информация о входных и выходных типах, приходится проверять их в рантайме
❌ Библиотечные методы не принимают на вход MethodHandle
Как же сделаны лямбды?
Об этом поговорим на следующей неделе:
▫️ Сначала вернёмся во времена java 6 и посмотрим как вызываются методы в JVM
▫️ Изучим новую байткод инструкцию java 7 и чуть глубже обсудим MethodHandle
После этого говорить про лямбды будет легко и понятно.