Niestandardowy program do mapowania klas w Javie

Feb 16 2023
W tym artykule wyjaśnię, jak zaimplementować niestandardową klasę użytkową mappera w Javie bez korzystania z zewnętrznych bibliotek, takich jak MapStruct. Przede wszystkim powinniście mieć podstawową wiedzę na temat języka programowania Java i Java Reflection API.

W tym artykule wyjaśnię, jak zaimplementować niestandardową klasę użytkową mappera w Javie bez korzystania z zewnętrznych bibliotek, takich jak MapStruct.

Przede wszystkim powinniście mieć podstawową wiedzę na temat języka programowania Java i Java Reflection API.

Zaczynajmy.

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

Przykłady:

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

Nazwisko nie istnieje w danych wyjściowych, ponieważ nie jest dostępne w klasie docelowej. Zatem metoda map ignoruje to pole.

Wniosek:
Większość bibliotek, frameworków i idei napisanych w Javie wykorzystuje Java Reflection API. Ale ten interfejs API jest trochę powolny, ponieważ sprawdza uprawnienia bezpieczeństwa dla każdej operacji. Aby uzyskać lepszą wydajność, możesz sprawdzić uchwyty metody Java z tego łączahttps://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.html.

Dziękuję za przeczytanie.
Z wyrazami szacunku