Home arrow Java arrow Page 6 - The Final (Constants) Story
JAVA

The Final (Constants) Story


Generally, it is better to swap a logic error for a compiler error. Compiler errors can be found quickly and fixed just as quickly, whereas logic errors can take thouands of man-hours to find and fix. This article explains how to use the Java keyword final to change logic errors into compiler errors, thus saving you an enormous amount of time. It is excerpted from chapter two of Hardcore Java, written by Robert Simmons Jr. (O'Reilly, 2004; ISBN: 0596005687).

Author Info:
By: O'Reilly Media
Rating: 4 stars4 stars4 stars4 stars4 stars / 8
October 20, 2005
TABLE OF CONTENTS:
  1. · The Final (Constants) Story
  2. · Excessive Constants
  3. · Deferred Initialization
  4. · Final Collections
  5. · Instance-Scoped Variables
  6. · Final Methods
  7. · Conditional Compilation Variable Location

print this article
SEARCH DEVARTICLES

The Final (Constants) Story - Final Methods
(Page 6 of 7 )

Final methods are an interesting feature of Java. They allow you to make a class partially final without preventing its inheritance by another class. To make a method final, use the final keyword on the declaration, as shown in Example 2-20.

Example 2-20. A final method

package oreilly.hcj.finalstory;
public class FinalMethod {
  public final void someMethod() {
  }
}

This declaration is the antithesis of theabstractkeyword. Whereas theabstractkeyword declares that subclasses must override the method, thefinalkeyword guarantees that the method can never be overridden by subclasses. Subclasses can inherit from theFinalMethodclass and can override any method other thansomeMethod().

You should never make a method final unless it must be final. When in doubt, leave thefinalkeyword off a method. After all, you never know the kinds of variations the users of your class may come up with.

One example of a situation in which making a method final is the proper route to take is when a read-only property is used. Example 2-21 shows an example of such a property.

Example 2-21. A final property

package oreilly.hcj.finalstory;
public class FinalMethod {
 
/** A demo property. */
 
private final String name; 
 
protected FinalMethod(final String name)  {
    this.name = name;
  }
 
public final String getName(){
    return this.name;
  }
}

In this example, the name property is set at construction time and can never be changed. Also, you have defined that you never want a subclass to hide this property (which it could by declaring its own name property ifgetName()wasn’t final). This is a good reason to make a method final. By makinggetName()afinalmethod, you can guarantee that the user of subclasses of this object will always call this method when she executesgetName(). In the JDK, the methodgetClass()injava.lang. Objectis final for this very reason.

Conditional Compilation

Conditional compilation is a technique in which lines of code are not compiled into the class file based on a particular condition. This can be used to remove tons of debugging code in a production build. To understand the power of conditional compilation, consider Example 2-22, which demonstrates a method that does a complex transaction and logs it using Log4J.

Conditional compilation is a technique in which lines of code are not compiled into the class file based on a particular condition. This can be used to remove tons of debugging code in a production build. To understand the power of conditional compilation, consider Example 2-22, which demonstrates a method that does a complex transaction and logs it using Log4J.

Example 2-22. A method with traces

package oreilly.hcj.finalstory;
import org.apache.log4j.Logger;
public class ConditionalCompile {
  private static final Logger LOGGER =
    Logger.getLogger(ConditionalCompile.class);
 
public static void someMethod(){
    // Do some set up code.
    LOGGER.debug("Set up complete, beginning phases.");
    // do first part.
    LOGGER.debug("phase1 complete");
    // do second part.
    LOGGER.debug("phase2 complete");
    // do third part.
    LOGGER.debug("phase3 complete");
    // do finalization part.
    LOGGER.debug("phase4 complete");
    // Operation Completed
    LOGGER.debug("All phases completed successfully");
 
}
}

If you assume that there is a lot of code in each phase of the method, the logging shown in this example could be essential to finding business logic errors. However, when you deploy this application in a production environment, you have to go back through the code and eliminate all the logging, or this method will run like a three-legged dog in quicksand. Even if Log4j is set to a higher error level, every logging statement requires a call to another method, a lookup in a configuration table, and so on.


Leaving extensive logging in your program is just not a viable option. I remember going to a new company and working on some code written by biologists. I fired up the GUI, which was one of those “typical slow Java GUIs,” and immediately noticed something odd. In my console window, there was so much stuff being written that the word “spam” hardly does it justice. In just initializing the application, the program wrote in the neighborhood of 6,000 lines of tracing information. “No wonder this GUI is slow,” I thought. Writing out traces is an extremely CPU-expensive activity, and you should avoid it in a production system whenever possible.


To reduce the overhead, you could try to turn off the logging using a variable:

package oreilly.hcj.finalstory;
import org.apache.log4j.Logger;
public class ConditionalCompile {
  private static final Logger LOGGER = 
    Logger.getLogger(ConditionalCompile.class);
  private static boolean doLogging = false;
    public static void someMethodBetter() {
    // Do some set up code.
   
if (doLogging){
      LOGGER.debug("Set up complete, beginning phases.");
    }
   
// do first part.
    if (doLogging) {
      LOGGER.debug("phase1 complete");
    }

   
// do second part.
   
if (doLogging) {
      LOGGER.debug("phase2 complete");
    }
   
// do third part.
   
if (doLogging) {
      LOGGER.debug("phase3 complete");
    }
   
// do finalization part.
   
if (doLogging) {
      LOGGER.debug("phase4 complete");
    }
   
// Operation Completed
   
if (doLogging) {
      LOGGER.debug("All phases completed successfully");
    }
 
}
}

In this code, you used the doLogging variable to shut off the logging information at runtime. Using if statements such as this improves your runtime performance and allows you to turn the logging on and off whenever you want. When doLogging is true, you get all of the logging along with the performance hit. WhendoLoggingisfalse, you get no logging, and somewhat less of a performance hit.

This may be a good idea in some cases. However, if your logging is printing out extensive information, this technique will still slow things down. When the variable isfalse, instead of printing out the debugging information, the CPU performs one comparison each time it hits a logging statement.


When using theisDebugEnabled()method in Log4J, the cost is much more than one comparison perifstatement.


Also, you can’t afford to forget that in a method run thousands of times, a few comparisons per method can add up quickly.

You need the ability to compile conditionally, depending on your build paradigm. In development mode, you want all of the debugging code. In deployment mode, you don’t. C++ refugees will undoubtedly recognize this ability in the#ifdefdirective. Although Java doesn’t have this directive, thefinalkeyword can be used to accomplish similar results:

package oreilly.hcj.finalstory;
import org.apache.log4j.Logger;
public class ConditionalCompile {
  private static final Logger LOGGER =
    Logger.getLogger(ConditionalCompile.class);
 
private final static boolean doLogging = false;
 
public static void someMethodBetter() {
    // Do some set up code.
    if (doLogging) {
      
LOGGER.debug("Set up complete, beginning phases.");
    }
   
// do first part.
    if (doLogging) {
      LOGGER.debug("phase1 complete");
    }
   
// do second part.
    if (doLogging) {
      LOGGER.debug("phase2 complete");
    }
   
// do third part.
    if (doLogging) {
      LOGGER.debug("phase3 complete");
    }
   
// do finalization part.
    if (doLogging) {
      LOGGER.debug("phase4 complete");
    }
   
// Operation Completed
    if (doLogging) {
      LOGGER.debug("All phases completed successfully");
    }
  }
}

By converting thedoLoggingattribute into afinalattribute, you have told the compiler that whenever it seesdoLogging, it should replace it withfalseas per the compile-time substitution rules from earlier in the chapter. The first pass of the compiler changes the code to something like this:

package oreilly.hcj.finalstory;
import org.apache.log4j.Logger;
public class ConditionalCompile {
  private static final Logger LOGGER =
    Logger.getLogger(ConditionalCompile.class);
 
private static boolean doLogging = false;
 
public static void someMethodBetter() {
    // Do some set up code.
   
if (false) {
     
LOGGER.debug("Set up complete, beginning phases.");
    }
   
// do first part.
   
if (false) {
     
LOGGER.debug("phase1 complete");
    }
   
// do second part.
   
if (false) {
     
LOGGER.debug("phase2 complete");
    }
   
// do third part.
   
if (false) {
      
LOGGER.debug("phase3 complete");
    }
   
// do finalization part.
   
if (false) {
    
LOGGER.debug("phase4 complete");
    }
   
// Operation Completed
   
if (false) {
     
LOGGER.debug("All phases completed successfully");
    }
  }
}

Once this is done, the compiler takes another look at it and sees that there are unreachable statements in the code. Since you are working with a top-quality         compiler,  it doesn’t like all those unreachable byte codes. So it removes them, and you end up with this:

package oreilly.hcj.finalstory;
import org.apache.log4j.Logger;
public class ConditionalCompile {
  private static final Logger LOGGER =
    Logger.getLogger(ConditionalCompile.class);
 
private static boolean doLogging = false;
  public static void someMethodBetter() {
   
// Do some set up code.
   
// do first part.
   
// do second part.
   
// do third part.
   
// do finalization part.
   
// Operation Completed
 
}
}

This is perfect! By setting the value of thedoLoggingtotrueorfalseand recompiling the class, you can turn on and off inclusion of code in your program. When you build for development, you alter the code to setdoLoggingtotrue. When you build a production release, set it tofalse. You now have the best of both worlds.


Remember that if you change a primitivefinalstatic logging variable, you’ll have to change the class that contains that variable and recompile all the classes that reference it.


 


blog comments powered by Disqus
JAVA ARTICLES

- Java Too Insecure, Says Microsoft Researcher
- Google Beats Oracle in Java Ruling
- Deploying Multiple Java Applets as One
- Deploying Java Applets
- Understanding Deployment Frameworks
- Database Programming in Java Using JDBC
- Extension Interfaces and SAX
- Entities, Handlers and SAX
- Advanced SAX
- Conversions and Java Print Streams
- Formatters and Java Print Streams
- Java Print Streams
- Wildcards, Arrays, and Generics in Java
- Wildcards and Generic Methods in Java
- Finishing the Project: Java Web Development ...

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials