Java Guru 🤓
13.3K subscribers
924 photos
15 videos
779 links
Канал с вопросами и задачами с собеседований!

По сотрудничеству и рекламе: @NadikaKir

Канал в перечне РКН: https://vk.cc/cJrSQZ

Мы на бирже: telega.in/channels/javatasks/card?r=lcDuijdm
Download Telegram
Что происходит внутри HashMap.put()?

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

1. Вычисляется хэш ключа. Если ключ null, хэш считается равным 0. Чтобы достичь лучшего распределения, результат вызова hashCode() «перемешивается»: его старшие биты XOR-ятся на младшие.

2. Значения внутри хэш-таблицы хранятся в специальных структурах данных – нодах, в массиве. Из хэша высчитывается номер бакета – индекс для значения в этом массиве. Полученный хэш обрезается по текущей длине массива. Длина – всегда степень двойки, так что для скорости используется битовая операция &.

3. В бакете ищется нода. В ячейке массива лежит не просто одна нода, а связка всех нод, которые туда попали. Исполнение проходит по этой связке (цепочке или дереву), и ищет ноду с таким же ключом. Ключ сравнивается с имеющимися сначала на ==, затем на equals.

4. Если нода найдена – её значение просто заменяется новым. Работа метода на этом завершается.

5. Если ноды с таким же ключом в бакете пока нет – добавляемая пара ключ-значение запаковывается в новый объект типа Node, и прикрепляется к структуре существующих нод бакета. Ноды составляют структуру за счет того, что в ноде хранится ссылка на следующий элемент (для дерева – следующие элементы). Кроме самой пары и ссылок, чтобы потом не считать заново, записывается и хэш ключа.
6. В случае, когда структурой была цепочка а не дерево, и длина цепочки превысила 7 элементов – происходит процедура treeification – превращение списка в самобалансирующееся дерево. В случае коллизии это ускоряет доступ к элементам на чтение с O(n) до O(log(n)). У comparable-ключей для балансировки используется их естественный порядок. Другие ключи балансируются по порядку имен их классов и значениям identityHashCode-ов. Для маленьких хэш-таблиц (< 64 бакетов) «одеревенение» заменяется увеличением (см. п.8).

7. Если новая нода попала в пустую ячейку, заняла новый бакет – увеличивается счетчик структурных модификаций. Изменение этого счетчика сообщит всем итераторам контейнера, что при следующем обращении они должны выбросить ConcurrentModificationException.

8. Когда количество занятых бакетов массива превысило пороговое (capacity * load factor), внутренний массив увеличивается вдвое, а для всего содержимого выполняется рехэш – все имеющиеся ноды перераспределяются по бакетам по тем же правилам, но уже с учетом нового размера.
13👍7🥰3🤨3
Что происходит внутри TreeMap.put()?

Недавно мы в деталях рассматривали, какие процессы происходят при добавлении элемента в HashMap. Теперь поговорим о TreeMap. Здесь не так много тонкостей, как в хэш-таблице.

TreeMap требует либо задать порядок ключей вручную (передать в конструктор Comparator), либо чтобы они имели собственный естественный порядок (были Comparable).

Подобно нодам в хэш-таблице, внутренняя структура дерева строится из объектов внутреннего класса узла – Entry. В каждом узле хранится информация о данных (пара key-value), и о положении в структуре (ссылки на родительский узел, левую и правую ветви).

Сама структура представляет из себя красно-чёрное дерево относительно ключей. Не будем здесь углубляться в детали его реализации. О нем важно знать два факта:

1. Это бинарное дерево поиска. Значит, каждый новый элемент начинает искать свое место в дереве, сравниваясь с узлами начиная с корневого. Меньшие элементы движутся влево, большие – вправо. Для этого и требуется наличие метода compare. Дойдя до конца, пара ключ-значение «повисает» новым узлом.

2. Это самобалансирующееся дерево. Если какая-то ветка начинает становиться слишком длинной (а её эффективность вырождаться в эффективность связного списка), происходит балансировка. В результате этой операции правило из пунтка 1 остается в силе, но нагрузка на ветки перераспределяется. Самое длинное поддерево становится выше самого короткого максимум на один элемент.
👍12🥰5🔥3
⚡️ Узнайте, как манипулировать байт кодом Java-приложений!

На занятии разберем, как просматривать и читать байт код классов и для чего это может быть нужно. Научитесь создавать утилиты для изменения байт кода существующих классов в runtime. Это знание поможет вам улучшить производительность ваших приложений и решать сложные задачи.

Вебинар будет полезен Java-разработчикам, которые сталкиваются с вопросами производительности приложений.

Встречаемся 1 августа в 20:00 мск.

Спикер — Tech Lead в одном из крупнейших российских банков и кандидат технических наук.

👉 Регистрируйтесь прямо сейчас, чтобы посетить бесплатное занятие: https://vk.cc/cyBaHQ

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥21
Можно ли хранить null в стандартных коллекциях?

Все интерфейсы Collections Framework позволяют своим реализациям самостоятельно решать, поддерживать ли null-значения. Если реализация не может принять null, она выбрасывает NullPointerException или ClassCastException.

Большинство списков (LinkedList, ArrayList) принимают null без проблем. Большинство очередей (Queue и Deque) не хранят null – возвращая из читающего метода null они сообщают пользователю о пустоте коллекции.

Unmodifiable Maps не допускают null-ов совсем. Обычные изменяемые мапы обычно не испытывают трудности со значениями null. А вот с ключами дело обстоит интереснее.

HashMap не может посчитать hash-сумму от null. Но вместо этого для таких ключей просто используется бакет номер 0.

Иногда этот вопрос дается как задача с подвохом про TreeMap. Nullability её ключей зависит от готовности к этому компаратора. Натуральный порядок (который работает для Comparable ключей) не поддерживает null. Раньше в реализации был баг, который позволял положить значение по ключу null в корень дерева без выброса исключения.

Для значений Set-ов действуют те же правила, что для ключей лежащих в основе их Map-ов.
👍11🔥3🥰3
👍 Тест по Java

Проверь насколько хорошо ты знаешь Java и готов освоить Spring!

Ответишь — пройдешь на продвинутый курс "Разработчик на Spring Framework" от OTUS по специальной цене.

👉 ПРОЙТИ ТЕСТ: https://vk.cc/cyBK7h

Время прохождения теста ограничено 30 минут, 21 вопрос.

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1🎉1
Какие есть преимущества у массива перед коллекцией?

Для хранения ссылочных типов массив подходит хуже чем ArrayList. В основе реализации коллекции лежит такой же массив, поэтому эффективность будет той же самой. Однако, вам придется самостоятельно реализовывать логику управления хранилищем: например, увеличение массива при переполнении. А значит, будет больше шансов на ошибку.

Если использовать массивы вместо коллекций для примитивов, можно получить выигрыш по эффективности. Коллекции – generic-типы, из-за этого простые значения хранятся в них в форме ссылочных типов-оберток.

1. Autoboxing выделяет память под новый объект, это дорогая операция;
2. Кроме данных, Object занимает дополнительную память под метаинформацию;
3. Ячейки массива лежат близко в оперативной памяти, это увеличивает шансы попадания в кэш процессора.

С другой стороны, для массива всё так же нужно написать больше кода, он сложнее. Поэтому замена листов на массивы обычно считается излишней микрооптимизацией.

Когда сэкономить всё-таки хочется, стоит выбрать одну из множества готовых библиотек не-generic реализаций коллекций. Списки примитивов можно найти в Eclipse Collections. В Android есть HashMap с целочисленными ключами – SparseArray.
👏17🔥2
Как обойти коллекцию?

for/while. Классический способ: целочисленная переменная-индекс, которая увеличивается от 0 до size(). Можно использовать для неполного обхода, с нестандартным шагом. Плата за это – возможность ошибиться в индексах и менее читабельный код.

