Что такое mapping в java
Map в Java с примерами
В статье о коллекциях я обещал написать статью о Map в Java. Сейчас мы подробно разберем эту очень полезную и нужную структуру данных с примерами кода.
Когда мы слышим слово мап, то первое, что приходит на ум — это карта (у меня гугл мапс). Map как структура данных не имеет ничего общего с картами гугла.
Чтобы правильно понять, что такое мап вспомните поликлинику. Да, воспоминания не очень. Но, нам нужно только вспомнить регистратуру, когда для приема у врача нам сначала нужно выстоять здоровенную очередь за своей картой. Не знаю как там в Европе, но для граждан стран СНГ это знакомо. Мы приходим, говорим свои данные и получаем карточку. Если же мы приходим впервые, нас регистрируют и только потом выдают карточку. Как правило карты находятся в алфавитном порядке и «милой» тете с регистратуры ее не сложно найти.
Иерархия классов Map немного похожа на Set:
HashMap — хранит ключи в hash-таблице. Она имеет наибольшую производительность. Однако такая реализация не гарантирует порядок элементов.
TreeMap — хранит ключи в отсортированном порядке. Работает медленнее чем хэшмап.
LinkedHashMap — хранит ключи в порядке их вставки в мап. Работает немного медленнее чем HashMap.
WeakHashMap — реализация интерфейса Map на основе хэш-таблицы со слабыми ключами. Запись в WeakHashMap будет автоматически удалена, если ее ключ больше не используется обычным образом.
Рассмотрим подробнее HashMap. Скорость ее работы О(1), а в худшем случае O(logn). Чтобы понять, когда будет худший случай давайте разберемся как она работает. Это, кстати, очень популярный вопрос на собеседованиях.
У каждого объекта есть метод hashCode, который возвращает значение хэш кода. Когда мы помещаем объект в HashMap сначала определяется значение hash кода его ключа, далее выбирается место, куда поместить объект в зависимости от полученного хэш кода. Если по такому ключу уже есть значение в мапе, то проверяется объект, который мы пытаемся добавить если он такой же как и существующий, то идет перезапись. Если объекты разные, а хэш код одинаковый (произошла коллизия или мы неправильно переопределили метод hashCode) объект помещается в ту самую ячейку в виде связанного списка. Вот откуда худший случай работы HashMap. Когда хэш код ключей одинаковый эта структура начинает работать как LinkedList скорость которого O(logn). Вы можете спросить: как нужно переопределить метод hashCode чтобы он возвращал одинаковое значение для всех объектов? Вот пример:
public class MapExamples <
private String name ;
private double sum ;
@Override
public int hashCode ( ) <
return 1 ;
>
Да, нужно постараться, чтобы такое сделать, но все же.
Теперь перейдем к примеру. Будут рассмотрены только самые популярные методы. Они у всех реализациях одинаковы.
import java.util.HashMap ;
import java.util.Map ;
public class MapExamples <
Что еще хотелось бы упомянуть о Map в java:
Вот и все, что касается интерфейса Map и его реализации. Теперь у Вас будет полное представление о стандартных коллекциях в java и их применениях.
Map в Java. Hashmap в Java
Привет! Это статья про Карты (Map), один из способов хранения данных в Java.
Что такое карта (Map)
К сожалению, карта (Map) в Java не имеет никакого отношения к картам из реального мира 🙂 Ну или почти никакого.
Какие есть виды карт (map) в Java
Cреди основных реализаций можно назвать:
Если представить в виде диаграммы, будет выглядеть так:
Но для начала этого явно многовато 🙂 Поэтому по теме «map в Java» мы чуть позже напишем несколько статей. А пока эта статья будет как вводная с основным акцентом на HashMap.
Давайте посмотрим, чем они друг от друга отличаются.
Можно сказать, что для начала Вам хватит знать и уметь работать с HashMap. Именно на ней мы и будем приводить примеры.
Синтаксис HashMap
Что такое ModelMapper и зачем он нужен?
Senior Java разработчик Usetech
В ходе разработки любого приложения программист сталкивается с необходимостью работать с моделями различных объектов, созданных для разных целей. И соответственно, с необходимостью конвертировать их между собой. Если ваш проект на начальном этапе развития, то можно, конечно, использовать рукописные конверторы. Но рано или поздно проект станет больше, и вы столкнётесь с необходимостью использовать уже готовое решение для конвертации моделей.
Одним из таких решений является МodelMapper. Его очень просто использовать в самом начале проекта без особых знаний. Попытаюсь разобрать основные моменты использования фреймворка.
Как начать использовать ModelMapper?
Сначала добавляем его в зависимости. Если вы используете maven, то:
implementation group: ‘org.modelmapper’, name: ‘modelmapper’, version: ‘2.3.0’
Предположим, у нас простой проект с базой данных и RestAPI и нам необходимо превратить entity в dto и обратно. На этапе прототипа проекта могут полностью совпадать, и в таком простейшем примере нам вообще не нужно ничего дополнительного писать. МodelMapper всё сделает за нас.
В примере, представленном ниже, я буду использовать аннотации Lombok, чтобы было проще =)
Наши entity:
Наша dto:
Для того, чтобы начать маппить entity в dto нам достаточно написать вот такой простой конвертор:
А для наполнения базы использовать следующие entity:
И наш контроллер с одним единственным методом:
После выполнения запроса http://localhost:8080/book мы получаем следующий ответ:
МodelMapper по названию полей сам догадывается, что на что нужно маппить. Это очень удобно, если у вас есть множество моделек, которые в целом похожи друг на друга. Весь процесс можно разбить на две части: распознание и связь полей, а также перенос значений.
Если описать работу маппера простыми словами, то: он сканирует поля в соответствии с AccessLevel, парсит их и бьёт на токены, сравнивая эти токены он пытается понять подходит ли поле для маппинга. Стратегии настраивают степень точности:
Пример настройки значений для ModelMapper:
Это далеко не все настройки МodelMapper, больше настроек можно посмотреть в классе InheritingConfiguration.
Маппинг отдельных полей
Для начинающего специалиста, показанного выше, вполне достаточно. Но для серьёзного приложения нужен больший контроль над маппингом определённых полей. Также было бы удобно маппить вложенные сущности. В этом разделе мы рассмотрим, как нам с этим поможет МodelMapper.
Давайте немного усложним наш маппинг. Предположим, что нам в нашем поле index не нужна подстрока ISBN:. Как нам изменить условия маппинга, чтобы для одного поля мы удаляли эту подстроку?
Можно использовать Converter :
В данном примере мы создали TypeMap для двух наших объектов и указали поле, для которого мы хотим использовать этот конвертер.
Теперь наш запрос возвращает следующее:
Мы научились модифицировать правила конвертации отдельных полей и целых объектов. С этим уже можно полноценно работать. Но бывают случаи, когда нам не нужно модифицировать значение, а необходимо просто связать два названных по-разному поля.
Добавим в наши объекты поля: comment в BookEntity и review в BookDto и модифицируем наш BookConverter:
И тогда запрос будет выглядеть вот так:
Теперь при маппинге отдельных полей у нас будет меньше мороки.
А что, если нам нужно маппить ещё и вложенную сущность? Для этого мы снова модифицируем BookDto и добавляем туда поле author вместо authorName. А также создаём класс AuthorDto, содержащий только поле name.
И наш BookConverter теперь будет выглядеть следующим образом:
А в ответе на запрос книг получаем:
Маппинг и наследование
Самое просто мы разобрали ранее. Теперь давайте посмотрим, как же ModelМapper работает с наследованием. Для этого мы изменим модель наших данных, добавив наследников для книг.
Теперь наша модель будет выглядеть так:
А наши начальные данные так:
И вот так мы поменяем наш конвертор:
Для того, чтобы ModelМapper понял, что AudioBookEntity и HardCoverBookEntity — это наследники BookEntity, мы должны к TypeMap вызвать include и добавить маппинги. Но, к сожалению, для внутренних маппингов нам надо будет указывать вручную маппинг всех полей, как показано в примере. Эта особенность может стать проблемой если у вас на проекте примитивные базовые классы и развитая иерархия наследования классов.
В ответе на запрос теперь мы получаем:
Заключение
ModelМapper — это удобный фреймворк, который можно использовать как на старте вашего проекта, так и на более поздних этапах. Но у него, как и у любого инструмента, есть свои слабые стороны и ограничения, о которых стоит знать и которые стоит учитывать.
Interesting Tasks
воскресенье, 20 октября 2013 г.
Маппинг объектов с помощью java-object-merger
Для чего нужны мапперы объектов?
Простой ответ: чтобы копировать данные автоматически из одного объекта в другой. Но тогда вы можете спросить: зачем нужно это копирование? Можно усомниться, что это нужно очень часто. Значит следует дать более развернутый ответ.
В мире энтерпрайз приложений принято бить внутреннюю структуру на слои: слой доступа к базе, бизнес и представление/веб сервиса. В слое доступа к базе как правило обитают объекты мапящиеся на таблицы в базе. Условимся называть их DTO (от Data transfer object). По хорошему, они только переносят данные из таблиц и не содержат бизнес логики. На слое представления/веб сервисов, находятся объекты, доставляющие данные клиенту (браузер / клиенты веб сервисов). Назовем их VO (от View object). VO могут требовать только часть тех данных, которые есть в DTO, или же агрегировать данные из нескольких DTO. Они могут дополнительно заниматься локализацией или преобразованием данных в удобный для представления вид. Так что передавать DTO сразу на представление не совсем правильно. Так же иногда в бизнес слое выделяют бизнес объекты BO (Business object). Они являются обертками над DTO и содержат бизнес логику работы с ними: сохранение, модифицирование, бизнес операции. На фоне этого возникает задача по переносу данных между объектами из разных слоев. Скажем, замапить часть данных из DTO на VO. Или из VO на BO и потом сохранить то что получилось.
Если решать задачу в лоб, то получается примерно такой “тупой” код:
Мапперы объектов
Чем плох дозер
Какими качествами должен обладать маппер?
Реализация
Перед началом разработки был соблазн написать библиотеку на scala. Т.к. уже был положительный опыт ее использования.
Почему merger а не mapper?
Использование
Программа “Hello world” выглядит примерно так:
Во-первых, видим, что для маппинга необходимо, чтобы у свойства был геттер на обоих объектах. Это нужно для сравнения значений. И сеттер у целевого объекта, чтобы записывать новое значение. Сами свойства должны именоваться одинаково.
Посмотрим же как реализован метод map. Это поможет понять многие вещи о библиотеке.
Если исходный снапшот это бин, и если у него есть identifier, тогда пытаемся найти целевой бин для класса destinationClass используя IBeanFinder-ы [тут createSnapshot(destinationClass, identifier);]. Мы такие не регистрировали, да и identifier-а нет, значит идем дальше. В противном случает бин создается используя подходящий IObjectCreator [тут createSnapshot(destinationClass)]. Мы таких тоже не регистрировали, однако в стандартной поставке имеется создатель объектов конструктором по умолчанию — он и используется. Далее у целевого снапшота берется дифф от снапшота источника и применяется к целевому объекту. Все.
Кстати, дифф, для этого простого случая, будет выглядеть так:
Основные аннотации:
Преобразования типов
В IMergingContext можно регистрировать пользовательские преобразователи типов, из одного типа в другой (интерфейс TypeConverter). Стандартный набор преобразователей включает преобразования:
java-object-merger — больше чем просто маппер объектов
Всем привет! Хотел бы представить вам новую библиотеку на java для маппинга/мержинга объектов, которую я “скромно” позиционирую как возможную альтернативу dozer-у. Если вы разрабатываете enterprise приложения на java, вам не безразлична эффективность вашей работы, и хочется писать меньше скучного кода, то приглашаю почитать дальше!
UPD. Выложено в центральный репозиторий мавена
Для чего нужны мапперы объектов?
Простой ответ: чтобы копировать данные автоматически из одного объекта в другой. Но тогда вы можете спросить: зачем нужно это копирование? Можно усомниться, что это нужно очень часто. Значит следует дать более развернутый ответ.
В мире энтерпрайз приложений принято бить внутреннюю структуру на слои: слой доступа к базе, бизнес и представление/веб сервиса. В слое доступа к базе как правило обитают объекты мапящиеся на таблицы в базе. Условимся называть их DTO (от Data transfer object). По хорошему, они только переносят данные из таблиц и не содержат бизнес логики. На слое представления/веб сервисов, находятся объекты, доставляющие данные клиенту (браузер / клиенты веб сервисов). Назовем их VO (от View object). VO могут требовать только часть тех данных, которые есть в DTO, или же агрегировать данные из нескольких DTO. Они могут дополнительно заниматься локализацией или преобразованием данных в удобный для представления вид. Так что передавать DTO сразу на представление не совсем правильно. Так же иногда в бизнес слое выделяют бизнес объекты BO (Business object). Они являются обертками над DTO и содержат бизнес логику работы с ними: сохранение, модифицирование, бизнес операции. На фоне этого возникает задача по переносу данных между объектами из разных слоев. Скажем, замапить часть данных из DTO на VO. Или из VO на BO и потом сохранить то что получилось.
Если решать задачу в лоб, то получается примерно такой “тупой” код:
Знакомо? 🙂 Если да, то могу вас обрадовать. Для этой проблемы уже придумали решение.
Мапперы объектов
Придуманы конечно-же не мной. Реализаций на java много. Вы можете ознакомится, к примеру тут.
Вкратце, задача маппера — скопировать все свойства одного объекта в другой, а так же проделать все то же рекурсивно для всех чайлдовых объектов, по ходу делая необходимые преобразование типов, если это требуется.
Мапперы из списка выше — все разные, более или менее примитивные. Самый мощный пожалуй dozer, с ним я работал около 2 лет, и некоторые вещи в нем перестали устраивать. А вялый темп дальнейшей разработки дозера побудили написать свой “велосипед” (да, я знакомился с другими мапперами — для наших требовний они еще хуже).
Чем плох dozer
Какими качествами должен обладать маппер?
Почему merger а не mapper?
java-object-merger отличает от других мапперов одна особенность. Основополагающая идея была в том, чтобы дать возможность создавать снимки объектов (Snapshot) на некоторый момент времени, и потом, сравнивая их, находить различия (Diff) подобно тому, как находим diff между двумя текстами. Причем должна быть возможность просмотра снапшотов и диффов в понятном для человека текстовом виде. Так, чтобы раз взглянув на дифф сразу стали ясны все отличия, а так же как будет изменен целевой объект после применения диффа. Таким образом добиваемся полной прозрачности процесса. Никакой магии и черных ящиков! Создание снапшотов открывает еще один интересный сценарий. Можно сделать снапшот объекта, потом как-то изменив его, сделать новый снапшот — проверить что изменилось, и, при желании, откатить изменения. Кстати дифф можно обойти специальным visitor-ом, и пометить только те изменения, которые хочется применить, а остальные проигнорировать.
Так что можно сказать, что merger — это нечто большее чем просто mapper.
Использование
Программа “Hello world” выглядит примерно так:
Во-первых, видим, что для маппинга необходимо, чтобы у свойства был геттер на обоих объектах. Это нужно для сравнения значений. И сеттер у целевого объекта, чтобы записывать новое значение. Сами свойства должны именоваться одинаково.
Посмотрим же как реализован метод map. Это поможет понять многие вещи о библиотеке.
Если исходный снапшот это бин, и если у него есть identifier, тогда пытаемся найти целевой бин для класса destinationClass используя IBeanFinder-ы [тут createSnapshot(destinationClass, identifier); ]. Мы такие не регистрировали, да и identifier-а нет, значит идем дальше. В противном случает бин создается используя подходящий IObjectCreator [тут createSnapshot(destinationClass) ]. Мы таких тоже не регистрировали, однако в стандартной поставке имеется создатель объектов конструктором по умолчанию — он и используется. Далее у целевого снапшота берется дифф от снапшота источника и применяется к целевому объекту. Все.
Кстати, дифф, для этого простого случая, будет выглядеть так:
Основные аннотации
Преобразования типов
Категории объектов
Производительность
Честно говоря, пока писал библиотеку — о производительности особо не задумывался. Да и изначально в целях высокой производительности не было. Однако, решил замерять время маппинга N раз на один тестовый объект. Исходный код теста. Объект довольно сложный, с полями значениями, дочерними бинами, коллекциями и мапами. Для сравнения взял dozer последней на текущий момент версии 5.4.0. Ожидал, что дозер не оставит никаких шансов. Но получилось совсем наоборот! dozer замапил 5000 тестовых объектов за 32 секунды, а java-object-merger 50000 объектов за 8 секунд. Разница какая-то дикая — в 40 раз…
Применение
java-object-merger был опробован на текущем проекте с моей основной работы (osgi, spring, hibernate, сотни мапящихся классов). Чтобы заменить им дозер полностью ушло менее 1 дня. По ходу находились некоторые явные косяки, но, после исправления, все основные сценарии работали нормально.