Функциональный вариант получения значения из if-elseif-else в Java

Мне пришлось обработать некоторый 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
}

Что не так с нефункциональным кодом? Выглядит отлично для меня.

Andy Turner 22.01.2019 21:07

В этом нет ничего плохого, и я, вероятно, соглашусь с этим, мне просто было любопытно, как можно решить проблему, используя более функциональный подход. Узнать больше

Eric Lilja 22.01.2019 21:09

то, что я чувствую, я узнаю снова и снова, пытаясь написать код в функциональном стиле на Java, насколько уродливее (самый) функциональный код на Java;)

Andy Turner 22.01.2019 21:10

Почему вы используете map(Optional::of)? findNode.apply() возвращает Optional<JsonNode>, поэтому вам не нужно использовать map().

Bob Dalgleish 22.01.2019 21:11

@AndyTurner Поначалу я тоже так думал, но после того, как некоторое время использовал его с groovy, а теперь и с java, невозможность писать функционально кажется действительно неудобной и ограничительной. Трудно объяснить, пока вы не проработали это несколько лет и не проложили новые пути в своем мозгу... Исключительно расстраивает то, что такие вещи, как if (), try/catch и switch, не возвращают значение, приводящее к уродливому синтаксису вроде определение переменной в null перед ее установкой в ​​переключателе или try/catch. if () использует ?: как функциональный эквивалент, но синтаксис уникален и неудобен, если вы разделяете строки.

Bill K 22.01.2019 22:13

@BillK это причина писать на Groovy, а не причина пытаться писать на Java. Я имею в виду, просто посмотрите на ответ Эндрю: этот код не является улучшением (я бы даже сказал, что это шаг или два назад). «Трудно объяснить, пока вы не поработаете несколько лет и не создадите новые пути в своем мозгу». Я работал над функциональными языками. Я думаю, что синтаксис Java не подходит для этой парадигмы.

Andy Turner 22.01.2019 22:15

@AndyTurner Попробуйте посмотреть на это с более высокого уровня - это читается как книга. Отфильтруйте его (избавьтесь от несуществующих), найдите первый, верните parentNode.at или parentNode, если он не существует. Для меня это удивительно легко читать, возможно, труднее сочинять. Сравните это с вопросом и посмотрите, что более читабельно. Я настоятельно рекомендую обниматься, а не бороться :)

Bill K 22.01.2019 22:24

@AndyTurner Java по-прежнему является языком OO с некоторыми функциональными особенностями. Это не чисто функциональный язык. Для этого перейдите на LISP или, может быть, на Haskell. Groovy — это просто Java со стероидами (их много) :)

fps 23.01.2019 01:38
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
8
343
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

я бы переписал на

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 в проекте, в котором я работаю, но планируем обновить его в ближайшем будущем. Очень полезный ответ, я узнал из него, что было моей целью с этим вопросом, спасибо!

Eric Lilja 22.01.2019 23:35

Просто задумайтесь, функция getNode — это идеальный функциональный код:

  • Выход зависит только от входных параметров и внутреннего алгоритма.
  • У него нет побочных эффектов (он ничего не читает из внешнего мира и ничего не записывает во внешний мир)
  • Он всегда будет возвращать один и тот же вывод из одного и того же ввода.

Другие вопросы по теме