Friday, December 4, 2009

2.2 Final Variables











 < Day Day Up > 







2.2 Final Variables





While we are on the subject of scoped

final variables, you should keep in mind that

these variables don't have to be primitives to be

useful. Final variables that are scoped and constructed can be used

as a powerful tool to solidify code in methods.







2.2.1 Method-Scoped final Variables





Although

final variables that appear within methods are a

little strange to some people at first, they become quite addictive

once you get used to reading them. See Example 2-5.







Example 2-5. Catching mistakes with method-scoped final variables


package oreilly.hcj.finalstory;

public class FinalVariables {



public static String someMethod(final String environmentKey) {

final String key = "env." + environmentKey;

System.out.println("Key is: " + key);

return (System.getProperty(key));

}

}






In this class, you build a scoped final variable

that adds a prefix to the parameter

environmentKey. In this case, the

final variable is final only within the

execution scope, which is different at each execution of

the method. Each time the method is entered, the

final is reconstructed. As soon as it is

constructed, it cannot be changed during the scope of the method

execution. This allows you to fix a variable in a method for the

duration of the method. To see how this works, use the test program

in Example 2-6.







Example 2-6. Testing final variables


package oreilly.hcj.finalstory;

public class FinalVariables {



public final static void main(final String[] args) {

System.out.println("Note how the key variable is changed.");

someMethod("JAVA_HOME");

someMethod("ANT_HOME");

}

}






Running this test program results in the following:





>ant -Dexample=oreilly.hcj.finalstory.FinalVariables run_example

run_example:

[java] Note how the key variable is changed.

[java] Key is: env.JAVA_HOME

[java] Key is: env.ANT_HOME




Each time the method is entered, the passed-in

environmentKey parameter is appended to the

constant prefix and then frozen for the duration of the method call.

So why make the variable final? Because once this variable is set in

the body of the method, it cannot be changed. Consider what would

happen if you made a mistake like the one shown in Example 2-7.







Example 2-7. A coding mistake caught by a final variable


package oreilly.hcj.finalstory;

public class FinalVariables {



public static String someBuggedMethod(final String environmentKey) {

final String key = "env." + environmentKey;

System.out.println("Key is: " + key);

key = new String("someValue"); // <= compiler error.

return (System.getProperty(key));

}

}






When you try to compile this code, it will give the following result:





>ant -Dexample=oreilly/hcj/finalstory/FinalVariables.java compile_example

compile_example:

[javac] Compiling 1 source file to C:\dev\hcj\bin

[javac] C:\dev\hcj\src\oreilly\hcj\finalstory\FinalVariables.java:53: cannot assign a value to final variable key

[javac] key = new String("someValue"); // <= compiler error.

[javac] ^

[javac] 1 error






In the example code, I commented out the

compiler error; you will have to uncomment it to run this test. I use

a similar procedure for all compiler errors throughout the book.








In this example, the mistake was made of trying to reassign

key to a different value. This type of mistake

simply happens; however, since you are a savvy programmer, and you

used the final keyword, the compiler tells you

that an error was made. This is a great example of trading a logic

error for a compiler error.





The technique of fixing variables with final is

extremely handy for long or complicated methods that have many local

variables. When alerted by the compiler, repairing this mistake takes

a matter of seconds. If you don't use

final to fix your variables now, you run the risk

of spending long hours to find logic bugs, only to discover that

someone reset your variable halfway through the method because of a

typo.









2.2.2 Deferred Initialization





If you want to, you can defer

the initialization of a final variable within the

method. Hold onto your hat because Example 2-8 is

going to look a little weird at first.







Example 2-8. Method-scoped final variables with deferred initialization


package oreilly.hcj.finalstory;

public class FinalVariables {



public void buildGUIDialog (final String name) {

final String instanceName;

if (name == null) {

// no problem here.

instanceName = getClass( ).getName( ) + hashCode( );

} else {

// no problem here as well.

instanceName = getClass( ).getName( ) + name;

}



JDialog dialog = new JDialog( );



// .. Do a bunch of layout and component building.



dialog.setTitle(instanceName);



// .. Do dialog assembly



instanceName = "hello"; // <= compiler error

}



}






In this case, you declare a final variable at the

start of the method without giving it a value, since the contents of

that variable depend on whether the user passed you

null. During the if statement,

check for null and then assign the variable

appropriately. Once you assign the variable a value, it

can't be assigned a value again. However, you could

have gone through half the method before assigning the variable its

value. This coding technique allows you to make single-shot,

assign-and-freeze variables. After assignment, these variables behave

like constants for the rest of the method.







Method-scoped final variables

aren't the same as constants, although they behave

like constants at times. The difference is that method-scoped

final variables are variable.

Each time the method is entered, their values are changed based on

the needs of that particular execution. However, method-scoped

constants always have the same values regardless of the circumstances

under which the method is run. Also, primitive and

String method-scoped final

variables are not substituted at compile time like primitive and

String method-scoped constants.








In addition to deferring the initialization of method variables, you

can defer the initialization of instance-scoped variables and

class-scoped variables. Instance-scoped variables must be initialized

in a constructor, and class-scoped variables must be initialized in

the static{} method or you will receive compiler

errors stating that the variable has not been initialized.







2.2.2.1 Chained deferred initialization




One interesting trick

you can employ with deferred initialization is to chain the

initialization of multiple final variables

together. For example, consider the following code, which chains

instance-scoped final variable initialization:





package oreilly.hcj.finalstory;

public class ChainingFinals {

public final String name;

public final int nameLength = this.name.length;

// public final String anotherValue = name; // <== Won't compile



public ChainingFinals(final String name) {

this.name = name;

}

}




In this code, the emphasized line will work properly because the

final variable name must be

initialized in the constructor to the class. Therefore, the

final variable nameLength can

take advantage of name when the instance is

initialized. However, make sure that you use the

this keyword in front of the variable name. If you

don't, it won't compile.





package oreilly.hcj.finalstory;

public class ChainingFinals {

public final String name;

public final int nameLength = name.length; // <== Won't compile



public ChainingFinals(final String name) {

this.name = name;

}

}




In this slightly revised example, I left off the keyword

this when using name. As a result, the line

won't compile but will instead tell me that

name is not declared. The compiler requires that

you use the this reference when chaining

finals in this manner.





Chaining final initialization can be a great tool

for precaching data or initializing final members

that are dependent on other final members. It also

has the benefit of giving you insight into how object instantiation

is managed in the virtual machine. As a result of this process, the

constructor to the class is run before the initializers.





















     < Day Day Up > 



    No comments: