JavaStart
910 subscribers
911 photos
28 videos
621 links
Учи Java вместе с нами!
Наша платформа с курсом - https://javastart.tech
Ютуб с обучающими видео - https://www.youtube.com/@javastart_tech
Download Telegram
Коллекции в Java. Часть 9.
Интерфейс Map.

Я поместил описание интерфейса Map вниз, потому что технически он не относится к иерархии коллекций Collection. Он не является потомком Iterable и, соответственно, Collection, у него нет общих для коллекций методов, это другой вид объекта, и методы у него свои. Но я решил упомянуть о нем именно в контексте коллекций, потому что они часто объясняются и используются вместе. При этом Map — другой вид объекта, и принцип построения у него отличается.

На собеседованиях на Junior Java разработчика и стажера, когда вы рассказываете о иерархии Collection, обязательно упоминайте об интерфейсе Map! Это неотъемлемая часть структур данных в Java, да и не только в Java.

Map, «карта», он же «словарь» или ассоциативный массив — это список формата «ключ-значение». Представьте себе таблицу, где данные указаны, например, в таком формате:

Имя: Иван
Фамилия: Иванов

«Имя» и «Фамилия» будут ключами, а «Иван» и «Иванов» — значениями. По такому принципу данные хранятся в Map: вместо индексов у них ключи. Map не итерируется, потому что сложно сказать, какой из ключей можно назвать «первым», а какой «вторым», — это неупорядоченная структура.

👉 JavaStart. Подписаться
👍9🔥31👨‍💻1
У Map три непосредственных реализации и один дочерний интерфейс, который в свою очередь имеет свою реализацию. Дочерний интерфейс называется SortedMap, от него наследуется еще один интерфейс NavigableMap, а уже у него есть реализация под названием TreeMap — карта в виде дерева. Структурирование дерева в сете возможно благодаря наличию в объекте элемента TreeMap для хранения позиций

Непосредственные реализации — это Hashtable, HashMap, LinkedHashMap.

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

HashMap — более новая альтернатива Hashtable. Она отличается от предыдущей двумя деталями: HashMap не синхронизированная, а еще в ней можно в качестве значения или ключа хранить null — «отсутствие», нулевую или пустую сущность. Эта реализация не отсортирована.

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

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

👉 JavaStart. Подписаться
👍113🔥21🆒1
Хэширование в Java. Часть 1.

Давайте подробнее рассмотрим хэширование и Hash таблицы.

Хэширование — это преобразование информации с помощью особых математических формул. В результате возникает хэш — отображение данных в виде короткой строки. Размер строки может быть одинаковым для информации разного объема.

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

Математические алгоритмы, по которым хэшируется информация, называются хэш–функциями. Их название тоже иногда сокращают как «хэш». Хэш–функций существует очень много, они различаются методами вычислений, назначением, надежностью и другими параметрами.

Но для чего же нужно хэширование?

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

Вот несколько примеров:

– вместо паролей на сервере хранятся хэши паролей;

– антивирус хранит в базе хэши вирусов, а не образцы самих программ;

– электронная подпись использует хэш для верификации;

– информация о транзакциях криптовалюты хранится в виде хэшей;

– коммиты в Git идентифицируются по хэшу.

Среди других, менее распространенных примеров использования — поиск дубликатов в больших массивах информации, генерация ID и построение особых структур данных. Это, например, хэш–таблицы — в них идентификатором элемента является его хэш, и он же определяет расположение элемента в таблице. Об этом далее.

👉 JavaStart. Подписаться
👍94❤‍🔥2🔥21
4😁3🤓31👨‍💻1
Хэширование в Java. Часть 2.

Hash таблицы.

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

Каждый элемент массива — своеобразная «корзина» или bucket, которая хранит связанный список со значением.

HashMap использует для хранения пары — на каждый ключ приходится только одно значение. То есть связанный список будет состоять из одного элемента, а ссылаться этот элемент будет на null — специальное «пустое» значение. Если бы в такой структуре значений было несколько, первое ссылалось бы на второе и так далее — так устроен связанный список.

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

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

HashMap — динамическая структура, то есть количество «корзинок» может изменяться. По умолчанию сущность создается с 16 «корзинками», но это поведение можно поменять при создании, для чего надо задать хэш-таблице начальный размер вручную. Когда элементов в ней становится больше, чем корзинок, структура удлиняется — перезаписывает массив на новый, с большей длиной. По умолчанию длина увеличивается вдвое.

У HashMap, как и у всех подобных структур, есть набор своих методов — функций, которые позволяют удобно работать с данными. Для добавления, поиска, перезаписи или удаления элемента есть свои команды; также можно перебирать элементы и ключи и делать многое другое.

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

👉 JavaStart. Подписаться
👍5🔥3221
Хэширование в Java. Часть 3.

Коллизии Hash таблиц и их предотвращение.

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

