Необязательно против производительности if / else-if java 8 - PullRequest
7 голосов
/ 21 мая 2019

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

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 +?

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

Ответы [ 4 ]

14 голосов
/ 21 мая 2019

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

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

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


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

5 голосов
/ 21 мая 2019

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

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

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

3 голосов
/ 26 мая 2019

ТЛ; др

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

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

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

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

A троичный оператор представляет собой сжатый if-then-else, объединенный в однострочник.

result = test ? valueToUseIfTestIsTrue : valueToUseIfTestIsFalse
* * 1 022 Пример: * 1 023 *
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 имеет ту же проблему, также вызывая ваш геттер дважды.

Производительность

как эти два сравниваются с точки зрения производительности

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

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

0 голосов
/ 21 мая 2019

Я согласен с подходом не чрезмерного использования Optionals, они быстро запутываются, как вложенные потоки для итерации по многомерным массивам.

Но, ради любопытства, я создал следующее приложение для вычисления коэффициента увеличения производительности, который у Optional превышает If/Else. Это постоянно дает мне менее 10 мс / миллион пробежек на моей машине. Вот код, если вы хотите поиграть с ним, и, возможно, использовать его для аналогичных сравнений:

import java.util.Optional;
import java.util.concurrent.Callable;

public class OptionalVsIfElse {

    public static long time(double numberOfRuns, Callable<Object> tested) throws Exception {
        long start = System.currentTimeMillis();
        for (long idx = 0; idx < numberOfRuns; idx++) 
            tested.call();

        return System.currentTimeMillis() - start;
    }

    public static void main(String[] args) throws Exception {
        double numberOfRuns = 10000000;

        Object target = new Object();
        Object comparison = new Object();

        long ifTime = time(numberOfRuns, () -> {
            return (target != null) ? target : comparison; 
        });

        long optionalTime = time(numberOfRuns, () -> {
            return Optional.ofNullable(target).orElse(comparison);
        });

        double performanceHitRatio = (double)(optionalTime - ifTime) * 1_000_000 / numberOfRuns;
        System.out.println(String.format("Performance Hit Ratio: %.2f ms / million runs", performanceHitRatio));
    }
}
...