Необязательная производительность против производительности if/else-if java 8

Здравствуйте, у меня есть два примера кода

операторы if/else if/else

private Object getObj(message) {
        if (message.getA() != null)
            return message.getA();
        else if (message.getB() != null)
            return message.getB();
        else if (message.getC() != null)
            return message.getC();
        else return null;
}

Необязательные операторы

private Optional<Object> wrap(Object o){
    return Optional.ofNullable(o);
}

private Object getObj(message) {
    return  wrap(message.getA())
            .orElseGet(() -> wrap(message.getB())
            .orElseGet(() -> wrap(message.getC())
            .orElse(null)));
}

Итак, мой вопрос: как эти два сравниваются с точки зрения производительности (у меня есть около 15-20 операторов if-else в реальном коде)?

Стоит ли проводить рефакторинг читаемости кода и производительности или это неправильное использование опций?

Также каково снижение производительности в случае, если операторы if/else-if выросли до 100+?

заранее спасибо

Лично мне цепочку легче читать, но мои коллеги считают меня странным, поэтому я думаю, что это зависит. с точки зрения производительности это на 99% не имеет значения для вас; если профайлер не докажет обратное

Eugene 21.05.2019 11:31
Стоит ли проводить рефакторинг читаемости кода и производительности или это неправильное использование опций? Это зависит от ваших потребностей
David Pérez Cabrera 21.05.2019 11:31

Вы заботитесь о выполнении 100+ проверок. Я бы предпочел беспокоиться о читаемости более 100 операторов if/else.

Arnaud Denoyelle 21.05.2019 11:32

Как насчет подхода создания Stream всех этих Optional и возврата с использованием findFirst с проверкой isPresent?

Naman 21.05.2019 11:33

или Stream без Optional... но вы, вероятно, скоро закончите использовать Stream с Supplier тогда... не знаю, действительно ли это того стоит... Я думаю, что if/else-утверждения намного читабельнее... или, по крайней мере, вы сразу знаете, что Зачем вы возвращаете его, тогда как с Optional это довольно скрыто... не говоря уже о том, что это в основном злоупотребление самим Optional...

Roland 21.05.2019 11:39

Если вы не знаете, что такое преждевременная оптимизация, найдите его.

Ole V.V. 21.05.2019 11:46

FYI Java 9 имеет Optional#or метод, позволяющий избежать wrap того, что вы делаете.

wilmol 22.05.2019 04:13

@wilmol or хорош, но в данном случае не спасает от wrap. Синтаксис будет return wrap(message.getA()) .or(() -> wrap(message.getB())) .or(() -> wrap(message.getC())) .orElse(null);. Кронштейны расположены немного иначе. wrap используется так же.

Ole V.V. 22.05.2019 05:03

возможно, геттеры должны вернуться Optional (вторая строка ответа Лино) - мы также не знаем, насколько дорог вызов геттеров или даже его побочные эффекты (причина против вышеприведенного решения if-else)

user85421 26.05.2019 07:28
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
15
9
5 707
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

Не используйте Optional для условной логики.

Они были разработаны, быть возвращенным из метода, чтобы указать потенциально отсутствующее значение.

То, что вы можете красиво связать их в одну строку, не означает, что это понятно. Кроме того, вы буквально ничего не получаете. Накладные расходы на производительность может быть значительны. В худшем случае N объекты создаются, а затем удаляются. Просто оставайтесь со своими «обычными» if-else цепями.


Вместо того, чтобы искать способы сделать ваш текущий код более читабельным, сделайте шаг назад и спросите себя Зачем, вам нужно 15-20 операторов if-else. Можете ли вы разделить некоторую логику? Зачем вообще нужен геттер для стольких разных полей с потенциально разными типами? и т.п.

Спасибо за вклад, Лино, на самом деле это основной метод проверки, который должен выполнять все эти проверки (я знаю, что это, вероятно, проблема разделения обязанностей, но на данный момент логика рефакторинга очень утомительна).

Panos K 21.05.2019 11:38

@PanosK Если бы вы могли включить в свои вопросы более конкретные причины, почему вам это нужно (звонок на сайт, примеры и т. д.), Я могу дать более конкретный ответ.

Lino 21.05.2019 11:41

Думаю, я получил свой ответ «Не используйте опционы для условной логики», я буду придерживаться if/else и попытаюсь реорганизовать логику, чтобы уменьшить их в будущем. Спасибо, Лино (все еще не получаю отрицательных голосов по моему вопросу).

Panos K 21.05.2019 11:45

Есть и третья форма (допускающая еще некоторые вариации).

return Stream.<Supplier<Object>>of(message::getA, message::getB, message::getC)
        .map(Supplier::get)
        .filter(Objects::nonNull)
        .findFirst()
        .orElse(null);

Вероятно, наименее гибкий и эффективный на данный момент, но понятный.

Вам нужно будет написать Stream.<Supplier<Object>>of(…), чтобы это скомпилировалось.

Didier L 22.05.2019 01:06

Отмечу, что использование поставщиков в потоке делает оценку этого столь же ленивой, как и в вопросе: getB вызывается только в том случае, если getA вернул null и т.д.

