Instance-Scoped Variables

Another type of final class member that can be very useful is instance-scoped final attributes. Consider the code in Example 2-14.

Example 2-14. A creation date property
package oracle.hcj.finalstory;
public class FinalMembers {
 /** Holds the creation date-time of the instance. */
 private Date creationDate = 
 Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime( );
 /** * Get the Date-Time when the object was created.
 *
 * @return The creation date of the object.
 */
 public Date getCreationDate( ) {
 return this.creationDate;
 }
}


The job of the property creationDate is to hold the date and time of the instance's creation. This property represents a read-only property that is set once; after all, an object can be created only once. However, there is a problem with this property: it leaves a massive potential bug lurking in your code. To illustrate this, lets look at another part of the same class in Example 2-15.

Example 2-15. A modification date property
package oracle.hcj.finalstory;
public class FinalMembers {
 /** Holds the modification date-time of the instance. */
 public Date modificationDate = creationDate;
 public void setModificationDate(Date modificationDate) {
 if (modificationDate == null) {
 throw new NullPointerException( );
 }
 this.creationDate = modificationDate;
 }
 public Date getModificationDate( ) {
 return this.modificationDate;
 }
}


Here, you have a neat and cryptic little logic bug. If you didn't see the bug instantly, that only reinforces my point. The problem is that the writer of the setModificationDate( ) method is obviously setting the wrong parameter. Due to a simple typo, instead of setting modificationDate, this method sets creationDate. No one ever intends to write bugs like this, but it happens. Fortunately, there is a way you can block this problem with a coding standard:

package oracle.hcj.finalstory;
public class FinalMembers {
 private final Date creationDate2 =
 Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime( );
 public Date getCreationDate2( ) {
 return this.creationDate2;
 }
 public void setModificationDate2(Date modificationDate) {
 if (modificationDate == null) {
 throw new NullPointerException( );
 }
 this.creationDate2 = modificationDate; // <= Compiler error.
 }
}


Now that the creationDate2 is final, any attempts to write to it after it is initialized will cause a compiler error. Instance-scoped final variables have a similar morphology to method-scoped final variables. They are constructed once at each instantiation of the class and then are frozen for the life of the instance. They are ideal for read-only attributes set only at instantiation. This technique gives you yet another coding standard that can save your skin. What used to be a difficult-to-find logic bug is magically transformed by the keyword final into a minor compile-time bug. Finding the logic bug could potentially take hours of painstaking work with a debugger. The compile-time bug you replaced it with would be fixed by a programmer in only a few seconds. It's unlikely the programmer would even notice the repair! You can take this concept even further. When writing classes, you can create instance-scoped attributes that are final and are not initialized. The class in Example 2-16 shows how this deferred initialization works.

Example 2-16. Deferring final initialization to the constructor
package oracle.hcj.finalstory;
public class FinalMembers {
 /** Holds the creation date-time of the instance. */
 private final Date creationDate3;
 /** * Constructor
 *
 * @param creationDate The creation date. * @param modificationDate The last modification date.
 */
 public FinalMembers(final Date creationDate, final Date modificationDate) {
 if (modificationDate.compareTo(creationDate) < 0) {
 throw new IllegalArgumentException("modificationDate");
 }
 this.creationDate3 = creationDate;
 // do a bunch of date calculations.  this.creationDate3 = modificationDate; // <= compiler error
 }
 /** * Second constructor. Use current date for creation date.
 *
 * @param modificationDate The last modification date.
 */
 public FinalMembers(final Date modificationDate) {
 this.modificationDate = modificationDate;
 // <= compiler error: 'creationDate may not have been initialized'
 }
}


In this example, you create a member variable named creationDate3 and set it as final, but don't initialize it. Upon construction, the user passes a date to the class and creationDate3 is set. As before, once it is set, it cannot be changed. This ability to defer an initialization to a constructor gives you a lot of flexibility and safety in object initialization. In the first constructor, you accidentally try to set creationDate3 twice: once with the creationDate parameter and once with the modificationDate parameter. Obviously, the author of this file mistyped the variable name and meant to type this.modificationDate in the second instance. However, since you are being vigilant with the final keyword, you catch the error at compile time and correct it. As a bonus, if you forget to set the variable, as in the second constructor in Example 2-16, you get a compiler error telling you that the variable hasn't been initialized:

>ant -Dexample=oracle/hcj/finalstory/FinalMembers.java compile_example compile_example:
 [javac] Compiling 1 source file to C:\dev\hcj\bin
 [javac] C:\dev\hcj\src\oracle\hcj\finalstory\FinalMembers.java:69: variable creationDate3 might not have been initialized
 [javac] public FinalMembers(final Date modificationDate) {
 [javac] ^
 [javac] 1 error


The reason you get this compiler error is because Java requires that all instance-scoped final variables and members must be set before the end of the constructor. In a similar manner, all class-scoped members must be set by the end of the static initialization of the class they are declared in, and all method-scoped finals must be set by the end of the method in which they are declared in. In the case of Example 2-16, your coding standard has given you two layers of security for the price of one. This is a bargain that no programmer could pass up!

      
Comments