Избавление от повторяющихся (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