Iterator. ООП-способ: методом iterator() получить объект-итератор, и вызывать у него next() пока hasNext() возвращает true. В реализации может быть дополнительная логика, такая как потокобезопасность. Такой «объект-итерацию» коллекции можно передать в сторонний код, не отдавая саму коллекцию. Всё еще требует слишком много кода.

for Iterable. Синтаксический сахар для обхода итератором. Простейший синтаксис когда нужен просто обход. В отличие от явного использования итератора не дает возможности модифицировать элементы в процессе.

Стримы. Создать от коллекции стрим и работать с элементами в нём. Кроме простого forEach(), можно воспользоваться всей мощью Java Steam API – фильтровать, преобразовывать и агрегировать элементы. За это создаются лишние объекты, а синтаксис гораздо более развесистый.

Функции Java 8. С этой версии появились удобные средства для обхода не только строк. У коллекций и хэш-таблиц добавились методы forEach для обхода и replaceAll для модификации. Как со стримами, они дают функциональный стиль, но без избыточного создания стримов. Внутри используются простые итераторы и циклы for.
👍14🔥51
👩‍💻 Курс для Java-разработчиков, которые хотят профессионального роста.

Пройди тест по Java и проверь свои знания, готов ли ты к обучению на курсе.

Ответишь — пройдешь на продвинутый курс "Java Developer. Professional" от OTUS по специальной цене + получишь доступ к записям открытых уроков курса

➡️ ПРОЙТИ ТЕСТ: https://vk.cc/cyCGJ8

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
Что такое fail-fast и fail-safe итераторы?

Это не какие-то отдельные типы, а характеристики разных реализаций интерфейса Iterator. Они определяют, как поведет себя итератор при изменении перебираемой последовательности.

Fail-fast – «быстрый» итератор. Когда после его создания коллекция как-либо изменилась, он падает с ошибкой без лишних разбирательств. Так работает итератор класса ArrayList, при изменении он выбрасывает ConcurrentModificationException. Рекомендуется не основывать логику программы на fail-fast отказах, и использовать их только как признак ошибки реализации.

Fail-safe – «умный» итератор. Обычно плата за отказоустойчивость – возможная неконсистентность данных («слабая консистентность»). Итератор класса ConcurrentHashMap работает с копией данных, он не выбросит исключение при изменении коллекции, но может не увидеть часть свежих изменений. Плата за отсутствие ошибок других fail-safe итераторов может отличаться, детали всегда можно найти в документации коллекций.
👍12👏3🔥2
Какие существуют примитивы?

В Java имеется 9 возможных типов значения переменной: ссылка на объект или один из восьми примитивных типов:
🔘 byte – знаковое целое число от -2^7 до 2^7-1;
🔘 short – знаковое целое число от -2^15 до 2^15-1;
🔘 int – знаковое целое число от -2^31 до 2^31-1;
🔘 long – знаковое целое число от -2^63 до 2^63-1;
🔘 float – знаковое число с плавающей точкой 32 бита стандарта IEEE 754;
🔘 double – то же, что и float, но 64 бита;
🔘 char – 16-битный символ Unicode, от '\u0000'(0) до '\uffff'(65535);
🔘 boolean – true или false;

По умолчанию поля примитивных типов принимают нулевые значения: 0, 0L, '\u0000', false. Про особенности работы, способ хранения и специальные значения чисел с плавающей точкой стоит почитать подробнее.

Отдельная интересная тема – boxing/unboxing. Каждый примитивный тип снабжен своей ссылочной версией. Примитивное значение заворачивается и разворачивается из него автоматически при необходимости. Это может приводить к большим затратам на выделение памяти, когда например int индекс цикла используется в качестве значения переменной Object и превращается в Integer без нужды – частая задача на собеседованиях. Еще классы-обертки содержат набор утилитарных методов для их примитивов.

Сколько памяти занимает примитив – вопрос с подвохом. Спецификация требует, чтобы размер был достаточным для всех значений. Конкретный размер определяется реализацией JVM, он может быть больше. Например в 64-bit HotSpot переменная boolean занимает не 1 бит, а 8.
👍14🔥1
⚡️Ваши программы страдают от утечек памяти и медленной работы из-за традиционной сборки мусора?