Может случиться так, что у двух разных ключей окажется одинаковый хэш. Или хэш будет разным, но по формуле позиция для обоих хэшей будет одинаковой. Тогда значения обоих ключей окажутся записаны в одну «корзинку» или bucket. Это и есть коллизия.

Именно из-за коллизий для хранения значений используется связанный список: если бы в массиве просто хранился объект, любая коллизия перезаписала бы текущее значение, а это опасно. А при текущей реализации, даже если случится коллизия, новое значение просто запишется в начало той же «корзинки», не изменив старое.

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

👉 JavaStart. Подписаться
👍93🔥211
Хэширование в Java. Часть 4.

HashMap умеет отличать коллизию от реальной перезаписи элемента.

🔞 Обратите внимание, это очень важный пункт, практически всегда на собеседовании вас спросят об этом!

Когда структуре дают новую пару «ключ–значение», она проверяет, есть ли в массиве такие хэши и такие ключи.

Результат такой:

1) если таких хэшей и ключей нет, в хэш–таблицу просто добавляется новая пара;

2) если такой ключ есть, это перезапись — структура переписывает элемент с таким же ключом;

3) если такого ключа нет, но хэш есть — это коллизия, новое значение записывается в ту же «корзинку» или bucket за предыдущим.

👉 JavaStart. Подписаться
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥21💯1
This media is not supported in your browser
VIEW IN TELEGRAM
А ты классный программист?

👉 JavaStart. Подписаться
😁86👍1👌1👨‍💻1🆒1
Коллекции в Java. Часть 10.

Как разобраться с коллекциями в Java?

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

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

👉 JavaStart. Подписаться
👍13🔥321👏1💯1
Что вернёт метод test123() ?

👉 JavaStart. Подписаться
🔥5👍3🤔1👨‍💻1
Что вернёт метод test123() ?
Anonymous Quiz
6%
2
60%
1
17%
NullPointerException
18%
Узнать ответ
👍10211
🤣8😱2🔥1🤔1🐳1
Поговорим о тестировании ПО. Часть 1.

Тестирование – это очень важный процесс в разработке ПО. Серьезно, настоящий разработчик должен уметь не только писать код, решая задачу, которую хочет бизнес, но и тестировать свое решение.

Тестирование в разработке – это, Иными словами, проверка правильности работы нашей программы в тех или иных ситуациях.

Видов тестирования, методик, разделов существует очень много, все они углубленно изучаются отдельными специалистами – тестировщиками (ручными или авто). Я же остановлюсь непосредственно на основных видах тестирования, которые используем мы – Java разработчики.

Тут все просто – будем опираться на пирамиду тестирования Майка Кона (на картинке).

Unit — модульные тесты, применяемые в различных слоях приложения, тестирующие наименьшую делимую логику приложения: чаще всего — метод. Эти тесты обычно стараются по максимуму изолировать от внешней логики, то есть создать иллюзию того, что остальная часть приложения работает в стандартном режиме.
Данные тесты покрывают маленькие кусочки кода и весьма легковесные, не кушающие много ресурсов (под ресурсами я имею виду оперативную память и время).

Integration — интеграционное тестирование. Оно проверяет более крупные кусочки системы, то есть это либо объединение нескольких кусочков логики (несколько методов или классов), либо корректность работы с внешним компонентом. Этих тестов как правило меньше, чем Unit, так как они тяжеловеснее.

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

UI — тесты, которые проверяют работу пользовательского интерфейса. Они затрагивают логику на всех уровнях приложения, из-за чего их еще называют сквозными. Их как правило в разы меньше, так они наиболее тяжеловесны и должны проверять самые необходимые (используемые) пути.

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

👉 JavaStart. Подписаться
👍83🔥31🆒1
Ключевые понятия юнит–тестирования. Часть 2. Начало.

Покрытие тестов (Code Coverage) — одна из главных оценок качества тестирования приложения. Это процент кода, который был покрыт тестами (0–100%).

На практике многие гонятся за этим процентом, с чем я не согласен, так как начинается навешивание тестов там, где они не нужны. Например, у нас в сервисе есть стандартные CRUD (create/get/update/delete) операции без дополнительной логики. Эти методы — сугубо посредники, делегирующие работу слою, работающему с репозиторием (для взаимодействия с базой данных). В данной ситуации нам нечего тестировать: разве то, что вызывает ли данный метод — метод из дао, но это не серьёзно 😅

TDD (Test–driven development) — разработка через тестирование. В рамках этого подхода в первую очередь пишется тест, который будет проверять определенный код. Получается тестирование чёрного ящика: мы знаем, что есть на входе и знаем, что должно получиться на выходе. Основная цель TDD – сделать код более понятным, простым и без ошибок.

Подход состоит из таких составляющих:

