我应该在可重复使用的方法内部还是外部声明变量?

6

我以String为例,但它可以被替换为Object > MB的内存。

经常会做这个操作:

private static String mTempString = "";

private static void SomeMethod()
{
    mTempString = "Whatever Result";
}

现在我的问题是,如果我这样写的话:

private static void SomeMethod()
{
    String mTempString = "Whatever Result";
}

如果将其放在循环中使用(例如,每秒执行数百次),Java 是否会像示例一样知道如何管理内存?那么内存的效率是否相同。 (很抱歉目前我无法测试此内容)

从内存效率角度来看(不考虑它们是小变量的事实),哪个更加节省?

--编辑--
在这里找到了一篇很好的文章,解释了这个问题:http://www.cs.berkeley.edu/~jrs/4/lec/08


在第二个例子中,变量具有局部作用域。在第一个例子中,变量具有全局作用域。因此,在第二个例子中,该变量无法在函数外部访问。而在前者中可以实现。所以,这取决于您的需求。 - Bill
1
这是一个关于内存的问题,变量会被重新创建吗? - Oliver Dixon
对于前者,不需要,因为它是静态的并具有全局范围。对于后者,这取决于编译器是否将继续使用相同的内存位置或新的内存位置。在我看来,使用第二个选项不应该影响性能。 - Bill
1
@Bill - 该方法可以有多个并发调用者。通过反射和JNI,这些调用者可能在编译类时不需要编译器的知识而存在。除非编译器完全优化掉本地变量,否则我认为每次调用都会在激活帧上有一个单独的副本。 - Andy Thomas
@AndyThomas-Cramer 是的,没错。但如果变量具有局部作用域,则不应将其设为全局变量。据我所知,第二种情况不应该有任何性能损失。 - Bill
1
使用本地变量比使用实例或静态变量更有效率,除了使用尽可能窄的范围的其他原因。 (请记住,“LO”变量本身并不大-它只是一个指针。)但是,如果您通过“new”重新分配(通过“new”)大型数组或类似物品,而不是重用“共享”副本,则该重新分配将会有一定的性能损失(虽然很难说有多少)。 - Hot Licks
6个回答

9
尽可能使您的变量范围狭窄,这非常重要,原因如下:
1. 可读性。如果您在三个不同的地方使用该变量且其值有四种不同的情况(难以想象),那么您会很难确定该变量应该承担的目的。
2. 减少错误。如果将单个变量保持在单个、定义明确的范围内,您可以减少应用程序中可能出现的错误数量。假设您有一个字符串,在两种方法中都希望它是某个值,但实际上它却是完全不同的另一个值。
3. 目的的意图。 readability部分中已经提到了这一点,但如果一个静态变量不断被重新定义,它的预期目的就变得不清晰了。通常,静态方法和变量可以独立于对象的状态使用,因此如果对象的状态影响静态变量的值,则意图就变得混淆了。
我不会过多担心内存效率(除非您有大量的字符串,但即使这样,我也会说您还有第二件事要担心)。
*优化的第一条规则:先不要进行优化。 *优化的第二条规则(仅适用于专家级人物!):现在还不要进行优化。

