«1. Обзор

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

2. Model Mapper

Основная роль ModelMapper заключается в отображении объектов путем определения того, как одна объектная модель сопоставляется с другой, называемой объектом преобразования данных (DTO).

Чтобы использовать ModelMapper, мы начинаем с добавления зависимости в наш pom.xml:

<dependency> 
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.3.7</version>
</dependency>

2.1. Конфигурация

ModelMapper предоставляет множество конфигураций для упрощения процесса сопоставления. Настраиваем конфигурацию, включая или отключая соответствующие свойства в конфигурации. Общепринятой практикой является установка для свойства fieldMatchingEnabled значения true и разрешение сопоставления частных полей:

modelMapper.getConfiguration()
  .setFieldMatchingEnabled(true)
  .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE);

Делая это, ModelMapper может сравнивать частные поля в классах сопоставления (объектах). В этой конфигурации нет строгой необходимости, чтобы все поля с одинаковыми именами существовали в обоих классах. Допускается несколько стратегий сопоставления. По умолчанию стандартная стратегия сопоставления требует, чтобы все исходные и конечные свойства сопоставлялись в любом порядке. Это идеально подходит для нашего сценария.

2.2. Type Token

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

List<Integer> integers = new ArrayList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);

List<Character> characters = new ArrayList<Character>();
modelMapper.map(integers, characters);

Далее, если мы распечатаем элементы списка символов, мы увидим пустой список. Это происходит из-за возникновения стирания типа во время выполнения.

Если мы изменим наш вызов карты для использования TypeToken, мы можем создать литерал типа для List\u003cCharacter\u003e:

List<Character> characters 
    = modelMapper.map(integers, new TypeToken<List<Character>>() {}.getType());

Во время компиляции анонимный внутренний случай TokenType сохраняет тип параметра List\u003cCharacter\u003e , и на этот раз наше преобразование прошло успешно.

3. Использование пользовательского сопоставления типов

Списки в Java могут быть сопоставлены с использованием пользовательских типов элементов.

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

List<UserDTO> dtos = users
  .stream()
  .map(user -> modelMapper.map(user, UserDTO.class))
  .collect(Collectors.toList());

Конечно, приложив немного усилий, мы могли бы создать параметризованный метод общего назначения:

<S, T> List<T> mapList(List<S> source, Class<T> targetClass) {
    return source
      .stream()
      .map(element -> modelMapper.map(element, targetClass))
      .collect(Collectors.toList());
}

Итак, вместо этого мы могли бы do:

List<UserDTO> userDtoList = mapList(users, UserDTO.class);

4. Сопоставление типов и свойств

В модель User-UserDTO можно добавить определенные свойства, такие как списки или наборы. TypeMap предоставляет метод для явного определения сопоставления этих свойств. Объект TypeMap хранит информацию об отображении определенных типов (классов):

TypeMap<UserList, UserListDTO> typeMap = modelMapper.createTypeMap(UserList.class, UserListDTO.class);

Класс UserList содержит коллекцию пользователей. Здесь мы хотим сопоставить список имен пользователей из этой коллекции со списком свойств класса UserListDTO. Для этого мы создадим первый класс UsersListConverter и передадим ему List \u003cUser\u003e и List \u003cString\u003e в качестве типов параметров для преобразования:

public class UsersListConverter extends AbstractConverter<List<User>, List<String>> {

    @Override
    protected List<String> convert(List<User> users) {

        return users
          .stream()
          .map(User::getUsername)
          .collect(Collectors.toList());
    }
}

Из созданного объекта TypeMap мы явно добавим сопоставление свойств, вызвав экземпляр UsersListConverter class:

 typeMap.addMappings(mapper -> mapper.using(new UsersListConverter())
   .map(UserList::getUsers, UserListDTO::setUsernames));

Inside the addMappings method, an expression mapping allows us to define the source to destination properties with lambda expressions. Finally, it converts the list of users into the resulting list of usernames.

5. Заключение

В этом уроке мы объяснили, как сопоставляются списки, манипулируя универсальными типами в ModelMapper. Мы можем использовать TypeToken, сопоставление универсальных типов и сопоставление свойств для создания типов списков объектов и выполнения сложных сопоставлений.

Полный исходный код этой статьи доступен на GitHub.