Мне пришлось обработать некоторый json, который мог быть в немного разных форматах (и где мне нужно было только подмножество данных json), и я использовал JsonPointer (от Джексона) для запроса json. Я написал нефункциональное решение проблемы, которое сработало для меня, но я хотел попробовать функциональный подход для учебных целей. В приведенной ниже тестовой программе вы можете увидеть два моих решения. Они оба работают, но функциональное решение стало довольно многословным, и у меня есть раздражающее предупреждение от Intellij относительно использования get() без проверки isPresent. Хотелось бы увидеть предложения по улучшению реализации функционала и рад видеть решения с использованием сторонних библиотек. Я полагаю, что основная проблема здесь заключается в том, как функционально смоделировать if-else-if-else, где каждая ветвь должна возвращать какое-то значение.
@Test
public void testIt() {
ObjectMapper om = new ObjectMapper();
ImmutableList.of(
"{ \"foo\": { \"key\": \"1\" } }",
"{ \"bar\": { \"key\": \"1\" } }",
"{ \"key\": \"1\" }")
.forEach(str -> {
try {
System.out.println("Non-functional: " + getNode(om.readTree(str)));
System.out.println("Functional: " + getNodeFunc(om.readTree(str)));
} catch (Exception e) {
throw new RuntimeException("", e);
}
});
}
private JsonNode getNode(JsonNode parentNode) {
JsonPointer jp1 = JsonPointer.compile("/foo");
JsonPointer jp2 = JsonPointer.compile("/bar");
if (!parentNode.at(jp1).isMissingNode()) {
return parentNode.at(jp1);
} else if (!parentNode.at(jp2).isMissingNode()) {
return parentNode.at(jp2);
}
return parentNode;
}
private JsonNode getNodeFunc(JsonNode parentNode) {
BiFunction<JsonNode, String, Optional<JsonNode>> findNode = (node, path) -> {
JsonPointer jp = JsonPointer.compile(path);
return node.at(jp).isMissingNode() ? Optional.empty() : Optional.of(node.at(jp));
};
return findNode.apply(parentNode, "/foo")
.map(Optional::of)
.orElseGet(() -> findNode.apply(parentNode, "/bar"))
.map(Optional::of)
.orElse(Optional.of(parentNode))
.get(); // Intellij complains here: Optional.get() without isPresent check
}
В этом нет ничего плохого, и я, вероятно, соглашусь с этим, мне просто было любопытно, как можно решить проблему, используя более функциональный подход. Узнать больше
то, что я чувствую, я узнаю снова и снова, пытаясь написать код в функциональном стиле на Java, насколько уродливее (самый) функциональный код на Java;)
Почему вы используете map(Optional::of)? findNode.apply() возвращает Optional<JsonNode>, поэтому вам не нужно использовать map().
@AndyTurner Поначалу я тоже так думал, но после того, как некоторое время использовал его с groovy, а теперь и с java, невозможность писать функционально кажется действительно неудобной и ограничительной. Трудно объяснить, пока вы не проработали это несколько лет и не проложили новые пути в своем мозгу... Исключительно расстраивает то, что такие вещи, как if (), try/catch и switch, не возвращают значение, приводящее к уродливому синтаксису вроде определение переменной в null перед ее установкой в переключателе или try/catch. if () использует ?: как функциональный эквивалент, но синтаксис уникален и неудобен, если вы разделяете строки.
@BillK это причина писать на Groovy, а не причина пытаться писать на Java. Я имею в виду, просто посмотрите на ответ Эндрю: этот код не является улучшением (я бы даже сказал, что это шаг или два назад). «Трудно объяснить, пока вы не поработаете несколько лет и не создадите новые пути в своем мозгу». Я работал над функциональными языками. Я думаю, что синтаксис Java не подходит для этой парадигмы.
@AndyTurner Попробуйте посмотреть на это с более высокого уровня - это читается как книга. Отфильтруйте его (избавьтесь от несуществующих), найдите первый, верните parentNode.at или parentNode, если он не существует. Для меня это удивительно легко читать, возможно, труднее сочинять. Сравните это с вопросом и посмотрите, что более читабельно. Я настоятельно рекомендую обниматься, а не бороться :)
@AndyTurner Java по-прежнему является языком OO с некоторыми функциональными особенностями. Это не чисто функциональный язык. Для этого перейдите на LISP или, может быть, на Haskell. Groovy — это просто Java со стероидами (их много) :)




я бы переписал на
private JsonNode getNodeFunc2(JsonNode parentNode) {
return Stream.of(JsonPointer.compile("/foo"), JsonPointer.compile("/bar"))
.filter(i -> !parentNode.at(i).isMissingNode())
.findFirst()
.map(parentNode::at)
.orElse(parentNode);
}
или
private JsonNode getNodeFunc3(JsonNode parentNode) {
return Stream.of(JsonPointer.compile("/foo"), JsonPointer.compile("/bar"))
.map(parentNode::at)
.filter(Predicate.not(JsonNode::isMissingNode))
.findFirst()
.orElse(parentNode);
}
или
private JsonNode getNodeFunc4(JsonNode parentNode) {
return Stream.of("/foo", "/bar")
.map(JsonPointer::compile)
.map(parentNode::at)
.filter(Predicate.not(JsonNode::isMissingNode))
.findFirst()
.orElse(parentNode);
}
потому что кусок
if (!parentNode.at(jp1).isMissingNode()) {
return parentNode.at(jp1);
} else if (!parentNode.at(jp2).isMissingNode()) {
return parentNode.at(jp2);
}
является дублированием кода и может быть аккуратно обработано циклом:
for (JsonPointer jsonPointer : jsonPointers) {
JsonNode kid = parentNode.at(jsonPointer);
if (!kid.isMissingNode()) {
return kid;
}
}
Predicate.not() - это Java 11? К сожалению, мы все еще используем Java 8 в проекте, в котором я работаю, но планируем обновить его в ближайшем будущем. Очень полезный ответ, я узнал из него, что было моей целью с этим вопросом, спасибо!
Просто задумайтесь, функция getNode — это идеальный функциональный код:
Что не так с нефункциональным кодом? Выглядит отлично для меня.