Преждевременная оптимизация в Java: когда использовать "x = foo.getX()" просто "foo.getX()"

Когда я нахожу себя одним и тем же методом getter несколько раз, следует ли это считать проблемой? Лучше ли [всегда] назначать локальную переменную и вызывать только один раз?

Я уверен, что ответ, конечно, "это зависит".

Я больше беспокоюсь о более простом случае, когда getter - это просто метод типа "pass-on-the-value-of-a-private-variable". то есть нет дорогостоящих вычислений, никаких подключений к базе данных и т.д.

Мой вопрос "лучше ли" относится как к читаемости кода (стилю), так и к производительности. т.е. большая часть производительности имеет:

SomeMethod1(a, b, foo.getX(), c);
SomeMethod2(b, foo.getX(), c);
SomeMethod3(foo.getX());

против

X x = foo.getX();
SomeMethod1(a, b, x, c);
SomeMethod2(b, x, c);
SomeMethod3(x);

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

Спасибо.

Ответы

Ответ 1

Выбор не должен касаться производительности, а читаемости кода.

Когда вы создаете переменную, вы можете присвоить ей имя, которое оно заслуживает в текущем контексте. Когда вы используете одно и то же значение более одного раза, оно, безусловно, имеет реальное значение, больше, чем имя метода (или, что еще хуже, цепочки методов).
И это действительно лучше читать:

String username = user.getName();
SomeMethod1(a, b, username, c);
SomeMethod2(b, username, c);
SomeMethod3(username);

чем

SomeMethod1(a, b, user.getName(), c);
SomeMethod2(b, user.getName(), c);
SomeMethod3(user.getName());

Ответ 2

Для простых геттеров - те, которые только что возвращают значение - HotSpot вставляет его в вызывающий код, поэтому он будет таким быстрым, насколько это возможно.

У меня, однако, есть принцип сохранения утверждения на одной строке, что очень часто приводит к тому, что выражения, подобные "foo.getBar()", слишком длинны, чтобы соответствовать. Тогда для меня это более читаемо - чтобы извлечь его в локальную переменную ( "Bar bar = foo.getBar()" ).

Ответ 3

Это могут быть две разные вещи.

Если GetX не является детерминированным, то первый будет давать разные результаты, чем второй

Лично я бы использовал второй. Это более очевидно и менее излишне подробный.

Ответ 4

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

Ответ 5

Это зависит от того, что на самом деле делает getX(). Рассмотрим этот класс:

public class Foo {
    private X x;

    public X getX() { return x; }
}

В этом случае, когда вы делаете вызов foo.getX(), JVM оптимизирует его до конца до foo.x (как в прямой ссылке на foo частное поле, в основном указатель на память). Однако, если класс выглядит следующим образом:

public class Foo {
    private X x;

    public X getX() { return cleanUpValue(x); }

    private X cleanUpValue(X x) {
        /* some modifications/sanitization to x such as null safety checks */
    }
}

JVM не может фактически внедрить его как можно более эффективно, поскольку с помощью конструктивного контракта foo он должен дезинфицировать x, прежде чем передавать его.

Подводя итог, если getX() действительно ничего не делает за пределами возвращения поля, то нет никакой разницы после того, как начальная оптимизация будет работать с байт-кодом в том, вы вызываете метод один или несколько раз.

Ответ 6

В большинстве случаев я использовал бы getX, если бы это был только один раз, и создать для него var для всех остальных случаев. Часто просто для сохранения ввода.

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

Ответ 7

Я обычно сохраняю его локально, если:

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

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

  • Я хочу переименовать переменную, чтобы лучше соответствовать задаче.

  • Тестирование указывает на значительное повышение производительности.

В противном случае мне нравится возможность получать текущие значения и более низкий уровень абстракции вызовов методов.

Ответ 8

Необходимо учитывать две вещи:

  • Есть ли у вызова getX() какие-либо побочные эффекты? Следуя установленным шаблонам кодирования, геттер не должен изменять объект, на который он вызывается, в большинстве случаев побочный эффект отсутствует. Таким образом, семантически эквивалентно вызов getter один раз и сохранение значения локально vs. вызов getter несколько раз. (Эта концепция называется idempotency - не имеет значения, вы вызываете метод один или несколько раз, эффект на данные точно такой же.)

  • Если у получателя нет побочного эффекта, компилятор может безопасно удалить последующие вызовы на получатель и создать временное локальное хранилище самостоятельно - таким образом, код остается ультра читабельным, и у вас есть все преимущества скорости от вызывая геттер только один раз. Это тем более важно, если геттер не просто возвращает значение, но должен получить/вычислить значение или выполнить некоторые проверки.

Предполагая, что ваш получатель не изменит объект, на котором он работает, возможно, более читаемо иметь несколько вызовов getX() - и благодаря компилятору вам не нужно торговать с производительностью для удобства чтения и обслуживания.