Оптимизация Java: объявление переменных класса VS с использованием временных переменных

Прежде всего, извините меня, если мой английский не идеален, но я не из англоязычной страны (Испания), так что...

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

Я приведу вам пример, используя простой класс SpriteSheet. Это очень короткий и популярный класс, используемый почти в каждой 2D-игре на Java.

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

public class SpriteSheet {

private String path;
private final int SIZE;
public int[] spriteSheetPixels;

public SpriteSheet(String path, int size) {
this.path = path;
SIZE = size;

spriteSheetPixels = new int[SIZE * SIZE];

load();
}

private final void load() {
try {
    BufferedImage image = ImageIO.read(SpriteSheet.class
        .getResource(path));
    int w = image.getWidth();
    int h = image.getHeight();
    image.getRGB(0, 0, w, h, spriteSheetPixels, 0, w);
} catch (IOException e) {
    e.printStackTrace();
}
}

}

Дело в том, что он занимается обычным классом, следуя всем соглашениям Java, насколько мне известно. Посмотрев на него, я подумал, что могу его немного улучшить. Вот моя версия того же класса:

public final class SpriteSheet {

public final int[] spriteSheetPixels;

public SpriteSheet(final String path, final int width, final int height) {
spriteSheetPixels = new int[width * height];

load(path, width, height);
}

private final void load(final String path, final int width, final int height) {
try {
    BufferedImage image = ImageIO.read(SpriteSheet.class
        .getResource(path));

    final int w = image.getWidth();
    final int h = image.getHeight();
    final byte ZERO = 0;

    image.getRGB(ZERO, ZERO, w, h, spriteSheetPixels, ZERO, w);
} catch (IOException e) {
    e.printStackTrace();
}
}

}

На всякий случай, если вам не хочется уделять слишком много внимания, я попытаюсь возобновить то, что я изменил и почему: - Добавлено "final" в объявление класса, так как я не думаю, что мне это когда-нибудь понадобится. чтобы создать его. - Удалены все переменные класса, кроме массива, так как это единственное, что я буду использовать в этом классе. Мне кажется, что остальные переменные, объявленные как переменные класса, — это пустая трата памяти. Если они временные, если я не ошибаюсь, они будут использованы, и тогда GC рано или поздно позаботится о них, освободив память. - Массив помечен как окончательный, потому что он останется таким же до конца времени выполнения. - Разделите константу SIZE на ширину и высоту, на тот случай, если я решу использовать неквадратные листы спрайтов. - Объявление w и h на самом деле является хорошей идеей, так как вызов методов в параметрах обычно плохо влияет на скорость выполнения (или это то, что я читал в некоторых местах). - Поскольку 0 используется несколько раз, я считаю, что объявление его как переменной поможет улучшить скорость выполнения (совсем чуть-чуть, вероятно, все равно не будет заметно).

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

Имейте в виду, что меня не особо волнует класс SpriteSheet, меня больше интересует качество моих оптимизаций.

¿Улучшил ли я что-то или ухудшил (сделал вещи на самом деле медленнее, менее читабельными, труднее поддерживать в будущем, сделал то, что компилятор все равно будет делать...)?

Извините, если мой вопрос слишком длинный и слишком расплывчатый, это мой первый вопрос, так что полегче со мной;)

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

РЕДАКТИРОВАТЬ:

Я только что прочитал это после небольшого перерыва, и это не имеет никакого смысла (вы видели, что я анализирую параметры ширины и высоты для загрузки () и никогда их не использую?)

Вот как я считаю должно быть:

public final class SpriteSheet {

public final int[] spriteSheetPixels;

public SpriteSheet(final String path, final int width, final int height) {
final byte ZERO = 0;
spriteSheetPixels = new int[width * height];

try {
    BufferedImage image = ImageIO.read(SpriteSheet.class
        .getResource(path));

    image.getRGB(ZERO, ZERO, width, height, spriteSheetPixels, ZERO,
        width);
} catch (IOException e) {
    e.printStackTrace();
}
}

}

Только что понял, что мне не нужен этот метод. Все можно сделать в конструкторе.


person Best Bloody Day    schedule 30.03.2014    source источник
comment
Переменные класса определяют атрибуты объекта. Если вы считаете, что переменная не влияет на атрибуты этого объекта, то лучше оставить ее временной. Все сводится к тому, как вы хотите спроектировать свой класс.   -  person ata    schedule 30.03.2014
comment
Да, и иногда самое сложное — это понять, действительно ли вам понадобятся эти атрибуты (например, использовать их для чего-то, доступного из другого класса).   -  person Best Bloody Day    schedule 30.03.2014


Ответы (3)


