Ответ 1
Он довольно глубоко вложен, но это не кажется чрезвычайно трудным.
Первое наблюдение заключается в том, что если for-loop преобразуется в поток, вложенные for-loops могут быть "сплющены" в один поток с использованием flatMap
. Эта операция принимает один элемент и возвращает произвольные числовые элементы в потоке. Я посмотрел вверх и обнаружил, что StandardServer.findServices()
возвращает массив Service
, поэтому мы превращаем его в поток с помощью Arrays.stream()
. (Я делаю подобные предположения для Engine.findChildren()
и Host.findChildren()
.
Затем логика в каждом цикле выполняет проверку instanceof
и приведение. Это можно смоделировать с использованием потоков в качестве операции filter
для выполнения instanceof
, за которой следует операция map
, которая просто выдает и возвращает ту же ссылку. На самом деле это no-op, но он позволяет системе статического ввода преобразовать Stream<Container>
в Stream<Host>
, например.
Применяя эти преобразования к вложенным циклам, мы получаем следующее:
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
return list;
}
Но подождите, там еще.
Последняя операция forEach
представляет собой несколько более сложную операцию map
, которая преобразует a Context
в ContextInfo
. Кроме того, они просто собираются в List
, поэтому мы можем использовать сборщиков, чтобы сделать это, вместо того, чтобы создавать и пустить список вперед, а затем заполнять его. Применение этих рефакторингов приводит к следующему:
public List<ContextInfo> list() {
final StandardServer server = getServer();
return Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.map(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
}
Обычно я стараюсь избегать многострочных lambdas (например, в финальной операции map
), поэтому я реорганизую его в небольшой вспомогательный метод, который принимает Context
и возвращает ContextInfo
. Это не сокращает код вообще, но я думаю, что он делает его более ясным.
UPDATE
Но подождите, еще больше.
Позвольте извлечь вызов service.getContainer()
в свой собственный элемент конвейера:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.filter(container -> container instanceof Engine)
.map(container -> (Engine)container)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
// ...
Это предоставляет повторение фильтрации на instanceof
, за которым следует сопоставление с литой. Это делается три раза. Похоже, что другому коду нужно будет делать подобные вещи, поэтому было бы неплохо извлечь этот бит логики в вспомогательный метод. Проблема состоит в том, что filter
может изменять количество элементов в потоке (отбрасывая те, которые не совпадают), но не может изменять их типы. И map
может изменять типы элементов, но не может изменить их число. Может ли что-то изменить как число, так и типы? Да, это наш старый друг flatMap
снова! Поэтому наш вспомогательный метод должен взять элемент и вернуть поток элементов другого типа. Этот обратный поток будет содержать один литой элемент (если он совпадает) или он будет пустым (если он не совпадает). Вспомогательная функция будет выглядеть так:
<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
(Это свободно основано на конструкции С# OfType
, упомянутой в некоторых комментариях.)
Пока мы находимся в нем, дайте возможность извлечь метод для создания ContextInfo
:
ContextInfo makeContextInfo(Context context) {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
}
После этих выходов конвейер выглядит следующим образом:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren()))
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(this::makeContextInfo)
.collect(Collectors.toList());
Ник, я думаю, и мы удалили страшный многострочный оператор lambda.
ОБНОВЛЕНИЕ: ВЫЗОВ БОНУСА
Еще раз, flatMap
- ваш друг. Возьмите хвост потока и перенесите его в последний flatMap
перед хвостом. Таким образом, переменная host
все еще находится в области видимости, и вы можете передать ее вспомогательному методу makeContextInfo
, который был изменен для принятия host
.
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren())
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(ctx -> makeContextInfo(ctx, host)))
.collect(Collectors.toList());