BUY THIS BOOK
Add to Cart

Print Book $39.95


Add to Cart

Print+PDF $51.94

Add to Cart

PDF $31.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £28.50

What is this?

Looking to Reprint or License this content?


Hardcore Java
Hardcore Java

By Robert Simmons, Jr.
Book Price: $39.95 USD
£28.50 GBP
PDF Price: $31.99

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Java in Review
I can hear the groans from here—a review on Java? Don't worry, I won't bore you with all of the gory syntax details or concepts of the Java language that you can easily pick up in other books. Instead, I will present a conceptual review that focuses on some various important issues that are often overlooked or underemphasized. The study of these issues will not only give you a better understanding of the Java language, but prepare you for what's covered in the rest of the book. You should think of this chapter as a roving spotlight, highlighting various issues of Java that are worthy of mention; even the intermediate and advanced programmer will benefit from the study of these issues.
To understand the advanced concepts of the Java language, there are a few core concepts that you must have firmly in mind. Without these concepts, much of this book will not make a lot of sense. The concepts of pointers in Java, its class hierarchy, and RTTI (runtime type identification) are three of the most important on this list.
Java and C++ use a very analogous syntax to symbolize their instructions to the computer's CPU. In fact, there are probably more similarities between these languages than the two entrenched camps of supporters would like to admit. One difference between Java and C++ that is often mentioned, though, is that Java does not use pointers.
Pointers in C++ were a constant source of problems and were determined to be the programming equivalent of evil incarnate. There was, and is to this day, a large group of applications in C++ that suffer from the effects of this particular wrong. Therefore, Sun decided to leave them out of Java—at least that's the theory.
In reality, Java uses what C++ calls references . In fact, other than primitives, all variables in Java are references. References and pointers are very similar; both contain the memory location of a particular object. Pointers, however, allow you to do arithmetic whereas references do not. The fact that Java uses so many references introduces some difficulties that novice and proficient Java developers often get burned by. The code shown in Example 1-1 demonstrates one of these difficulties.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Core Concepts
To understand the advanced concepts of the Java language, there are a few core concepts that you must have firmly in mind. Without these concepts, much of this book will not make a lot of sense. The concepts of pointers in Java, its class hierarchy, and RTTI (runtime type identification) are three of the most important on this list.
Java and C++ use a very analogous syntax to symbolize their instructions to the computer's CPU. In fact, there are probably more similarities between these languages than the two entrenched camps of supporters would like to admit. One difference between Java and C++ that is often mentioned, though, is that Java does not use pointers.
Pointers in C++ were a constant source of problems and were determined to be the programming equivalent of evil incarnate. There was, and is to this day, a large group of applications in C++ that suffer from the effects of this particular wrong. Therefore, Sun decided to leave them out of Java—at least that's the theory.
In reality, Java uses what C++ calls references . In fact, other than primitives, all variables in Java are references. References and pointers are very similar; both contain the memory location of a particular object. Pointers, however, allow you to do arithmetic whereas references do not. The fact that Java uses so many references introduces some difficulties that novice and proficient Java developers often get burned by. The code shown in Example 1-1 demonstrates one of these difficulties.
Example 1-1. Collections are passed as references
package oreilly.hcj.review;
public class PointersAndReferences {
  public static void someMethod(Vector source) {
    Vector target = source;
    target.add("Swing");
  }
}
Here, you simply copy the passed-in source vector and add a new element to the copy (named target); at least that is how it appears. Actually, something quite different happens. When you set
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Syntax Issues
When compared to languages such as C++, Java has a very simple syntax. However, there are some points of syntax that should be covered, even for the intermediate and advanced Java programmer.
One of the things that is not well understood about the if statement is that it abbreviates evaluations in order from left to right. For example, consider the following code:
package oreilly.hcj.review;

public class SyntaxIssues {
  public static void containsNested(final List list, 
                                    final Object target) {
    Iterator iter = list.iterator( );
    for (Set inner = null; iter.hasNext( ); inner = (Set)iter.next( )) {
      if (inner != null) {
                       if (inner.contains(target)) {
                         // do code.
                       }
                     }
    }
  }
}
In this code, the method is passed a list of sets to determine whether the targeted element is in one of the nested sets. Since a list can contain nulls, the method wisely checks for null before dereferencing inner. As long as inner isn't null, the method checks to see whether the set contains target. This code works, but the deep nesting is not necessary. You can write the code in another way:
package oreilly.hcj.review;