А теперь представьте, что ваши программы работают быстрее и эффективнее. Вы освоили правила владения и заимствования в Rust, научились безопасно использовать ссылки и управлять временем жизни объектов.

Ваш код стал более надежным и производительным!

🧑‍💻Присоединяйтесь к открытому вебинару «Как Rust управляет ресурсами без сборки мусора?» 1 августа в 20:00 мск и сделайте этот прыжок в будущее!

🎯 Мы рассмотрим правила владения и заимствования, посмотрим примеры кода и разберёмся, зачем в язык добавили явные лайфтаймы.

🧑‍💻Урок будет полезен разработчикам, которые хотят познакомиться с Rust или углубить свои знания об управлении ресурсами в этом языке. Вебинар подходит как для новичков, так и для опытных разработчиков.

Спикер Кирилл Федченко — опытный разработчик на нескольких языках.

👉Регистрируйтесь прямо сейчас, чтобы не пропустить мероприятие: https://vk.cc/cyM00r
👍8👏31🔥1😁1
🚀 Заканчивается набор на открытый урок "Java Generics для автоматизации тестирования"! 🚀

Стартуем 30 июля в 20:00 по мск.  Успейте попасть в группу! 

Присоединяйтесь к нашему открытому уроку по Java Generics от OTUS, ведущего образовательного центра с более чем 130 авторскими курсами для IT-специалистов. 

Открытый урок проходит в рамках курса Java QA Engineer. Professional. Для разработчиков автоматизированного тестирования на Java. Прокачайте профессиональные навыки, углубите и систематизируйте знания, научитесь грамотно внедрять автоматизацию в проекты.

👉 Что Вы узнаете на уроке?

Что такое Java Generics?
Вы разберетесь в основах Java Generics, узнаете, что это такое и зачем они нужны. Поймете, как использование Generics может повысить безопасность и эффективность вашего кода.

Использование Java Generics в UI автоматизации тестирования
Вы изучите применение Java Generics в UI автоматизации тестирования. Узнаете, как Generics помогают создавать более гибкие и переиспользуемые тесты для пользовательского интерфейса, что значительно упростит поддержку автоматизации.

Использование Java Generics в API автоматизации тестирования
Вы освоите методы использования Java Generics в API автоматизации тестирования. Поймете, как применять Generics для создания универсальных и мощных тестов для API, что повысит надежность и масштабируемость ваших тестовых сценариев.

🤔 Как Вы сможете применить полученные знания?

Повысить безопасность и эффективности кода
Вы сможете писать более безопасный и эффективный код, используя Java Generics. Это позволит вам избежать ошибок на этапе компиляции и улучшить качество вашего программного обеспечения.

Создать гибкие и переиспользуемые тесты для UI
Вы научитесь применять Java Generics для создания гибких и переиспользуемых тестов для пользовательского интерфейса. Это упростит поддержку и расширение автоматизации тестирования, делая ваши тесты более надежными.

Разработать универсальны тесты для API
Вы сможете создавать универсальные и мощные тесты для API, используя Java Generics. Это повысит надежность ваших тестов и упростит их масштабирование, что важно для эффективного тестирования сложных систем.

Для регистрации на открытый урок нажмите здесь.

Торопитесь! Места ограничены.

Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
👍41🔥1
Что такое static?

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

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

Для статических методов используется раннее связывание, то есть вызов конкретного метода разрешается на этапе компиляции, не работают перегрузка и переопределение в наследниках.

Статический блок инициализации выполняется потокобезопасно, один раз сразу после загрузки класса класслоадером. Инициализаторы статических полей выполняются в неявном статическом блоке. Блоков может быть несколько, выполнятся они в порядке объявления.

Статический импорт (static import) импортирует статические члены классов в .java-файл.
👍8🔥3
В чем разница между разными модификаторами доступа?