Пишем наш тест.
– Запускаем тест, прошел он или нет (видим, что всё красное — не психуем: так и должно быть 😄).
Добавляем код, который должен удовлетворить данный тест (запускаем тест).
Выполняем рефакторинг кода.
– That's it.

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

👉 JavaStart. Подписаться
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥3221
😁142👍2💯1
Ключевые понятия тестирования. Часть 2. Продолжение.

BDD (Behavior–driven development) — разработка через поведение. Это подход основан на TDD. Если говорить точнее, он использует написанные понятным языком примеры (как правило на английском), которые иллюстрируют поведение системы для всех, кто участвует в разработке. Не будем углубляться в данный термин, так как он в основном затрагивает тестировщиков и бизнес-аналитиков.

Тестовый сценарий (Test Case) — сценарий, описывающий шаги, конкретные условия и параметры, необходимые для проверки реализации тестируемого кода.

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

Мок (Mock) — объекты, которые настраиваются (обычно специфично для каждого теста) и позволяют задать ожидаемые ответы методов, которые мы хотим получить. Проверки соответствия ожиданиям проводятся через вызовы к Mock–объектам.

Заглушки (Stub) — обеспечивают жестко зашитый ответ на вызовы во время тестирования.

Также они могут сохранять в себе информацию о вызове (например, параметры или количество этих вызовов). Такие иногда называют своим термином — шпион (Spy).

Иногда эти термины stubs и mock путают: разница в том, что стаб ничего не проверяет, а лишь имитирует заданное состояние. А мок — это объект, у которого есть ожидания. Например, что данный метод класса должен быть вызван определенное число раз. Иными словами, ваш тест никогда не сломается из-за стаба, а вот из-за мока может.

👉 JavaStart. Подписаться
👍722🔥1👨‍💻1
JUnit тестирование. Часть 3.

Для Java доступно несколько сред тестирования (фреймворков). Самый популярный из них — JUnit. Используется для модульных тестов. Библиотека JUnit подключается к системе сборки приложений, которой пользуется разработчик, — Maven или Gradle.

JUnit тест представляет собой метод, содержащийся в классе, который используется только для тестирования. Класс, как правило, называется так же, как и класс, который он тестирует с +Test в конце. Например, CarService → CarServiceTest. По сути этот класс и называется тестовым.

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

Рассмотрим примитивные юнит тесты для понимания.

Создадим некий тестовый класс PersonServiceTest.

В первом тестовом методе на картинке мы создаем объект типа Person, сетим ему имя (в String поле firstName) и просто проверяем, что это поле теперь равно “Stephen”.

Мы используем метод .assertEquals(x, y) из пакета org.junit* ,
где x – это значение, которое мы ожидаем, y – это реальное значение (в нашем случае реальное значение поля firstName).

Во втором тестовом методе на картинке создаем объект типа Person, но уже не сетим никакого состояния ему (не сетим какие-либо поля – firstName или, например, secondName, неважно), но проверяем, что созданный объект существует в памяти, используя один из методов фреймворка JUnit - .assertNotNull(x),
где x – это проверяемый аргумент любого типа (наследуется от класса Object).

Идея такая, в целом, ничего сложного)

👉 JavaStart. Подписаться
👍11🔥21👏1
8😁7🔥4
Базовые аннотации в Java тестировании. Часть 4.

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

@Test — определение данного метода в качестве тестируемого (метод, помеченный данной аннотацией и есть тест).

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

@After — ставится над методом, который будет вызывать после каждого теста (чистка данных, восстановление дефолтных значений).

@BeforeClass — ставится над методом, вызывается лишь однажды перед всеми тестами для данного класса. Используется для выполнения более тяжелых операций, как например подъем тестовой БД.

@AfterClass — исполняется один раз для данного класса, но после всех тестов (например, для очистки постоянных ресурсов или отключения от БД).

@Ignore — отмечает, что метод ниже отключен и будет игнорироваться при общей прогонке тестов. Используется в разных случаях, например, если изменили базовый метод и не успели переделать под него тест. В таких случаях ещё желательно добавить описание — @Ignore("Some description").

@Test(expected = Exception.class) – для отрицательных тестов. Ожидается, что метод выкинет некоторое исключение.

@Test(timeout=100) — проверяет, что метод исполняется не более чем 100 миллисекунд.

@Mock — используется над полем в тестовом классе для задания какого-либо объекта моком (это не из JUnit библиотеки, а из Mockito, о нем позже), поведение мока задаётся в конкретной ситуации в методе теста.

@RunWith(MockitoJUnitRunner.class) — метод ставится над классом. Он и является кнопкой для прогона тестов в нем. Runner-ы могут быть различными: например, есть такие: MockitoJUnitRunner, JUnitPlatform, SpringRunner и т. д.).

В JUnit 5 аннотацию @RunWith заменили более мощной аннотацией @ExtendWith.

👉 JavaStart. Подписаться
👍8🔥721