Asignador de clases personalizado en Java

Feb 16 2023
En este artículo, explicaré cómo implementar una clase de utilidad de mapeador personalizado en Java sin usar bibliotecas de terceros como MapStruct. En primer lugar, muchachos, deben tener conocimientos básicos del lenguaje de programación Java y la API de Java Reflection.

En este artículo, explicaré cómo implementar una clase de utilidad de mapeador personalizado en Java sin usar bibliotecas de terceros como MapStruct.

En primer lugar, muchachos, deben tener conocimientos básicos del lenguaje de programación Java y la API de Java Reflection.

Empecemos.

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;
}

Ejemplos:

@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);
}

El apellido no existe en la salida porque no está disponible en la clase de destino. Entonces, el método del mapa ignora este campo.

Conclusión:
la mayoría de las bibliotecas, marcos e idus escritos en Java aprovechan la API de Java Reflection. Pero esta API es un poco lenta porque verifica los permisos de seguridad para cada operación. Para un mejor rendimiento, puede consultar los identificadores de métodos de Java desde este enlacehttps://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.html.

Gracias por leer.
Atentamente