В данной заметке будет рассказано про удаление из исходников повторяющихся кусков кода с помощью 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;
} |
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(); |
private static List<Map.Entry<String, String>> actions = TestAction.listOfActions();
Но при этом возникает дублирование кода, поскольку приходится реализовывать метод в каждом из перечислений.
Провести рефакторинг в данном случае довольно неочевидно. Так как все перечисления в Java наследуются от класса Enum, то создать класс AbstractAction, объявить метод в нём и отнаследовать от него все классы действий не представляется возможным. И тут приходят на помощь Reflection и Generics.
Для получения поля класса без использования Reflection API необходимо вывести его в интерфейс:
public interface IAction {
String getDescription();
} |
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;
} |
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());
}
} |
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); |
private static List<Map.Entry<String, String>> actions = Handler.getListOfActions(TestActionImproved.class);