Предполагая, что вы имеете в виду переменные экземпляра в соответствии с вашим примером, а не переменные класса/static. (Если вы действительно имели в виду static переменных, то у вас есть много других проблем, с которыми нужно справиться...)


Давайте начнем с аспекта «оптимизации» этого Вопроса.

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

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


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

Если вы превратите локальные «временные» переменные в переменные экземпляра, тогда появится возможность для разных методов... или разных вызовов одного и того же метода... мешающих друг другу посредством использования переменных экземпляра. Обратите внимание, что в некоторых случаях помехи неизбежны. Например, когда два разных потока одновременно вызывают один и тот же метод для одного и того же объекта или когда метод прямо или косвенно вызывает сам себя; то есть рекурсия.

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

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

Короче говоря, объявление переменных как переменных экземпляра "просто для того, чтобы все было ясно" на самом деле будет иметь обратный эффект. Это сделает вещи значительно менее понятными.


Наконец, на сайте Программистов появился интересный Вопрос по этому поводу:

Мой вывод в моем ответе заключался в том, что это на самом деле не квалифицируется как «анти-шаблон», потому что это не шаблон проектирования. Но тем не менее это действительно плохо.

person Stephen C    schedule 30.03.2014
comment
Итак, вы хотите сказать, что, возможно, кодирование класса таким образом безопаснее и быстрее во время выполнения, НО это будет очень сложно понять (например, если я проведу несколько недель, не взглянув на код, а затем, когда я попробуйте что-то изменить или расширить программу, я не смогу понять свой собственный код). Может быть, добавление javadoc или комментариев поможет с будущими изменениями в коде? - person Best Bloody Day; 30.03.2014
comment
Нет. Я не говорю, что это будет безопаснее и быстрее во время выполнения. Это определенно не будет безопаснее, и я сомневаюсь, что это можно будет измерить быстрее. - person Stephen C; 30.03.2014
comment
Извините, но сразу уточню, речь идет о вашем примере про антипаттерн или о моем примере (я там просто теряюсь)? И, пожалуйста, не могли бы вы немного подробнее объяснить неудобство моего примера? Я спрашиваю, потому что в моем примере я не вызываю метод из другого метода, а просто метод из конструктора. И, наконец, суть примера в том, чтобы заполнить массив пикселей. Я не вижу (помните, я новичок) никакой опасности, как сейчас. - person Best Bloody Day; 30.03.2014
comment
1) Я говорю о вашем примере, где вы превратили локальные переменные в переменные экземпляра. 2) Вопросы удобочитаемости, ремонтопригодности и корректности... не удобства. 3) В таком простом примере, как ваш, код достаточно прост для понимания. Но для более сложного кода это не так. Я, вероятно, не смогу объяснить это, пока вы сами не прочтете и не поймете большую и сложную программу. - person Stephen C; 30.03.2014

Или лучше объявить ваши переменные как переменные класса, просто чтобы все было ясно?

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

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

person Óscar López    schedule 30.03.2014
comment
Стандартный язык в Stack Overflow — английский, но если это поможет прояснить ситуацию, просто знайте, что … aquí también se habla español :) - person Óscar López; 30.03.2014
comment
Да, именно это я и хотел сказать, просто не смог правильно перевести. Спасибо (gracias jeje). Кстати, немного подредактирую код, так как скопировал не ту реализацию. - person Best Bloody Day; 30.03.2014
comment
Атрибуты не являются подходящей терминологией для Java. JLS использует поле термина. - person Mike Samuel; 30.03.2014
comment
Это так расстраивает @MikeSamuel, вся эта путаница из-за того, что я изучал Java на испанском языке, а теперь пытаюсь объясниться на английском. Спасибо за ссылку. - person Best Bloody Day; 30.03.2014
comment
Не извиняйся. Твой английский в порядке. Это мы, носители английского языка, должны извиниться за то, что никогда не исправили нашу орфографию, возникшую в результате тысячелетнего крушения поезда между романским языком и германским языком, а кельтские языки кричали со стороны. - person Mike Samuel; 30.03.2014

При создании класса рекомендуется использовать все возможные временные переменные, или лучше объявить ваши переменные как переменные класса, просто для того, чтобы все было ясно?

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

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

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

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

person Mike Samuel    schedule 30.03.2014
comment
Ссылаясь на пример, я думаю, что сейчас мне нужен только массив, поэтому имеет смысл оставить этот массив единственным полем в классе. Но, очевидно, я не могу предвидеть, понадобится ли мне что-то еще из этого класса в будущем (может быть, мне что-то понадобится, и тогда мне придется превратить некоторые локальные переменные в поля. - person Best Bloody Day; 30.03.2014