В данной заметке будет рассказано про удаление из исходников повторяющихся кусков кода с помощью 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); |