Ole V.V. 22.05.2019 09:50

тл;др

Если ваша цель — сжатый код, используйте троичную цепочку. Производительность, вероятно, идентична серии операторов if-then-else.

        ( this.getA() != null ) ? this.getA()
                : ( this.getB() != null ) ? this.getB()
                : ( this.getC() != null ) ? this.getC()
                : null;

Тернарная цепочка

Как правильно указано в Ответ от Лино, вы пытаетесь вывести Optional за пределы их первоначальной цели (возвращая значения в лямбда-выражениях и потоках). Как правило, лучше всего использовать Optional только с оператором return и только тогда, когда вы хотите прояснить, что null является допустимым значением для возврата. См. этот ответ Брайана Гетца.

тернарный оператор — это сжатый if-then-else, объединенный в однострочный.

result = test ? valueToUseIfTestIsTrue : valueToUseIfTestIsFalse

Пример:

Color color = isPrinterMonochrome ? Color.GREY : Color.GREEN ; 

Используйте цепочку троичных операторов.

Итак, это:

    if ( this.getA() != null )
        return this.getA();
    else if ( this.getB() != null )
        return this.getB();
    else if ( this.getC() != null )
        return this.getC();
    else return null;

… становится следующим:

    return
            ( this.getA() != null ) ? this.getA()
                    : ( this.getB() != null ) ? this.getB()
                    : ( this.getC() != null ) ? this.getC()
                    : null;

Пример кода.

public String getA ()
{
    // return "A";
    return null;
}

public String getB ()
{
    // return "B";
    return null;
}

public String getC ()
{
    return "C";
    // return null;
}

public String getABC ()
{
    if ( this.getA() != null )
        return this.getA();
    else if ( this.getB() != null )
        return this.getB();
    else if ( this.getC() != null )
        return this.getC();
    else return null;
}

public String getABCTernary ()
{
    return
            ( this.getA() != null ) ? this.getA()
                    : ( this.getB() != null ) ? this.getB()
                    : ( this.getC() != null ) ? this.getC()
                    : null;
}

Запустите этот пример кода.

String s = this.getABCTernary();
System.out.println( "s: " + s );

C

Плюсы и минусы

  • Преимущество троичной цепочки — сжатый код, свернутый в одну строку.
  • Недостатком является то, что в этой конкретной ситуации вы дважды вызываете свой метод получения только для того, чтобы получить одно значение. Это не проблема для простого метода получения переменной, но влияет на производительность, если метод получения требует много времени, например вызов удаленных веб-служб. И каскадный if-then-else имеет ту же проблему, также вызывая ваш геттер дважды.

Представление

how these two compare in terms of performance

Тернарный оператор в Java — "короткое замыкание", что означает, что левая или правая сторона, которая соответствует результатам теста, является единственным вызываемым кодом. В нашем коде здесь, если getA возвращает ненулевое значение, это значение возвращается немедленно. Дальнейшие вызовы getB и getC никогда не выполняются. Так что в этом отношении производительность цепочечной тройки такая же, как у каскадного оператора if-then-else: выигрывает первое совпадение, дальнейших вызовов нет.

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

Пару дней назад я провел тщательный анализ производительности. Это оказывает огромное влияние на производительность. С AdoptOpenJDK операторы if выполняются до 10 раз быстрее. Когда JIT-компилятор работает в горячем режиме, штраф снижается до 20%.

GraalVM работает лучше: 3-кратное замедление с холодной JVM, и после того, как компилятору дается достаточно времени, чтобы сделать свое волшебство, также снижается производительность на 20%.

Однако реальный вопрос заключается в том, какая версия лучше для чтения и обслуживания приложения. Если вы похожи на меня, вам проще прочитать утверждение if, но есть и люди, предпочитающие функциональный подход.

Если вы готовы к глубокому-глубокому погружению, приглашаю вас на прочитайте мой подробный анализ о производительности и реализации Optional.orElseGet() и его друзей.

По моему мнению, после примерно 20 лет коммерческого опыта у меня сложилось мнение, что погоня за читабельностью — это абсолютная глупость, и в то же время намеренное написание запутанного кода — это зло.

Я знаю, что это полностью противоречит общественному мнению.

Однако это необходимо осознать каждому...

  1. То, что читается одним человеком, не обязательно читается другим. Даже в этой ветке у нас разные мнения о том, читабельнее ли if или Optional. Такого рода дебаты будут происходить независимо от того, в каких конструкциях или ситуациях мы находимся.
  2. Если мы возьмем вариант if, который более эффективен, чем функциональный подход, каждый и каждый раз, то люди, читающие этот код, привыкнут к нему и найдут его БОЛЕЕ ЧИТАЕМЫЙ — потому что это стиль, к которому они теперь привыкли.
  3. Эффективный код не обязательно должен быть «сложным для чтения»… но это возвращает нас к пунктам 1 и 2. Разработчики должны действительно знать основы языка, который они используют, и писать код, подходящий для этого языка. , а не пытаться формировать «английские предложения» в своем коде.

Итак, по сути: идите с if... используйте НЕТ это Optional!

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