Избавление от повторяющихся (boilerplate) методов в Enum в Java

В данной заметке будет рассказано про удаление из исходников повторяющихся кусков кода с помощью Reflection/Generics. Пусть в проекте содержится несколько перечислений для описания действий, в каждом из которых реализован метод, возвращающий список значений:
import lombok.AllArgsConstructor;
import lombok.Getter;
 
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
 
@Getter
@AllArgsConstructor
public enum TestAction {
    UP("up", "вверх"),
    DOWN("down", "вниз");
 
    public static List<Map.Entry<String, String>> listOfActions() {
        return Arrays.stream(TestAction.values())
                .map(a -> new AbstractMap.SimpleEntry<String, String>(a.name(), a.getDescription()) {
                })
                .collect(Collectors.toList());
    }
 
    private final String action;
    private final String description;
}
Неообходимые пояснения. Используются две аннотации AllArgsConstructor и Getter из фреймворка lombok для генерации конструктора и геттеров. Вызываем метод так:
private static List<Map.Entry<String, String>> actions = TestAction.listOfActions();
Но при этом возникает дублирование кода, поскольку приходится реализовывать метод в каждом из перечислений. Провести рефакторинг в данном случае довольно неочевидно. Так как все перечисления в Java наследуются от класса Enum, то создать класс AbstractAction, объявить метод в нём и отнаследовать от него все классы действий не представляется возможным. И тут приходят на помощь Reflection и Generics. Для получения поля класса без использования Reflection API необходимо вывести его в интерфейс:
public interface IAction {
    String getDescription();
}
Обновлённая версия перечисления импленментирует интерфейс IAction и не содержит метода получения списка:
import lombok.AllArgsConstructor;
import lombok.Getter;
 
@Getter
@AllArgsConstructor
public enum TestActionImproved implements IAction {
    UP("up", "вверх"),
    DOWN("down", "вниз");
 
    private final String action;
    private final String description;
}
Метод получения списка использует метод getEnumConstants() Reflection API и обрабатывает только классы, реализующие интерфейс IAction:
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
 
public class Handler {
 
    /**
     * Получает из перечисления список объектов Map.Entry с наименованием элемента перечисления и значением поля description
     *
     * @param actionClass объект class перечисления, из которого нужно получить список
     * @return список объектов Map.Entry с наименованием элемента перечисления и значением поля description
     */
    public static List<Map.Entry<String, String>> getListOfActions(Class<? extends IAction> actionClass) {
        return Arrays.stream(actionClass.getEnumConstants())
        .map(c -> new AbstractMap.SimpleEntry<String, String>(c.toString(), c.getDescription()){})
        .collect(Collectors.toList());
    }
}
Для нашего случая вызываем так:
private static List<Map.Entry<String, String>> actions = Handler.getListOfActions(TestActionImproved.class);
You can leave a response, or trackback from your own site.

Leave a Reply