🔘 private – доступ только непосредственно из этого класса и его внутренних/вложенных классов;
🔘 package-private – доступ из всех классов этого пакета. Наследники доступа не имеют. Применяется когда модификатор не указан;
🔘 protected – доступ из всех классов этого пакета и всех наследников;
🔘 public – никаких ограничений доступа;

Модификаторы доступа применяются к классам, интерфейсам, методам и полям. Они нужны для реализации принципа наименьших привилегий и для отделения внутренней реализации от частей публичного API.
👍11🔥1
🔄 Как часто вам приходится вносить изменения в тесты из-за обновления интерфейса? 

Это может занимать уйму времени и вызывать постоянные проблемы с поддержкой. Java Generics позволяет вам создать универсальные методы, которые можно использовать с различными элементами UI. 

Например, можно создать метод, который будет взаимодействовать как с кнопками, так и с полями ввода, без необходимости дублирования кода. 

В итоге, вы уменьшите время на разработку и поддержку тестов, значительно сократив время на их обновление и улучшив их стабильность.

🔧Ваши тесты API часто оказываются негибкими и требуют множества изменений при добавлении новых эндпоинтов? 

Применение Java Generics для создания универсальных тестов помогает создать тесты, которые могут адаптироваться под различные эндпоинты. 

Представьте, что у вас есть тест, который может работать с несколькими типами запросов и ответов API, не требуя изменений для каждого нового эндпоинта. 

В итоге, вы сможете повысить надежность и масштабируемость тестового покрытия. Ваши тесты станут более гибкими и надежными, что позволит вам легко адаптироваться к изменениям в API.

🔍Приходится ли вам сталкиваться с повторяющимися структурами данных в тестах? 

Это может усложнять их поддержку и читабельность. С помощью Java Generics можно создать общие классы и методы для обработки различных типов данных, что сделает ваш код более организованным и понятным. 

Например, вы можете использовать один обобщенный класс для обработки списков данных различных типов. 

В результате, вы сможете улучшить структуру и читабельность тестов, что упростит их поддержку и развитие.

🌐Сложности с поддержкой тестов на разные типы браузеров? 

Тестирование UI на различных браузерах может быть трудоемким и запутанным. Java Generics позволяет создать обобщенные методы для работы с различными браузерами, что упрощает создание и поддержку тестов. 

Например, вы можете создать метод, который будет работать как с Chrome, так и с Firefox, без необходимости дублирования кода. 

В результате, вы сократите время на поддержку тестов для различных браузеров и улучшите их стабильность и гибкость.

📢Хотите узнать как применить Java Generics на своих проектах?

Приходите на открытый урок с Павлом Балахоновым и задавайте свои вопросы.

Открытый урок проходит в рамках курса Java QA Engineer. Professional. Для разработчиков автоматизированного тестирования на Java.

🗓Стартуем сегодня в 20:00 по мск.

Торопитесь! Места ограничены. Для регистрации на открытый урок нажмите здесь.

Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
👍4🔥2
Опишите процесс создания экземпляра класса

Сначала класс и цепочка его предков должны быть загружены, сверху вниз. Рассмотрим ClassLoader и процесс загрузки классов в будущих постах. Здесь важно сказать, что класс загружается только один раз, при первом к нему обращении в рамках одного класслоадера.

После загрузки каждого класса выделяется память под его статические поля и выполняются статические блоки инициализации. В будущем возможно будет ленивой не только загрузка всего класса, но и инициализация его статических финальных полей.

Далее инстанциируется сам экземпляр. Как и с загрузкой классов, процесс выполняется для всей цепочки наследования, с самого дальнего родителя:
1. Выделяется память в куче для экземпляра, получается ссылка на этот экземпляр;
2. Выполняются инициализации нестатических полей и блоков инициализации в порядке объявления;
3. Вызывается конструктор;

Статические поля интерфейсов не инициализируются при создании объекта, а другого состояния интерфейс не имеет – это исключает вопрос порядка инициализации предков при множественном наследовании.