public class SyntaxIssues {
  public static void containsNested2(final List list, 
                                     final Object target) {
    Iterator iter = list.iterator( );
    for (Set inner = null; iter.hasNext( ); inner = (Set)iter.next( )) {
      if ((inner != null) && (inner.contains(target))) {
                       // do code.
                     }
    }
  }
}
In this version, the method checks for null and containment on the same line. This version of the method is in no danger of throwing NullPointerExceptions because the evaluation of the if statement is abbreviated at runtime. While evaluating an if statement, the evaluations are run from left to right. Once the evaluation reaches a definitive condition that cannot be altered by any other evaluation, the remaining evaluations are skipped.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Access Issues
When you look through the many Java books that are available, they all talk about access restrictions. The words private, protected, and public are some of the first keywords that a newbie Java programmer learns. However, most of these books discuss access restrictions only with regards to the impact of restrictions on the code.
By now, you should know what a private method is and the difference between private and protected. Therefore, I won't bother rehashing this familiar territory. Instead, I would like to take your understanding of access restrictions to another level. Instead of focusing on what they do, I will focus on which to use in various situations.
While writing Java programs, many programmers fall into a definable pattern. All attributes are private, all interface methods are public, and all helper methods are private. Unfortunately, this causes a ton of problems in the real world. Consider the following common GUI code:
package oreilly.hcj.review;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class SomeDialogApp extends JDialog implements ActionListener {
  
  private JButton okBtn = null;
  private JButton someBtn = null;

  //  . . . etc.

  public SomeDialogApp( ) {
    setJMenuBar(buildMenu( ));
    buildContents( );
  }

  public void actionPerformed(final ActionEvent event) {
    Object source = event.getSource( );
    if (source == this.okBtn) {
      handleOKBtn( );
    } else if (source == this.someBtn) {
      handleSomeBtn( );
    }

    //  . . . etc.
  }

  private void buildContents( ) {
    this.okBtn = new JButton("OK");
    this.okBtn.addActionListener(this);
    this.someBtn = new JButton("Something");
    this.someBtn.addActionListener(this);
    //  . . . etc.
  }

  private JMenuBar buildMenu( ) {
    JMenuBar result = new JMenuBar( );

    //  . . . add items and menus
    return result;
  }

  private void handleOKBtn( ) {
    // handler code
  }

  private void handleSomeBtn( ) {
    // handler code
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Common Mistakes
There are a certain group of mistakes in Java programming that are made over and over again at hundreds of companies throughout the world. Knowing how to avoid these mistakes will make you stand out from the crowd and look like you actually know what you are doing.
The Java System streams represent the ability to write to the console or to read from it. When you invoke a method such as printStackTrace( ) with no arguments, its output is written to the default System stream, which is usually the console that started the program. However, these streams can cause problems in your code. Consider the following from a hypothetical GUI:
public void someMethod( ) {
  try {
    // do a whole bunch of stuff
  } (catch Exception ex) {
     ex.printStackTrace( );
     throw new MyApplicationException( );
  }
}
To debug this GUI, you print the stack trace if something goes wrong. The problem is that the code will print the stack trace to the console window, which may be hidden, or even running on another computer.
Printing to the console window is iffy, at best, in enterprise Java. In fact, there are times when you cannot use the console at all, such as when you write EJB code. At other times, you may be writing a library for others to use, and not have any idea of what the runtime environment is. Therefore, since one of your prime goals should be to promote reusability, you cannot count on the console always being around. The solution to the problem is to keep throwing those exceptions.
In JDK 1.4, there is a new facility that will tell you if one exception caused another. This is called the Chained Exception Facility . In short, if you throw an exception inside of a catch block, the virtual machine will note the exception that you are throwing, along with the exception and stack trace that caused you to enter the catch block in the first place. For more information on this facility, consult the JDK documentation at
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: The Final Story
One fundamental principle of programming is that, generally, it is best to swap a logic error for a compiler error. Compiler errors tend to be found in seconds and are corrected just as fast. Syntax errors are a good example. A missing semicolon can make things confusing. If the compiler error is something particularly cryptic, the resolution may take as long as a couple of minutes to discover.
Logic errors, on the other hand, are the bane of all programmers. They hide and hate to reveal themselves. Logic errors seem to have minds of their own, constantly evading detection and dodging your efforts to pin down their cause. They can easily take a thousand times more effort to solve than the worst compiler errors. Worst of all, many logic errors are not found at all and occur only intermittently in sensitive places, which causes your customers to scream for a fix. Logic errors often require you to throw thousands of man-hours at them, only to finally discover that they are minor typos.
The Java keyword final can be instrumental in turning thousands of logic errors into compiler errors without too much effort. With some training in coding standards and some code retrofitting, you can save an enormous amount of man-hours that are better spent elsewhere. Also, you can save your support departments from having to deal with irate customers.
Final constants are a good place to start, since many of you are already familiar with the concept. Consider the code in Example 2-1.
Example 2-1. A class that doesn't use constants
package oreilly.hcj.finalstory;
public class FinalConstants {

  public static class CircleTools {

    public double getCircleArea(final double radius) {
      return (Math.pow(radius, 2) * 3.141);
    }

    public double getCircleCircumference(final double radius) {
      return ((radius * 2) * 3.141);
    }

    public double getCircleExtrudedVolume(final double radius, 
                                          final double height) {
      return ((radius * 2 * height) * 
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Final Constants
Final constants are a good place to start, since many of you are already familiar with the concept. Consider the code in Example 2-1.
Example 2-1. A class that doesn't use constants
package oreilly.hcj.finalstory;
public class FinalConstants {

  public static class CircleTools {

    public double getCircleArea(final double radius) {
      return (Math.pow(radius, 2) * 3.141);
    }

    public double getCircleCircumference(final double radius) {
      return ((radius * 2) * 3.141);
    }

    public double getCircleExtrudedVolume(final double radius, 
                                          final double height) {
      return ((radius * 2 * height) * 3.141);
    }
  }
}
The problem with this code is that the developer has to change all three instances of the value 3.141, his estimate for π, in all three methods if he wants to make his calculations more precise. Seasoned developers will see the opportunity for a class-scoped constant, as seen in Example 2-2.
Example 2-2. Simple constants using final
package oreilly.hcj.finalstory;
public class FinalConstants {

  public static class CircleToolsBetter {
    /** A value for PI. **/
                   public final static double PI = 3.141;
    
    public double getCircleArea(final double radius) {
      return (Math.pow(radius, 2) * PI);
    }

    public double getCircleCircumference(final double radius) {
      return ((radius * 2) * PI);
    }

    public double getCircleExtrudedVolume(final double radius, 
                                          final double height) {
      return ((radius * 2 * height) * PI);
    }
  }
}
This code is much better. Now the developer can change the constant, and this one change will propagate throughout the class. The reason I am beating this particular dead horse is because there are some traps involving constants that trip up even experienced developers.
The first of these traps involves public primitive constants that are used by other code. Because primitive
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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
            
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Final Parameters
Just when you think it's safe to hit Compile, you can go even further with finals. To illustrate, suppose you hire a new developer and, while adding a new feature, he decides to make a little change to the equation2( ) method from Example 2-4. The changes he makes are shown in Example 2-9.
Example 2-9. Danger of nonfinal parameters
package oreilly.hcj.finalstory;
public class FinalParameters {

  public double equation2(double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

    double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);

    double powInputValue = 0;         
    if (result > 360) {
                     powInputValue = X * Math.sin(result); 
                   } else {
                     inputValue = K * Math.sin(result);      
                   }
    
    result = Math.pow(result, powInputValue);
    if (result > 360) {
      result = result / inputValue;
    }

    return result;
  }

}
The problem is that the new guy changed the value of the parameter passed in to the method. During the first if statement, the developer made one little mistake—he typed inputValue instead of powInputValue. This caused errors in the subsequent calculations in the method. The user of the function expects certain output and doesn't get it; however, the compiler says that everything in the code is okay. Now it's time to put on another pot of coffee and hope your spouse remembers who you are after you figure out this rather annoying problem.
Little bugs like this are often the most difficult to locate. By Murphy's Law, you can absolutely guarantee that this code will be in the middle of a huge piece of your project, and the error reports won't directly lead you here. What's more, you probably won't notice the impact of the bug until it goes into production and users are screaming for a fix.
You cannot afford to forget that once you write code, the story is not over. People will make changes, additions, and errors in your code. You will have to look through the code and fix everything that was messed up. To prevent this problem from occurring, do the following:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Final Collections
Periodically, while programming, you may want to make constant sets and store them in final variables for public use. This desire can lead to all sorts of problems. Consider the code in Example 2-11.
Example 2-11. A collection in a final static member
package oreilly.hcj.finalstory;
public class FinalCollections {
  
  public static class Rainbow {

    public final static Set VALID_COLORS; 

    static {
      VALID_COLORS = new HashSet( );
      VALID_COLORS.add(Color.red);
      VALID_COLORS.add(Color.orange);
      VALID_COLORS.add(Color.yellow);
      VALID_COLORS.add(Color.green);
      VALID_COLORS.add(Color.blue);
      VALID_COLORS.add(Color.decode("#4B0082")); // indigo
      VALID_COLORS.add(Color.decode("#8A2BE2")); // violet
    }
  }
}
The goal of this code is to declare a class with a Set of final and static Colors representing the colors of the rainbow. You want to be able to use this Set without concerning yourself with the possibility of accidentally changing it. The problem is that the Set isn't final at all! Break it with Example 2-12.
Example 2-12. A defect caused by a nonimmutable set
package oreilly.hcj.finalstory;
public final static void someMethod( ) {
    Set colors = Rainbow.VALID_COLORS;
    colors.add(Color.black); // <= logic error but allowed by compiler
    System.out.println(colors);
  }
The reference to the Set is final, but the Set itself is mutable. In short, your constant variable isn't very constant. The point is that final is not the same as immutable.
You can firm up this code in the same way you locked down returned collections from a bean in Chapter 1:
package oreilly.hcj.finalstory;
public static class RainbowBetter {

    public final static Set VALID_COLORS; 

    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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 oreilly.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 oreilly.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:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Final Classes
A final class is a class that does not allow itself to be inherited by another class. Final classes mark endpoints in the inheritance tree.
There are two ways to make a class final. The first is to use the keyword final in the class declaration:
public final class SomeClass {
  //  . . . Class contents
}
The second way to make a class final is to declare all of its constructors as private:
public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);

  private SomeClass(final int value) {
  }
}
When you give all constructors private visibility, you are implicitly declaring the class as final; often, this is not the intended result. In fact, it is the omission of the keyword final on the class declaration that should alert you to the fact that something is wrong. The class above may very well need to be final, in which case you should always specifically use the keyword final in the class declaration. If you don't follow this rule, you could end up causing some devious problems.
To find an example of these problems, you need to look no further than the JDK itself. In the java.beans package, you will find a class called Introspector (see Chapter 8). Take a look at its single constructor in Example 2-17.
Example 2-17. The java.beans.Introspector source snippets
public class Introspector {
  //  . . . snip . . . 
  private Introspector(Class beanClass, Class stopClass, int flags)
    throws IntrospectionException {
    //  . . . snip . . . 
  }
}
The constructor for the Introspector class is private. I noticed this while studying this class. My goal was to extend the Introspector and create a class that is more feature-rich than Introspector itself. Unfortunately, since the only constructor of the class is private, it is impossible to extend this class. In the case of the Introspector class, there is no reason that the class should be final. The Introspector class is a good example of how implicit
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Final Methods
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 the abstract keyword. Whereas the abstract keyword declares that subclasses must override the method, the final keyword guarantees that the method can never be overridden by subclasses. Subclasses can inherit from the FinalMethod class and can override any method other than someMethod( ).
You should never make a method final unless it must be final. When in doubt, leave the final keyword 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 if getName( ) wasn't final). This is a good reason to make a method final. By making getName( ) a final method, you can guarantee that the user of subclasses of this object will always call this method when she executes getName( ). In the JDK, the method getClass( ) in java.lang.Object is final for this very reason.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using final as a Coding Standard
I imagine that many of you never thought you would see an entire chapter written on a single keyword. However, this particular keyword is quite useful. I strongly advise that you spread final all over your code. You should use it so much that not seeing it becomes a rare, if not completely unknown, occurrence.
This coding standard may take a little getting used to but it will really pay off in the long term. The best way to get started is to force yourself to use final heavily whenever you write or edit code. Also, you should force the junior developers working for you to adopt the use of final as a coding standard. They may grumble and balk for a bit, but the coding standard will quickly become so automatic that the developers won't consciously think about it.
Like good Javadoc habits, this one is much easier to implement if you do it while you are coding. Having to go back through old code to implement the standard is a real pain. For this reason, I suggest you make it a coding standard starting today. If you have tools that allow you to edit the code templates, edit them to introduce final everywhere you can. Also, when you edit someone else's code, introduce the final variable liberally. Doing so helps to guarantee that no one can mess up your code without actually trying to do so.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Immutable Types
The source of one of the most persistent problems encountered in Java is the fact that variables for constructed types are always references, and these references are passed by value. Essentially, this allows anyone with a reference to the object to change the object. Although this may not be bad in some circumstances, it would be disastrous in others. If one part of the program is expecting data to be in an object, and another part of the program alters that data to be null, the program would crash with NullPointerExceptions. Since the part of the program that changed the data object is different from the part that generated the exceptions, the bug could be very difficult to locate.
One approach to this problem is to make the data object an instance of a class that cannot be changed after construction; these types are referred to as immutable types. Instances of immutable types are called immutable objects . If your data object is an immutable object, the other classes using the object simply have no way to change the data in the object. This is the main advantage of immutable objects; you can pass their references all over the place without having to worry about breaking encapsulation or thread safety.
Although immutable objects exist in many languages, they take on a more serious role in Java. Like virtual shoelaces, they tie together the language of Java without getting much credit. However, the conscious and correct usage of immutable types is often the mark of a Java guru.
Immutable types are not as simple as they appear at first. In fact, there are devious pitfalls with immutable types that can ensnare even the best developers. However, as long as you know where these traps lie, evading them is an easy matter. Let's start the journey by trying to create an immutable type.
Creating an immutable type is a simple process that takes just a minute to learn. You merely have to create a class that has no write methods; this includes property
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Fundamentals
Immutable types are not as simple as they appear at first. In fact, there are devious pitfalls with immutable types that can ensnare even the best developers. However, as long as you know where these traps lie, evading them is an easy matter. Let's start the journey by trying to create an immutable type.
Creating an immutable type is a simple process that takes just a minute to learn. You merely have to create a class that has no write methods; this includes property set methods as well as other methods that alter the state of the instance. See Example 3-1.
Example 3-1. An immutable person
package oreilly.hcj.immutable;

public class ImmutablePerson {
  private String firstName;
  private String lastName;
  private int age;

  public ImmutablePerson(final String firstName, final String lastName, 
                         final int age) {
    if (firstName == null) {
      throw new NullPointerException("firstName"); 
    }
    if (lastName == null) {
      throw new NullPointerException("lastName"); 
    }
    this.age = age;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public int getAge( ) {
    return age;
  }

  public String getFirstName( ) {
    return firstName;
  }

  public String getLastName( ) {
    return lastName;
  }
}
In the ImmutablePerson class, you allow the user to pass in all arguments to the constructor and then simply don't declare any write methods to the attributes. Once the person is constructed, it looks like it can't be changed. However, unfortunately, it can be changed. This type is immutable by all appearances, but there is actually a hole.
Using reflection, a Java developer can remove the access protection on the class and then change variable values. It is true that this wouldn't be a very smart thing to do; however, I have seen far stranger things in my career.
At this point, don't worry about how the access permission can be removed. We will beat that subject to death in Chapter 9, which covers this trick as well as many other reflection techniques.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Immutable Problems
Although immutable objects are extremely useful in creating solid code, they can cause problems if you aren't paying attention. The most important thing to remember about immutable objects is that whenever you try to change them, you actually end up creating new objects that are themselves immutable. This can result in some extremely slow code. The String trap is one of the most prevalent examples of this problem.
The most commonly used immutable type in the Java language is java.lang.String . However, many good developers don't know that String is immutable. They are fooled by all of the "operations" that can be done to a String. They often say, "How can String be immutable? I can concatenate strings and replace values." However, these well-meaning developers are wrong!
Whenever you perform an operation on a string, you are actually creating copies of the string that are modified to accommodate your request. This applies to concatenation as well as to operations such as splitting and replacing strings. However, the fact that this nuance of Java is not well-known is the cause of many common programming problems.
For example, consider the following code used to concatenate strings in a sentence:
public void buildSentence (String[] words) {
  String sentence = new String( );
  for (int idx = 0; idx < words.length; idx++) {
    sentence += " " + words[idx];
  } 
}
The problem with this code is that at each and every iteration of the for loop, the virtual machine allocates an entirely new String object; this new object contains the characters in the sentence variable concatenated with the word being added. Since the String object is immutable, a new String object must be created to reflect each modification. Assuming that 234,565 words are being added, you may want to take a long lunch.
If that isn't bad enough, remember that all of those intermediary String objects that this code created are not purged from memory until garbage collection is run, at which point the program will hit another speed bump the size of Mt. Everest. So, on top of a slow program, you have a program that eats memory like candy.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Immutable or Not
There are good reasons to make things immutable, but there are also good reasons to make them mutable. Although you should make each decision on a case-by-case basis, the following factors should help:
  • If the object is supposed to be a constant, it should always be immutable.
  • If the object will be changed frequently, it should be a mutable object. For example, a class such as StringBuffer is changed frequently, so it wouldn't make much sense as an immutable object.
  • If the object is very large, be careful if you opt for immutability. Large immutable objects need to be copied in order to be changed; this copying can slow down a program significantly.
  • Sets and other collections returned from a method should be immutable to preserve encapsulation.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: Collections
The single most common type of question encountered on Sun's Java Developer Connection forums has to do with the usage of collection classes in the java.util package. Since there are many Java developers out there that have migrated from other professions, this is not surprising. Those who haven't taken university-level data structures courses may find the collections to be a bit confusing.
However, the proper use of collections is one of the cornerstones of quality Java programming. Therefore, this chapter will explore them in detail. We will cover the architecture of the collections framework and the usage and concepts behind each collection type. However, we won't cover many of the actual methods in the collections, since these are easily understood by studying the Javadoc. The goal of this discussion is to help you decide which collection to use and why it is the best for a specific job.
During the early days of object-oriented programming, one of its main deficiencies was its inability to manage collections of objects. For years, C++ suffered because the collections management that was available was not standardized. To attack this problem, various vendors designed packages of collection classes that could be purchased and implemented. However, even these packages did not solve the portability problem among cooperating vendors. For example, if my company had bought Rogue Wave Tools.h++, all of your business partners would have needed to make a similar purchase to extend your software.
To fix this fundamental deficiency, Sun introduced standard collection classes in the earliest version of the JDK. This decision, along with the other common class libraries in the JDK, contributed to the rapid rise of Java technology. Now, instead of my partner companies having to make a separate and often expensive purchase, they can simply use the collections in the JDK.
The JDK collection classes can be found in the java.util package. They are structured using an interface-implementation model. For each type of collection, there is one interface and various implementations. For example, the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Collection Concepts
During the early days of object-oriented programming, one of its main deficiencies was its inability to manage collections of objects. For years, C++ suffered because the collections management that was available was not standardized. To attack this problem, various vendors designed packages of collection classes that could be purchased and implemented. However, even these packages did not solve the portability problem among cooperating vendors. For example, if my company had bought Rogue Wave Tools.h++, all of your business partners would have needed to make a similar purchase to extend your software.
To fix this fundamental deficiency, Sun introduced standard collection classes in the earliest version of the JDK. This decision, along with the other common class libraries in the JDK, contributed to the rapid rise of Java technology. Now, instead of my partner companies having to make a separate and often expensive purchase, they can simply use the collections in the JDK.
The JDK collection classes can be found in the java.util package. They are structured using an interface-implementation model. For each type of collection, there is one interface and various implementations. For example, the List interface is implemented by the AbstractList, ArrayList, LinkedList, and Vector classes.
Don't worry about these implementation classes for now; we will discuss them later in the chapter.
Interfaces are a special approach to object-oriented engineering. While classes implement the concept, the interface defines the concept. For example, an ArrayList and a LinkedList are both conceptually lists.
The interfaces of the java.util package are the only components that should be exposed to the users of your classes, unless you have no other choice. This follows from the idea of encapsulation, which dictates that the programmer should not expose implementation details to the user of a class, but only the interface to that class. For example, the following code is an example of a good interface-based technique:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Implementations
Now that you have the various types of collections in mind, it is time to turn your attention to the implementation of these collections. A List, for example, can be implemented a number of ways, each with advantages and disadvantages. However, before you can understand the various implementations of Lists and other collections, you need to understand how collections and maps determine equality and order.
Until now, we have been discussing equality and order quite a bit, but without explaining how these conditions are actually discovered by the collections. For example, how does a Map decide if two keys are the same, and how does a SortedSet determine the order used to sort the objects? To answer these questions, you have to tackle the basic principles of object equality: identity and comparability.

Section 4.2.1.1: Equality versus identity

For many collections to do their job, they need to know which objects are equal. For example, a Set can't exclude duplicate entries if it doesn't know how to check to see whether supplied objects are equal. These collections take advantage of the fact that all objects in Java descend from the common class java.lang.Object.
Since all objects descend from Object, the Set implementations can call the method equals( ) on all objects. This allows the set to compare objects. However, there is one catch: for this to be effective, the object must define (or inherit) a valid implementation of equals( ).
The default implementation of equals( ) compares two objects to see whether they are the same object in the virtual machine by identity , not by equality. Suppose you create two Address objects that define the addresses of employees. Both address objects can contain the same street name, number, country, postal code, and other data. However, these objects are two different instances (and therefore reside at two different memory addresses). In this case, the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Choosing a Collection Type
When faced with such a wide variety of collections, it is often difficult to decide which collection type you should use. The general rule is that you should use the collection type that is the most restrictive and still accomplishes your needs. The reason for this is that collections tend to slow down as their abilities increase.
First, you should decide whether you need values that can be looked up by a key. If so, then you are restricted to a Map type. If not, then you can use a more general Collection type. If you decide to use a Collection type, the next thing you need to ask is whether you need duplicates in your collection. If so, then you will need to use a List; otherwise, a Set should suffice.
After you have made these decisions, you should decide whether you need the objects to be sorted. This decision is much hazier than the other decisions. Almost all collections will need to be sorted at one time or another by a user. However, there are other ways of doing this sorting, such as copying the collection and sorting it in a list. You should opt for SortedSets or SortedMaps only if there is a reason why the collection should always be sorted in a specific manner. Sorted collections and maps must maintain the sorting order, so they tend to be significantly slower than other collections and take up more memory. You should incur this performance hit only if you need the functionality.
As for choosing the implementations, you need to consider the merits of each type of implementation before determining which is the best. Figure 4-5 should help you make that decision.
Figure 4-5: Choosing the right collection
In this book, we deal only with the JDK standard collection types. However, the Apache Jakarta Commons project has a library of other collection types that broadens the selection scope significantly. I highly encourage developers to seek out this project at http://jakarta.apache.org/commons/collections.html
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Iterating Collections
Now that you have the fundamentals of collections firmly in mind, you need a way to iterate through collections. You can accomplish this using three types of iterator interfaces.
The java.util.Enumeration, java.util.Iterator, and java.util.ListIterator interfaces are used to iterate through data objects in a collection. The collections themselves are responsible for implementing these interfaces and for providing the iterator for the caller. They do this using inner classes.
In the JDK, there are three principle types of iterators. Understanding each of them will help you pick the best one for your particular programming task.

Section 4.4.1.1: java.util.Enumeration

This is the oldest of the iterators. It allows you to iterate one way through a collection. Once you pass an element, you cannot go back to that element without getting a new enumeration. One problem with enumeration is that it has been replaced by the Iterator interface. The Collection and Map interfaces require the developer to implement an Iterator but not an Enumeration. Therefore, you probably won't use this interface often unless the collection classes are not available (for instance, during J2ME programming on some limited profiles).

Section 4.4.1.2: java.util.Iterator

This interface is the replacement in the JDK 1.2+ collection class architecture. Not only does it provide the same functionality as an Enumeration, but it allows you to remove an element from the collection by calling Iterator.remove( ). However, using Iterator.remove( ) can cause some rather confusing code.

Section 4.4.1.3: java.util.ListIterator

The ListIterator interface allows you to iterate backwards in a list as well as forwards. This iterator is available only on classes that implement the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Collection Gotchas
Content preview·Buy PDF of this chapter|