Java优化:使用临时变量声明类变量VS.

Java optimization: declaring class variables VS using temporary variables

首先,如果我的英语不完美,请原谅,但我不是来自一个讲英语的国家(西班牙),所以…

好吧,问题是。创建类时,?尽你所能使用临时变量是一个好的实践,还是最好将变量声明为类变量,以保持事情的清晰?

我将使用一个简单的类spriteSheet为您提供一个示例。是一个很短很流行的类,几乎在爪哇的2D游戏中使用。

以下是我所观看的教程的创建者最初计划的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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约定。看过之后,我想我可以稍微改进一下。这是我对同一个班级的看法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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迟早会处理它们,释放内存。-将数组标记为final,因为它将在运行时的其余时间保持不变。-将大小常量拆分为宽度和高度,以防我决定使用一些非方形的sprite表。-声明w和h实际上是一个很好的想法,因为参数中的调用方法通常不利于执行速度(或者这是我在某些地方读到的)。-由于0被多次使用,我相信将它声明为一个变量将有助于提高执行速度(只是一点点,可能无论如何都不会被注意到)。

基本上就是这样。请注意,我是一个学生,可能我犯了一些非常n00b的错误,这就是为什么我想问这里,因为我确信有很多经验丰富的程序员。

坦率地说,我并不真正关心SpriteSheet类,我对优化的质量更感兴趣。

?我是否改进了一些东西或者把它们做得最糟(使它们变得更慢、更不可读、将来更难维护、做编译器无论如何都会做的事情…)

对不起,如果我的问题太长太模糊,我的第一个问题是不是太简单了;)

事先谢谢。

编辑:

我只是在稍作休息后才读到它,但它没有任何意义(您是否看到我正在解析width和height参数以加载(),并且从未使用它们?)

我认为应该是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}
}

}

我才意识到我真的不需要这个方法。一切都可以在构造函数中完成。


Or is better to declare your variables as class variables, just in order to keep things clear?

我认为您的意思是"属性"(实例变量),而不是静态(类)变量。将所有内容声明为实例变量将使事情变得非常不清楚。

简短回答:对于跨不同方法共享的数据,仅在严格必要时使用局部变量并创建属性。此外,局部变量访问速度比属性稍快。


假设您是根据示例而不是class/static变量来表示实例变量。(如果你真的是指static变量,那么你还有很多其他的问题要处理…)

让我们从这个问题的"优化"方面开始。

首先要说的是,这两种方法之间的差异可能是微不足道的。很可能它不会对程序的性能产生显著的影响。在您的例子中,我可以想象您在任何时候都最多拥有该类的几十个实例,因此内存使用的差异最多为几千字节。

尽管如此,还是有区别的。当您将这些字段声明为实例字段时,它们将在对象的生存期内存在(并占用堆内存)。相反,当封闭方法调用结束时,局部变量就不再存在。因此,从长远来看,使用局部变量可能会占用较少的内存。

但这里的重要问题是可读性、可维护性和正确性。

如果您将本地"临时"变量转换为实例变量,那么就有了不同方法的范围…或对同一方法的不同调用…通过使用实例变量互相干扰。请注意,在某些用例中,干扰是不可避免的。例如,当两个不同的线程同时对同一对象调用同一个方法时,或者当一个方法直接或间接调用自身时,即递归。

这种情况可能发生,这使得代码更难阅读和维护。(除非你对代码非常熟悉,并且正在跟踪任何人对代码所做的所有更改…你不能确定有什么东西打破了你的假设…你得检查一下。)

相反,对于局部变量,您没有这些问题。它们保证在当前线程或其他线程上对任何其他方法调用都不可见。

简而言之,将变量声明为实例变量"只是为了让事情保持清晰",实际上会产生相反的效果。这将使事情变得不那么清楚。

最后,程序员站点上有一个与此相关的有趣问题:

  • https://softwarengineering.stackexchange.com/questions/163163/name-for-this-antpattern-fields-as-local-variables

我在回答中得出的结论是,这并不真正符合"反模式",因为它不是一个设计模式。但尽管如此,这确实很糟糕。


When creating a class, ?is a good practice to use temporary variables all you can, or is better to declare your variables as class variables, just in order to keep things clear?

如果信息只与当前运行的方法相关,则将其存储在尽可能小的范围内的局部变量中。

如果有关该对象的信息需要比任何一个方法调用都长,并且不能从其他源派生,则将其存储在字段中。

如果有关对象的信息可以从其他来源获得,但继续派生它效率低下或不方便,则记录它与其他数据的关系,可能将其标记为transient,并存储在字段中。

如果你找到一个有很多字段的类,那么也许是时候把它分成更小的类了。同样,如果一个方法有许多局部变量,那么也许是时候尝试将它分解成更小的方法了。