Declaring variables inside or outside of a loop
JavaOptimizationWhile LoopJava Problem Overview
Why does the following work fine?
String str;
while (condition) {
str = calculateStr();
.....
}
But this one is said to be dangerous/incorrect:
while (condition) {
String str = calculateStr();
.....
}
Is it necessary to declare variables outside the loop?
Java Solutions
Solution 1 - Java
The scope of local variables should always be the smallest possible.
In your example I presume str
is not used outside of the while
loop, otherwise you would not be asking the question, because declaring it inside the while
loop would not be an option, since it would not compile.
So, since str
is not used outside the loop, the smallest possible scope for str
is within the while loop.
So, the answer is emphatically that str
absolutely ought to be declared within the while loop. No ifs, no ands, no buts.
The only case where this rule might be violated is if for some reason it is of vital importance that every clock cycle must be squeezed out of the code, in which case you might want to consider instantiating something in an outer scope and reusing it instead of re-instantiating it on every iteration of an inner scope. However, this does not apply to your example, due to the immutability of strings in java: a new instance of str will always be created in the beginning of your loop and it will have to be thrown away at the end of it, so there is no possibility to optimize there.
EDIT: (injecting my comment below in the answer)
In any case, the right way to do things is to write all your code properly, establish a performance requirement for your product, measure your final product against this requirement, and if it does not satisfy it, then go optimize things. And what usually ends up happening is that you find ways to provide some nice and formal algorithmic optimizations in just a couple of places which make our program meet its performance requirements instead of having to go all over your entire code base and tweak and hack things in order to squeeze clock cycles here and there.
Solution 2 - Java
I compared the byte code of those two (similar) examples:
Let's look at 1. example:
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
after javac Test.java
, javap -c Test
you'll get:
public class inside.Test extends java.lang.Object{
public inside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
Let's look at 2. example:
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
after javac Test.java
, javap -c Test
you'll get:
public class outside.Test extends java.lang.Object{
public outside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
The observations shows that there is no difference among those two examples. It's the result of JVM specifications...
But in the name of best coding practice it is recommended to declare the variable in the smallest possible scope (in this example it is inside the loop, as this is the only place where the variable is used).
Solution 3 - Java
Declaring objects in the smallest scope improve readability.
Performance doesn't matter for today's compilers.(in this scenario)
From a maintenance perspective, 2nd option is better.
Declare and initialize variables in the same place, in the narrowest scope possible.
As Donald Ervin Knuth told:
> "We should forget about small efficiencies, say about 97% of the time: > premature optimization is the root of all evil"
i.e) situation where a programmer lets performance considerations affect the design of a piece of code. This can result in a design that is not as clean as it could have been or code that is incorrect, because the code is complicated by the optimization and the programmer is distracted by optimizing.
Solution 4 - Java
if you want to use str
outside looop also; declare it outside. otherwise, 2nd version is fine.
Solution 5 - Java
Please skip to the updated answer...
For those who care about performance take out the System.out and limit the loop to 1 byte. Using double (test 1/2) and using String (3/4) the elapsed times in milliseconds is given below with Windows 7 Professional 64 bit and JDK-1.7.0_21. Bytecodes (also given below for test1 and test2) are not the same. I was too lazy to test with mutable & relatively complex objects.
double
Test1 took: 2710 msecs
Test2 took: 2790 msecs
String (just replace double with string in the tests)
Test3 took: 1200 msecs
Test4 took: 3000 msecs
Compiling and getting bytecode
javac.exe LocalTest1.java
javap.exe -c LocalTest1 > LocalTest1.bc
public class LocalTest1 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
double test;
for (double i = 0; i < 1000000000; i++) {
test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
public class LocalTest2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
for (double i = 0; i < 1000000000; i++) {
double test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
Compiled from "LocalTest1.java"
public class LocalTest1 {
public LocalTest1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore 5
7: dload 5
9: ldc2_w #3 // double 1.0E9d
12: dcmpg
13: ifge 28
16: dload 5
18: dstore_3
19: dload 5
21: dconst_1
22: dadd
23: dstore 5
25: goto 7
28: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
31: lstore 5
33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
36: new #6 // class java/lang/StringBuilder
39: dup
40: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
43: ldc #8 // String Test1 Took:
45: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
48: lload 5
50: lload_1
51: lsub
52: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
55: ldc #11 // String msecs
57: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
60: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: return
}
Compiled from "LocalTest2.java"
public class LocalTest2 {
public LocalTest2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore_3
6: dload_3
7: ldc2_w #3 // double 1.0E9d
10: dcmpg
11: ifge 24
14: dload_3
15: dstore 5
17: dload_3
18: dconst_1
19: dadd
20: dstore_3
21: goto 6
24: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
27: lstore_3
28: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
31: new #6 // class java/lang/StringBuilder
34: dup
35: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
38: ldc #8 // String Test1 Took:
40: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: lload_3
44: lload_1
45: lsub
46: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
49: ldc #11 // String msecs
51: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
57: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
60: return
}
UPDATED ANSWER
It's really not easy to compare performance with all the JVM optimizations. However, it is somewhat possible. Better test and detailed results in Google Caliper
- Some details on blog:Should you declare a variable inside a loop or before the loop?
- GitHub repository: https://github.com/gunduru/jvdt
- Test Results for double case and 100M loop (and yes all JVM details): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4
- DeclaredBefore 1,759.209 ns
- DeclaredInside 2,242.308 ns
Partial Test Code for double Declaration
This is not identical to the code above. If you just code a dummy loop JVM skips it, so at least you need to assign and return something. This is also recommended in Caliper documentation.
@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Declaration and assignment */
double test = i;
/* Dummy assignment to fake JVM */
if(i == size) {
dummy = test;
}
}
return dummy;
}
/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Actual test variable */
double test = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Assignment */
test = i;
/* Not actually needed here, but we need consistent performance results */
if(i == size) {
dummy = test;
}
}
return dummy;
}
> Summary: declaredBefore indicates better performance -really tiny- and it's against the smallest scope principle. JVM should actually do this for you
Solution 6 - Java
Inside, the less scope the variable is visible into the better.
Solution 7 - Java
One solution to this problem could be to provide a variable scope encapsulating the while loop:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
They would be automatically de-reference when the outer scope ends.
Solution 8 - Java
If you don't need to use the str
after the while loop (scope related) then the second condition i.e.
while(condition){
String str = calculateStr();
.....
}
is better since if you define an object on the stack only if the condition
is true. I.e. use it if you need it
Solution 9 - Java
I think the best resource to answer your question would be the following post:
https://stackoverflow.com/questions/407255/difference-between-declaring-variables-before-or-in-loop
According to my understanding this thing would be language dependent. IIRC Java optimises this, so there isn't any difference, but JavaScript (for example) will do the whole memory allocation each time in the loop.In Java particularly I think the second would run faster when done profiling.
Solution 10 - Java
Declaring String str
outside of the while
loop allows it to be referenced inside & outside the while
loop. Declaring String str
inside of the while
loop allows it to only be referenced inside that while
loop.
Solution 11 - Java
Variables should be declared as close to where they are used as possible.
It makes RAII (Resource Acquisition Is Initialization) easier.
It keeps the scope of the variable tight. This lets the optimizer work better.
Solution 12 - Java
According to Google Android Development guide, the variable scope should be limited. Please check this link:
Solution 13 - Java
As many people have pointed out,
String str;
while(condition){
str = calculateStr();
.....
}
is NOT better than this:
while(condition){
String str = calculateStr();
.....
}
So don't declare variables outside their scopes if you are not reusing it...
Solution 14 - Java
Declaring inside the loop limits the scope of the respective variable. It all depends on the requirement of the project on the scope of the variable.
Solution 15 - Java
Truly, the question stated above is an programming issue. How would you like to program your code? Where do you need the 'STR' to be accessed? There is no use of declaring a variable which is used locally as a global variable. Basics of programming I believe.
Solution 16 - Java
These two examples result in the same thing. However, the first provides you with using the str
variable outside of the while loop; the second is not.
Solution 17 - Java
The str
variable will be available and reserved some space in memory even after while executed below code.
String str;
while(condition){
str = calculateStr();
.....
}
The str
variable will not be available and also the memory will be released which was allocated for str
variable in below code.
while(condition){
String str = calculateStr();
.....
}
If we followed the second one surely this will reduce our system memory and increase performance.
Solution 18 - Java
I think the size of the object matters as well. In one of my projects, we had declared and initialized a large two dimensional array that was making the application throw an out-of-memory exception. We moved the declaration out of the loop instead and cleared the array at the start of every iteration.
Solution 19 - Java
Warning for almost everybody in this question: Here is sample code where inside the loop it can easily be 200 times slower on my computer with Java 7 (and the memory consumption is also slightly different). But it is about allocation and not only scope.
public class Test
{
private final static int STUFF_SIZE = 512;
private final static long LOOP = 10000000l;
private static class Foo
{
private long[] bigStuff = new long[STUFF_SIZE];
public Foo(long value)
{
setValue(value);
}
public void setValue(long value)
{
// Putting value in a random place.
bigStuff[(int) (value % STUFF_SIZE)] = value;
}
public long getValue()
{
// Retrieving whatever value.
return bigStuff[STUFF_SIZE / 2];
}
}
public static long test1()
{
long total = 0;
for (long i = 0; i < LOOP; i++)
{
Foo foo = new Foo(i);
total += foo.getValue();
}
return total;
}
public static long test2()
{
long total = 0;
Foo foo = new Foo(0);
for (long i = 0; i < LOOP; i++)
{
foo.setValue(i);
total += foo.getValue();
}
return total;
}
public static void main(String[] args)
{
long start;
start = System.currentTimeMillis();
test1();
System.out.println(System.currentTimeMillis() - start);
start = System.currentTimeMillis();
test2();
System.out.println(System.currentTimeMillis() - start);
}
}
Conclusion: Depending of the size of the local variable, the difference can be huge, even with not so big variables.
Just to say that sometimes, outside or inside the loop DOES matter.
Solution 20 - Java
You have a risk of NullPointerException
if your calculateStr()
method returns null and then you try to call a method on str.
More generally, avoid having variables with a null value. It stronger for class attributes, by the way.