我以String作为例子,但它可以被替换为Object> MB的内存。 - Oliver Dixon
如果你自定义对象的一个实例正在使用兆字节的内存,那么现在你需要担心[三件事情](http://en.wikipedia.org/wiki/God_object)。 - Makoto
这里有正式定义的规范 链接,但除非你必须关注它,否则不要让自己担心。 - Makoto
嗯,你改变了这个问题的目标。此外,你的问题本身有点奇怪——静态变量与字段或局部变量在其生命周期和分配方面是不同的。我将回滚你所做的编辑,但你可以添加它——只是不要让它替换整个问题(大多数答案都基于此)。 - Makoto
那么问题的答案是什么呢?编译器是否会重新创建本地作用域变量? - Oliver Dixon
1
@Olly 考虑到你的示例使用了(不可变的)String,每次循环都需要创建新对象并丢弃,因此你想要微调优化的愿望毫无意义。 - arkon

3

通常情况下,尽可能限制变量的作用范围。这样可以使代码更易于理解、调试和重构。

在本例中,这些变量所需的内存在两种情况下都非常小。每个变量都是对象的引用,而不是对象本身。当作为局部变量使用时,引用可能只需要四个字节。


1
但是对象不是一个变量——只有引用是。引用是几个字节的小数字,无论对象的大小如何。 - Andy Thomas
所以当本地方法一遍又一遍地创建本地变量时,它们只是引用,那么不同的参数会产生不同的值吗? - Oliver Dixon
你能澄清一下问题吗?"不同的参数"是指方法参数还是其他什么?"不同的值"是指方法返回值还是其他什么? - Andy Thomas

1

JVM应该能够确定这是一个常量,并在必要时进行优化,因此您不需要担心样式问题。

在算术表达式的情况下,JVM可以执行常量折叠优化。

如果您只关注字符串,则常量字符串存储在字符串池中。

正如您所知道的,Java中的字符串是不可变的。

因此,如果您在Java中有常量字符串,它们将仅被存储一次,并且所有引用都将指向该字符串对象。

例如:

    String s = "a" + "bc";

    String t = "ab" + "c";

    System.out.println(s == t);

返回true,因为t和s指向同一个字符串对象。

此外,Java类具有常量池,所有这些常量都被移动到其中。

实际上,JVM非常聪明,我认为这两个玩具示例在效率方面至少不比彼此更好。尽管如其他答案所提到的那样,您可能应该考虑软件设计方面的考虑。


1

我对此进行了自己的测试,以查看编译器是否实际上在每次迭代中创建了一个“新”的对象。以下是以下代码的结果:

private static long mStartedTime;

public static void main(String args[])
{
    long TotalTime = 0;
    int NumberOfLoops = 7;

    for(int i = 0; i < NumberOfLoops; i++)
    {
        mStartedTime = System.currentTimeMillis();

        for(float Index = 0; Index < 10000000; Index++)
        {
            test1("wewgwgwegwegwegsd veweewfefw fwefwef wfwefdwvdw wefwe wevwev etbe tbebetbetb evberve");
        }

        System.out.println("Program took: " + String.valueOf(System.currentTimeMillis() - mStartedTime) + " to complete.");
        TotalTime += System.currentTimeMillis() - mStartedTime;
    }

    System.out.println("Average time taken: " + String.valueOf(TotalTime / NumberOfLoops));
}


public static void test1(String THisIsText)
{
    String Test = THisIsText;
    Test = Test.substring(1);
}

private static String mTempString;
public static void test2(String THisIsText)
{
    mTempString = THisIsText;
    mTempString = mTempString.substring(1);
}

他们得出了不同的结果,似乎将变量放在局部作用域中会导致性能损失:
我只能猜测局部方法变量被删除需要时间,这就是为什么test1需要更长时间的原因吗?(有人可以确认一下吗)
(经过多次测试)
局部作用域平均值:平均所需时间:1183
类作用域可重复使用变量:平均时间:1043

3
字符串是一种特殊情况。编译器可以将字符串字面量对象放入字符串池中。多个引用变量可以引用同一个对象。 - Andy Thomas
1
@OllyDixon:真是惊人,有多少人回答了与你所问无关的问题。最终,你自己回答了你的问题。我也有同样的问题,需要重复使用一个位图。所以,根据你在这里得出的结果,我将把它声明为全局变量。 - Luis A. Florit

1

本地变量的作用域应该尽可能小。

除非你需要在作用域之外使用变量,否则最好在作用域内声明变量。在这种情况下,没有性能差异,但为了最佳编程实践,建议在尽可能小的作用域内声明变量。可以参考这个类似且流行的SO问题

另一种情况是使用不可变类型而不是字符串。这将会有轻微的性能差异(非常微不足道)。在这种情况下,在顶部声明会稍微好一些,因为你不需要在每个循环中初始化变量。但是在字符串中,由于每次都会创建新对象,所以在这种情况下不是一个问题。


1
“最小可能范围”包括需要使用它的所有地方。因此,根据定义,您不能在“最小可能范围”之外使用它。 - Craig

0
如果 mTempString 只是在 someMethod() 中需要的临时变量,那么它必须被声明为方法内部的局部变量,而不是类的静态成员。原因是你想确保没有其他人会干扰你的临时变量。
如果你在类中声明它,那么你的代码更容易出错,因为你无法确定谁可以修改这个变量。
在你的例子中,临时变量被声明为静态字段。这使得代码不是线程安全的。如果两个不同的线程几乎同时调用相同的方法,那么第二个方法调用将改变临时变量的值,影响调用第一个方法的线程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接