В процессе конструирования объекта может возникать проблема виртуального вызова в конструкторе, свойственная для многих языков.
Effective Java Item 17 рекомендует не использовать переопределяемые методы в расширяемом классе. Иллюстрация неочевидного поведения в результате приведена выше на картинке
👍103🔥2❤‍🔥1
📚 Пройди тест на знание Java и получи доступ к 59 открытым урокам по Spring

🕗 Два открытых урока можно посмотреть без регистрации и оценить формат обучения. Для доступа ко всем открытым урокам из курса «Разработчик на Spring Framework» от OTUS необходимо пройти входное тестирование: ответить на 21 вопрос и уложиться в тайминг.

📊 -Курс рассчитан на профессионалов с практическим опытом работы на Java.
- Мы будем 5 месяцев погружать вас в теорию и практику Spring Framework.
- Дадим 18 практических работ. Процесс review сдаваемой работы максимально приближен к тому, каким он мог быть на реальном проекте. Помимо этого, мы попросим вас сдать дипломную работу, каких скучных записанных занятий, только живые онлайн-лекции, интересные практические задачи и развернутая обратная связь от лучших экспертов ниши. 📅 Старт группы уже 31 июля 2024г. Доступна рассрочка на обучение.

➡️ НАЧАТЬ ТЕСТИРОВАНИЕ

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
👍3🔥2
new Integer(128) == 128?

Для всех классов-оберток над примитивами кроме Float и Double работает механизм кэширования. Некоторые значения создаются на этапе инициализации класса, и переиспользуются когда объект создается не оператором new (например с помощью valueOf).

Кэшируемые значения – оба возможных Boolean, Character до '\u007f' (127) и все целые числа от -128 до 127 включительно. С Java 7 верхнюю границу для Integer можно увеличить параметром java.lang.Integer.IntegerCache.high.

Значения кэшируются и во многих других встроенных классах: BigDecimal, Currency, пустые коллекции. Детали можно узнавать из исходников и документаций, так как эти кэши реализованы не на уровне JVM а в коде классов.

В конкретно этом примере скрыт еще один подвох: объект класса-обертки сравнивается с примитивом. Это приводит к анбоксингу и сравнению значений. И ответ на вопрос – да.
🔥13👍53
Что можно делать с переменной хранящей null?

Во-первых, если переменная не финальная, использовать как L-value этого типа – присваивать новое значение.

Во-вторых, то же, что со значением null, но с учетом типа:
🔘 Сравнивать с null или переменной этого же класса;
🔘 Приводить к типу-родителю (upcast) или типу-наследнику (downcast), учитывая границы generic-параметров при наличии;
🔘 Обращаться к членам экземпляра и получать NullPointerException;
🔘 Применять instanceof и получать false
🔘 Использовать как параметр для методов и других совместимых с типом операторов

В-третьих, можно обращаться к статическим членам класса. В вопросе подразумевается именно эта интересная часть. Это безопасно, NullPointerException не возникнет, но для упрощения отладки и из-за отсутствия переопределения статических членов рекомендуется так не делать. Вместо этого обращайтесь к статике явно через имя класса, либо неявно, добавив для класса import static.
👍12🔥31
Что такое enum?

enum
– тип-перечисление. Бывает много разных формулировок вопроса, все они сводятся к разговору о перечислениях вообще. Технически это финальный класс со статическими финальными полями-экземплярами. enum Foo всегда неявно наследуется от Enum<Foo> – то есть перечислением нельзя расширить другой класс, но всё еще можно реализовать интерфейсы. Из-за generic-параметра разные перечисления не имеют общего предка кроме Object.

Является Comparable (сравнивается позиция по порядку объявления значений) и Serializable (сериализуется только имя константы).

Имеет только заранее заданный набор значений. Значения неявно public static final и это нельзя переопределить. Для инициализации констант действуют все правила статической инициализации.

Копии элементов перечисления не создаются даже при десериализации. Вот почему Effective Java предлагает использовать для сериализуемого синглтона enum.

Экземпляры хранят свойства name и ordinal – имя и порядковый номер константы. Статический метод values вернет список всех констант, valueOf – константу по имени.
Спецификация.

Финализация и клонирование перечислений запрещены.
👍11🔥3