Mapeador de classe personalizado em Java

Feb 16 2023
Neste artigo, explicarei como implementar uma classe util de mapeador personalizado em Java sem usar bibliotecas de terceiros como MapStruct. Em primeiro lugar, pessoal, vocês devem ter conhecimento básico da linguagem de programação Java e da API Java Reflection.

Neste artigo, explicarei como implementar uma classe util de mapeador personalizado em Java sem usar bibliotecas de terceiros como MapStruct.

Em primeiro lugar, pessoal, vocês devem ter conhecimento básico da linguagem de programação Java e da API Java Reflection.

Vamos começar.

public static <O, Target> O map(Object instance, Class<Target> targetClass) {
        if (instance.getClass().isPrimitive() || targetClass.isPrimitive()) {
            throw new RuntimeException("Provide a non primitive class");
        }

        if (instance.getClass().isInterface() || targetClass.isInterface()) {
            throw new RuntimeException("Provide a non interface class");
        }

        if (instance.getClass().isEnum() || targetClass.isEnum()) {
            throw new RuntimeException("Provide a non enum class");
        }

        if (Modifier.isAbstract(instance.getClass().getModifiers()) || Modifier.isAbstract(targetClass.getModifiers())) {
            throw new RuntimeException("Provide a non abstract class");
        }

        return doMap(instance, instance.getClass(), targetClass);
    }

@SneakyThrows
private static <O, Source, Target> O doMap(Object sourceInstance, Class<Source> sourceClass, Class<Target> targetClass) {
    Set<Field> declaredSourceFields = findAllFields(sourceClass);
    Set<Field> declaredTargetFields = findAllFields(targetClass);

    Object targetInstance = targetClass.getDeclaredConstructor().newInstance();

    for (Field field : declaredTargetFields) {
        Field sourceField = findSourceField(field, declaredSourceFields);
        if (sourceField != null) {
            field.setAccessible(true);
            field.set(targetInstance, sourceField.get(sourceInstance));
        }
    }
    return (O) targetInstance;
}

public static Set<Field> findAllFields(Class<?> inputClass) {
        Set<Field> allFields = new HashSet<>();

        if (inputClass == null) {
            return allFields;
        }

        Field[] fields = inputClass.getDeclaredFields();
        allFields.addAll(Set.of(fields));
        allFields.addAll(findAllFields(inputClass.getSuperclass()));
        return allFields;
}

private static Field findSourceField(Field targetField, Set<Field> declaredSourceFields) {
    for (Field field : declaredSourceFields) {
        field.setAccessible(true);
        String sourceFieldName = field.getName();
        if (sourceFieldName.equals(targetField.getName()) &&
                field.getType().equals(targetField.getType())) {
            return field;
        }
    }
    return null;
}

Exemplos:

@Setter
@Getter
public class SourceClass {
    String surname;
    Integer age;
    private String name;

    @Override
    public String toString() {
        return "SourceClass{" +
                "surname='" + surname + '\'' +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
@Setter
@Getter
public class TargetClass {
    Integer age;
    private String name;

    @Override
    public String toString() {
        return "TargetClass{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
public static void main(String[] args) {
        SourceClass sourceInstance = new SourceClass();
        sourceInstance.setAge(27);
        sourceInstance.setName("Java");
        sourceInstance.setSurname("Reflection");

        TargetClass targetInstance = CustomMapper.map(sourceInstance, TargetClass.class);
        System.out.println(targetInstance);
}

O sobrenome não existe na saída porque não está disponível na classe de destino. Portanto, o método map ignora esse campo.

Conclusão:
A maioria das bibliotecas, estruturas e ides escritos em Java aproveitam a API Java Reflection. Mas esta API é um pouco lenta porque verifica as permissões de segurança para cada operação. Para um melhor desempenho, você pode verificar o Java Method Handles neste linkhttps://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.html.

Obrigado por ler.
Atenciosamente