JETZT ONLINE BESTELLEN
Add to Cart
Hardcore Java

First Edition März 2004
ISBN 978-0-596-00568-9
344 Seiten
EUR32.00

Weitere Informationen zu diesem Buch

Inhaltsverzeichnis | Kolophon |


Inhaltsverzeichnis

	
Chapter 1: Java in Review
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Core Concepts
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Syntax Issues
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Access Issues
Inhaltsvorschau
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

  }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Common Mistakes
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 2: The Final Story
Inhaltsvorschau
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) * 
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Final Constants
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Final Variables
Inhaltsvorschau
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

            
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Final Parameters
Inhaltsvorschau
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:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Final Collections
Inhaltsvorschau
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);

    }

  }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Instance-Scoped Variables
Inhaltsvorschau
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:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Final Classes
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Final Methods
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Conditional Compilation
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Using final as a Coding Standard
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 3: Immutable Types
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Fundamentals
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Immutable Problems
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Immutable or Not
Inhaltsvorschau
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.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 4: Collections
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Collection Concepts
Inhaltsvorschau
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:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Implementations
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Choosing a Collection Type
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Iterating Collections
Inhaltsvorschau
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
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Collection Gotchas
Inhaltsvorschau
Collections provide you with a powerful tool for storing and traversing data. However, they have their share of gotchas that the savvy developer needs to beware of.
One of the most prevalent gotchas has to do with how collections store data. Collections store data by making references to that data, not by copying the actual data. This is important to remember because the collection holding the data is not necessarily the only object that can access the underlying value of that data. Since the variables for all constructed types in Java are references, and these reverences are passed by value, another part of your program could have a reference to a data object in your collection.
When placing collections in a hash-based data set, the collections will place objects with similar hash codes in the same bucket. This can be a problem if you forget to override the hashCode( ) method in your collected objects.
If you don't override hashCode( ), the collections will use the default implementation in the java.lang.Object class. This implementation needs the memory location of the object to compute the hash code. However, if you create a lot of objects, it is likely that they will be close to each other within the memory. The result would be a HashMap with most of the objects in the first bucket and few, if any, in the other buckets. Such an unbalanced HashMap would behave poorly; in extreme conditions, it could degrade from O(1) to O(n) efficiency.
The solution to this problem is to make sure you override the hashCode( ) method to give your data an even distribution inside the buckets of the hash-based collection. Instead of calculating based on location, you should calculate based on the data in the object. However, don't forget to override equals( ) if you override hashCode( ).
The Jakarta Commons Lang library contains utilities that make creating high-quality hash codes easy. I highly recommend you check it out at
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 5: Exceptional Code
Inhaltsvorschau
The common attribute of all exceptional code is, not surprisingly, the proper use of exceptions. Exceptions can give your code immense debugging power and provide a base for indicating user errors. At one time, using exceptions for business logic errors was considered to be bad form. Instead of throwing an exception, the programmer was encouraged to use deeply nested if statements to catch user errors. Java has changed this perspective somewhat through the use of two types of exceptions, both of which are covered extensively in this chapter.
Java started out by borrowing the C++ exception mechanism. However, early in the development of the JDK, Sun made some important modifications. Instead of only one category of exception, Java has two. Today, Java differentiates between an Exception and a RuntimeException. To understand how this differentiation is advantageous, you must first understand these two types of exceptions.
When a method can throw an Exception , the exception must be caught in the body of the method or declared in the throws clause of the method declaration:
public void someDatabaseMethod ( ) throws SQLException {

  // Do some JDBC Work.

}
In this code, the method someDatabaseMethod( ) can throw a SQLException if there is a problem with the database. Since SQLException is a descendant of the Exception class, someDatabaseMethod( ) must either handle the exception with a try-catch block or declare that the method throws the SQLException. Since someDatabaseMethod( ) declares that it throws SQLException, any method calling someDatabaseMethod( ) is required to catch the exception or declare that it also throws the exception. This process of passing the responsibility to callers must be propagated down the call stack until the exception is actually handled. This mechanism allows code to clearly show the problems that may occur within a method and require the user of that method to handle them.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Two Types of Exceptions
Inhaltsvorschau
Java started out by borrowing the C++ exception mechanism. However, early in the development of the JDK, Sun made some important modifications. Instead of only one category of exception, Java has two. Today, Java differentiates between an Exception and a RuntimeException. To understand how this differentiation is advantageous, you must first understand these two types of exceptions.
When a method can throw an Exception , the exception must be caught in the body of the method or declared in the throws clause of the method declaration:
public void someDatabaseMethod ( ) throws SQLException {

  // Do some JDBC Work.

}
In this code, the method someDatabaseMethod( ) can throw a SQLException if there is a problem with the database. Since SQLException is a descendant of the Exception class, someDatabaseMethod( ) must either handle the exception with a try-catch block or declare that the method throws the SQLException. Since someDatabaseMethod( ) declares that it throws SQLException, any method calling someDatabaseMethod( ) is required to catch the exception or declare that it also throws the exception. This process of passing the responsibility to callers must be propagated down the call stack until the exception is actually handled. This mechanism allows code to clearly show the problems that may occur within a method and require the user of that method to handle them.

Section 5.1.1.1: Superfluous exceptions

The problem with declaring a method in the throws clause is that these exceptions can become lengthy and superfluous. If you have ever encountered a method that threw five or six exceptions, you know exactly what I mean. Since you have to catch exceptions or rethrow them, using third-party libraries that throw a lot of exceptions can become a pain. In fact, there are instances when throwing even one exception is an annoyance. Consider the basic JDBC program in Example 5-1 to get a good idea of what I mean.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
When to Use Exceptions
Inhaltsvorschau
Two of the biggest problems in most Java software are using exceptions (of either variety) improperly, or not using them at all. The failure to use exceptions properly accounts for more logic errors in production systems than any other single issue. The errors often result from forgetting to use exceptions or from declaring excessive custom exception types.
When using the Exception classes, a common mistake made by many software engineers is to neglect the exceptions already provided in the JDK. When writing methods, the savvy developer often uses NullPointerException, IllegalArgumentExcep-tion, and the other built-in exception types. This allows the developer to precheck parameters to avoid corrupting data. The Java bean shown in Example 5-4 shows how forgetting to use these exceptions can be dangerous.
Example 5-4. Forgetting to use built-in exceptions
package oreilly.hcj.finalstory;

public class ExceptionUsage {



  /** Storage for a customer name (Required). */

  private String customerName;



  public void setCustomerName(final String customerName) {

    this.customerName = customerName;

  }



  public String getCustomerName( ) {

    return this.customerName;

  }



}
Although everything looks okay here, there is a glaring omission. Note how the customer name is said to be required; however, the setter does not check the name coming in. If the user of this bean passes null or a zero-length string for customerName, the property would be in a corrupted state. You can prevent problems like this by checking every single parameter for data validity. The RuntimeException classes can help:
package oreilly.hcj.finalstory;

public class ExceptionUsage {



  public void setCustomerName(final String customerName) {

    if (customerName == null) {

                     throw new NullPointerException( );

                   }

                   if (customerName.length( ) == 0) {

                     throw new IllegalArgumentException( );
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Finally for Closure
Inhaltsvorschau
In the try -catch-finally triad, is the finally clause is often neglected. This clause allows you to write code that will run regardless of the outcome of the method. Whether the code within a try block throws an exception or not, code in a finally block will be executed by the compiler. The JDBC program in Example 5-6 shows how finally is used.
Example 5-6. Closing database resources with finally
package oreilly.hcj.finalstory;

public class FinallyForClosure {



  public int getValue(final Connection conn, final String sql) 

    throws SQLException {

    int result = 0;

    Statement stmt = null;

    ResultSet rs = null;

    try {

      stmt = conn.createStatement( );

      rs = stmt.executeQuery(sql);

      if (rs.next( )) {

        result = rs.getInt(1);

      }

    } catch (final SQLException ex) {

      throw ex;

    } finally {

      if (rs != null) {

        rs.close( );

      }

      if (stmt != null) {

        stmt.close( );

      }

    }

    return result;

  }



}
In this code, even if a SQLException is thrown, the method will attempt to close the ResultSet and Statement and thus release the resources they were consuming. In this manner, you can ensure that the database resources are closed no matter what happens. It is good practice to use finally blocks whenever you seize external resources inside of a try block.
Although there is nothing technically wrong with the code in Example 5-6, you can make it even better. To understand how, consider the catch clause:
    } catch (final SQLException ex) {

      throw ex;

    } finally {
The catch clause is this example is superfluous. To use a finally block, you do not have to catch any thrown exceptions; you can merely ignore them and let them be thrown by the method. When you reconsider the getValue( ) method, it becomes obvious that not catching the exceptions is a good idea.
The getValue( ) method may throw a SQLException during the course of the method. Whatever happens, you need to close the database resources used in the call to prevent a resource leak. However, the attempted closure of these resources could generate
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Exceptional Traps
Inhaltsvorschau
Exceptions give you substantial power in your debugging and error-handling capabilities. However, they can lead to elusive corruption bugs, which can take hours to eradicate. See Example 5-7.
Example 5-7. A data table model with a problem
package oreilly.hcj.finalstory;

public class ExceptionalTraps implements TableModel {

  

  /** Cache of the data fetched. */

  private ArrayList data; 

  

  /** Holds the sql used to fetch the data. */

  private String sql;

  

  /** 

   * Gets data from the the database and caches it for the table model.

   *

   * @param conn The database connection to use.

   * @param sql The SQL to use.

   *

   * @return The result value. 

   *

   * @throws SQLException If there is a SQL error. 

   */

  public int loadData(final Connection conn, final String sql)

    throws SQLException {

    int result = 0;

    Statement stmt = null;

    ResultSet rs = null;

    Object[] record = null;

    this.data = new ArrayList( );

    try {

      this.sql = sql;

      stmt = conn.createStatement( );

      rs = stmt.executeQuery(sql);

      int idx = 0;

      int columnCount = rs.getMetaData( ).getColumnCount( );

      while (rs.next( )) {

        record = new Object[columnCount];

        for (idx = 0; idx < columnCount; idx++) {

          record[idx] = rs.getObject(idx);          

        }

        data.add(record);

      }

    } finally {

      if (rs != null) {

        rs.close( );

      }

      if (stmt != null) {

        stmt.close( );

      }

    }

    return result;

  }



  public void refresh(final Connection conn) throws SQLException {

    loadData(conn, this.sql);

  }

}
This snippet is from a JDBC-powered GUI. The code is supposed to provide a table model for a set of data from a database. When the user calls loadData( ) , the given SQL is run against the database, and the data is stored in a cache, which will be used by the GUI. This SQL is also stored in a local data member so that the user can periodically refresh the data in the table model.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 6: Nested Classes
Inhaltsvorschau
One aspect of the Java language that is not widely understood is the concept of nested classes. These classes allow you to constrain an entire class within a limited scope of another class or method. The concept of nesting a class within another class or method presents unique issues not found elsewhere in object-oriented programming. Not all types of nested classes should be used routinely, so you will likely encounter most of them in other people's code. Therefore, it is important that you understand how the various nested classes function.
Nested classes fall into one of three basic categories:
  • Inner classes
  • Limited-scope inner classes
  • Static nested classes
Each of these categories has its own access rules and usage.
Inner classes are fairly common within the JDK, especially in collections. Example 6-1 shows a class-scoped inner class.
Example 6-1. A class-scoped inner class
package oreilly.hcj.nested;

public class InnerClassDemo extends JDialog {



  public InnerClassDemo(final int beepCount) {

    super( );

    setTitle("Anonymous Demo");

    contentPane = getContentPane( );

    contentPane.setLayout(new BorderLayout( ));



    JLabel logoLabel = new JLabel(LOGO);

    contentPane.add(BorderLayout.NORTH, logoLabel);



    JButton btn = new BeepButton("Beep");

    contentPane.add(BorderLayout.SOUTH, btn);

    pack( );

    this.beepCount = beepCount;

  }



  private class BeepButton extends JButton implements ActionListener {



                   public BeepButton(final String text) {

                     super(text);

                     addActionListener(this);

                   }



                   public void actionPerformed(final ActionEvent event) {

                     try {

                       for (int count = 0; count < beepCount; count++) {

                         Toolkit.getDefaultToolkit( ).beep( );
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Inner Classes
Inhaltsvorschau
Inner classes are fairly common within the JDK, especially in collections. Example 6-1 shows a class-scoped inner class.
Example 6-1. A class-scoped inner class
package oreilly.hcj.nested;

public class InnerClassDemo extends JDialog {



  public InnerClassDemo(final int beepCount) {

    super( );

    setTitle("Anonymous Demo");

    contentPane = getContentPane( );

    contentPane.setLayout(new BorderLayout( ));



    JLabel logoLabel = new JLabel(LOGO);

    contentPane.add(BorderLayout.NORTH, logoLabel);



    JButton btn = new BeepButton("Beep");

    contentPane.add(BorderLayout.SOUTH, btn);

    pack( );

    this.beepCount = beepCount;

  }



  private class BeepButton extends JButton implements ActionListener {



                   public BeepButton(final String text) {

                     super(text);

                     addActionListener(this);

                   }



                   public void actionPerformed(final ActionEvent event) {

                     try {

                       for (int count = 0; count < beepCount; count++) {

                         Toolkit.getDefaultToolkit( ).beep( );

                         Thread.sleep(100);  // wait for the old beep to finish.

                       }

                     } catch (final InterruptedException ex) {

                       throw new RuntimeException(ex);

                     }

                   }

                 }

}
This code shows how inner classes are used in a GUI dialog. To create a special kind of button that beeps when you click on it, the javax.swing.JButton class has been extended and the appropriate action listener has been added. In the constructor, another of these special buttons is instantiated and added to the dialog. Although this seems pretty simple, there are a couple of interesting points worthy of study.
First of all, you may notice that the variable beepCount, used in the for statement, is in scope inside the action listener, even though it was not declared in the actionPerformed( )
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Limited-Scope Inner Classes
Inhaltsvorschau
One of the strangest types of inner classes is the limited-scoped inner class. Limited-scoped classes are scoped to a particular block of code. Their declaration and usage all happen within that block. To get a better idea of how limited-scoped inner classes work, see Example 6-4.
Example 6-4. A limited-scope inner class scoped to a method
package oreilly.hcj.nested;

public class MethodInnerClassDemo extends JDialog {



  /** Holds the location of the logo image. */

  private static final String LOGO_LOCATION =

                  "oreilly/hcj/nested/oreilly_header3.gif";



  static {

    LOGO = new ImageIcon(ClassLoader.getSystemClassLoader( )

                                    .getResource(LOGO_LOCATION));

  }



  /** Holds a reference to the content pane. */

  private final Container contentPane;



  /** holds a demo variable. */

  private String demo;



  public MethodInnerClassDemo(final int value) {

    super( );

    String title = "Inner Class Demo";

    setTitle(title);

    setModal(true);

    contentPane = getContentPane( );

    contentPane.setLayout(new BorderLayout( ));



    JLabel logoLabel = new JLabel(LOGO);

    contentPane.add(BorderLayout.NORTH, logoLabel);



    JButton btn = new JButton("Beep");



    class MyActionListener implements ActionListener {

                     public void actionPerformed(final ActionEvent event) {

                       Toolkit.getDefaultToolkit( ).beep( );

                       System.out.println(value);

                       System.out.println(LOGO_LOCATION);

                       System.out.println(MethodInnerClassDemo.this.demo);

                     }

                   }

    btn.addActionListener(new MyActionListener( ));



    contentPane.add(BorderLayout.SOUTH, btn);

    pack( );

  }

}
Other than the fact that the declaration of the class occurs within the body of a method, this looks like any other class declaration. You can even use the keywords final and abstract on these declarations and develop class hierarchies all within one limited block. Once declared, you can use the class throughout the remainder of the method just like any other class. However, there are some important differences between limited-scope inner classes and normal classes.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Static Nested Classes
Inhaltsvorschau
A static nested class is, not surprisingly, a nested class that is declared with the keyword static on the declaration. Many developers call these classes "inner classes" as well. However, these developers are wrong. To be an inner class, a class has to have instance scope. To understand how static nested classes function, see Example 6-8.
Example 6-8. A static nested class
package oreilly.hcj.nested;

public class OuterClass {



  private static final String name = "Robert";

  private static String company = "O'Reilly";

  private int value = 5;



  public static class SomeClass {

    public void someMethod( ) {

      System.out.println(company);

      System.out.println(name);

      System.out.println(value); // <= Compiler error

    }

  }

}
This code declares a public static nested class. This nested class can access the various static members of the enclosing class. However, unlike inner classes, it cannot access any instance variables in the enclosing class. This is an important difference between static nested classes and inner classes. Formally, it can be said that an inner class is instance-scoped and a static nested class is class-scoped. The class scope of a static nested class makes it easier to use:
package oreilly.hcj.nested;

public class StaticNestedClassDemo {

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

    OuterClass.SomeClass obj = new OuterClass.SomeClass( );

    obj.someMethod( );

  }

}
To instantiate a public static nested class, you do not need an instance of the outer class. You can simply use the class as is to create new instances:

            import oreilly.hcj.nested.OuterClass.SomeClass;

public class StaticNestedClassDemo {

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

    SomeClass obj = new SomeClass( );

    obj.someMethod( );

  }

}
With this import syntax, the class is quite easy to use. However, static nested classes can also use access specifiers. You can have public, private, protected,
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Double Nested Classes
Inhaltsvorschau
One peculiar form of declaration that I came across while working for an aerospace company was a nested class within a nested class. The dynamics of this sort of declaration are similar to those of a normal nested class. The difference is that the dynamics extend to the subnested class as well as the nested class. See Example 6-9.
Example 6-9. A double nested class
package oreilly.hcj.nested;

public class DoubleNestedClass {

  private static final String name = "Robert";

  private static String company = "O'Reilly";



  private int value = 5;



  public static String getCompany( ) {

    return company;

  }



  public static String getName( ) {

    return name;

  }



  public int getValue( ) {

    return value;

  }



  public static class SomeClass {

    private final static String book = "Hardcore Java";

    

    public void someMethod( ) {

      System.out.println("In SomeClass.someMethod( )");       

      System.out.println(company);

      System.out.println(name);

      // System.out.println(value); // <= Compiler error

    }



    public static class SomeOtherClass {

                     public void someMethod( ) {

                       System.out.println("In SomeOtherClass.someMethod( )");       

                       System.out.println(company);       

                       System.out.println(book);       

                     }

                   }

  }



}
In this example, the class SomeOtherClass is nested inside of the class SomeClass. Because of the double nesting, SomeOtherClass can access the private final static members of DoubleNestedClass as well as those of SomeClass. The dynamics extend down the nesting. Similar rules apply to method-scoped and instance-scoped double nested classes. For example, a method-scoped double nested class would have access to instance variables and final local variables of the declaring method. Regardless of how deep the nesting is, the rules propagate down.
However, just because you can use double nested classes doesn't necessarily mean you should. In fact, I can't think of a single legitimate reason for double nested classes. If you find yourself writing one of these, you should probably rethink your architecture. If you find yourself doing this routinely, you may want to consider taking a long vacation.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Nested Classes in Interfaces?
Inhaltsvorschau
Java supports the concept of nested classes in interfaces. The syntax and dynamics work just like nested classes declared in a class. However, declaring a class nested inside an interface would be extremely bad programming. An interface is an abstraction of a concept, not an implementation of one. Therefore, implementation details should be left out of interfaces. Remember, just because you can cut off your hand with a saw doesn't mean that it's a particularly good idea.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Nested Interfaces
Inhaltsvorschau
As with classes, you can nest interfaces inside an outer class or another interface. The dynamics are similar to nested classes. However, keep in mind that the classes that ultimately implement these nested interfaces don't have the special access to the class members that a nested class does; instead, they behave just like normally declared classes.
The syntax for nested interfaces is the same as nested classes, but the results are quite different. Since you are defining only the interface and not the implementation, the user of the interface is free to implement the class any way he wants. A good example of a nested interface implementation is with a special collection. See Example 6-10.
Example 6-10. A nested interface
package oreilly.hcj.nested;

public class Inventory {

  public HashSet items;



  public Set getValues( ) {

    return Collections.unmodifiableSet(items);

  }



  public void addItem(final InventoryItem item) {

    items.add(item);

  }



  public Iterator iterator( ) {

    return items.iterator( );

  }



  public void removeElement(final InventoryItem item) {

    items.remove(item);

  }



  public static interface InventoryItem {

    public String getSKU( );

  }

}
In this example, the special collection named Inventory will contain only objects that implement the InventoryItem interface (and therefore define the method getSKU( )). Furthermore, since you don't want to tie down the inventory items to a particular superclass, you make an interface for the inventory items, which allows the user to implement that interface as he sees fit.
There are only a few good reasons why you should use inner interfaces. One is to model a composition relationship without restricting the implementation of the composed object. In this case, you would need an inner interface to model the compositional relationship and to let the user of the interface decide on the implementation.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Nested Class Rules
Inhaltsvorschau
Nested classes have many confusing rules. Table 6-1 will help you keep them straight.
Table 6-1: Nested class references
Type
Scope
Access
Keywords
method
instance
class
method final
class final
class static
instance
abstract/final
private
protected
public
Anonymous
Limited-scope
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 7: All About Constants
Inhaltsvorschau
The function of constants is a concept that is often taken for granted by most developers. On the surface, they seem simple and trivial. However, constants that are not used correctly can be a major source of development headaches.
Although most developers regard constants as a single concept, there are actually three categories of constants: substitution, bit field, and option. Each of these categories has different dynamics and different issues to deal with.
Substitution constants are the simplest type of constants. Basically, they are substituted for something else in code. See Example 7-1.
Example 7-1. Substitution constants
package oreilly.hcj.constants;

public class SubstitutionConstants {

  /** Holds the logging instance. */

  private static final Logger LOGGER = 

                Logger.getLogger(SubstitutionConstants.class);



  /** A value for PI. */

  public static final double PI = 3.141;



  public double getCircleArea(final double radius) {

    double area = (Math.pow(radius, 2) * PI);

    LOGGER.debug("The calculated area is " + area);

    return area;

  }



  public float calculateInterest(final float rate, 

                                 final float principal) {

    final String LOG_MSG1 = 

                                "Error: The interest rate cannot be less than ";

                   final String LOG_MSG2 = ". The rate input = ";

                   final double MIN_INTEREST = 0.0;



    // -- 

    if (rate < MIN_INTEREST) {

      LOGGER.error(LOG_MSG1 + MIN_INTEREST + LOG_MSG2 + rate);

      throw new IllegalArgumentException("rate");

    }

    return principal * rate;

  }

}
This code declares several substitution constants. The constant PI has class scope while the constants LOG_MSG1, LOG_MSG2, and MIN_INTEREST have method scope. All of these constants are declared and then later substituted in various parts of the application. In the case of the public final static primitives and String
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Substitution Constants
Inhaltsvorschau
Substitution constants are the simplest type of constants. Basically, they are substituted for something else in code. See Example 7-1.
Example 7-1. Substitution constants
package oreilly.hcj.constants;

public class SubstitutionConstants {

  /** Holds the logging instance. */

  private static final Logger LOGGER = 

                Logger.getLogger(SubstitutionConstants.class);



  /** A value for PI. */

  public static final double PI = 3.141;



  public double getCircleArea(final double radius) {

    double area = (Math.pow(radius, 2) * PI);

    LOGGER.debug("The calculated area is " + area);

    return area;

  }



  public float calculateInterest(final float rate, 

                                 final float principal) {

    final String LOG_MSG1 = 

                                "Error: The interest rate cannot be less than ";

                   final String LOG_MSG2 = ". The rate input = ";

                   final double MIN_INTEREST = 0.0;



    // -- 

    if (rate < MIN_INTEREST) {

      LOGGER.error(LOG_MSG1 + MIN_INTEREST + LOG_MSG2 + rate);

      throw new IllegalArgumentException("rate");

    }

    return principal * rate;

  }

}
This code declares several substitution constants. The constant PI has class scope while the constants LOG_MSG1, LOG_MSG2, and MIN_INTEREST have method scope. All of these constants are declared and then later substituted in various parts of the application. In the case of the public final static primitives and String objects, the substitution occurs at compile time, according to the rules discussed in Chapter 2. In all other cases, it occurs at runtime.
The use of substitution constants is standard in most applications. However, there are a few things to note, even in this mundane bit of code. For instance, the only constant declared in the class scope is a public constant. If you remember the discussion of the final keyword, you will understand why. Since method-scoped constants are not accessible outside the method, it would be impossible to declare a public method-scoped constant. All of the other constants are used within only one method, so it would make no sense to declare them in class scope.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Bit Fields
Inhaltsvorschau
A bit field is a memory-saving tool that was developed early in the history of computer science. It saves memory by combining several true-false options into one byte. For example, a car may have several options that are either present or not present. These true or false options can be grouped into a bit field rather than using an entire byte for each of the options, which would be the case if you used booleans. The options in the bit field are represented as set or unset according to the status of a specific bit in the byte. These options can then be checked and compared using logical operators. One implementation of the hypothetical Car class is shown in Example 7-3.
Example 7-3. A car class that uses bit field constants
package oreilly.hcj.constants;

public class Car {

  public final static int POWER_WINDOWS = 1;

  public final static int POWER_LOCKS = 2;

  public final static int SNOW_TIRES = 4;

  public final static int STANDARD_TIRES = 8;

  public final static int CRUISE_CONTROL = 16;

  public final static int CD_PLAYER = 32;

  public final static int AIR_CONDITIONING = 64;

  

  private int options;



  public Car( ) {

  }



  public void setOptions(final int options) {

    this.options = options;

  }



  public int getOptions( ) {

    return this.options;

  }

}
In this example, a car can have several options, such as power windows and air conditioning. Each of the options are combined into a bit field and stored in the integer variable options. The bit field constants, such as POWER_WINDOWS, define which bit, within the options variable, represents that particular option. Since bits in a byte ascend in powers of two, these bit field constants will always be in powers of two as well. This is one easy way to distinguish bit fields from other types of constants.
If you want to determine whether the car has power windows, all that matters is the zero bit in the options integer variable. In Figure 7-1, a bit field is mapped out in memory.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Option Constants
Inhaltsvorschau
Option constants often bear a striking resemblance to bit field constants. However, the main difference between them is that a bit field uses powers of two for successive constants, while option constants are often numbered sequentially and use combinations that wouldn't make sense for a bit field. For example, consider this constant:
public final static int CONSTANT = 3;
If you see a constant such as this, the value of 3 is a dead giveaway that it isn't a bit field constant.
Option constants also differ from substitution constants in that they represent a concept and are not merely there for efficiency or readability. If you consider a constant other than an option constant, the situation becomes a bit easier to understand:
public final static int MAX_COKES_PER_DAY = 200;
Clearly, this constant is there to hold a significant value. However, the constant itself doesn't signify anything in particular. Compare that with the following examples of option constants:
public final static String LOCALE_US = 0;

public final static String LOCALE_UK = 1;

public final static String LOCALE_DE = 2;
Here, the constant represents various available options. Your code may use constants such as these to represent the three locales used in your GUI.
The most common kind of option constant is the integral option constant, which uses integral numbers to distinguish between option values, such as in the previous code snippet. However, you can use anything as the value of the option constant as long as there are no other constants for the same option that use the same value. The following code is valid even though integers are not used to distinguish option constants (although it is probably a little silly):
public final static int LOCALE_US = "fred";

public final static int LOCALE_UK = "joe";

public final static int LOCALE_DE = "john";
Option constants are concerned only with differentiation between the various constants, not with how this differentiation is accomplished. However, most option constants use integral values because these don't take as long to compare as string values.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Constant Objects
Inhaltsvorschau
Earlier, I said that you can use anything to differentiate option constants. Constant objects use final instances of final classes to declare constants and differentiate them. Each constant is an object that is an instance of the constant class. The constant object class groups options together by theme. To understand how constant objects work, look at the constant object class in Example 7-8.
Example 7-8. Exception types as constant objects
package oreilly.hcj.constants;

public final class NetAppException2 extends Exception {

  /** An undefined network exception. */

  public static final NetAppException2 NETWORK_UNDEFINED =

    new NetAppException2("NETWORK_UNDEFINED");



  /** A network exception caused by the server dropping you. */

  public static final NetAppException2 NETWORK_SEVER_RESET =

    new NetAppException2("NETWORK_SEVER_RESET");



  /** A network exception caused by undefined hostname. */

  public static final NetAppException2 NETWORK_INVALID_HOST_NAME =

    new NetAppException2("NETWORK_INVALID_HOST_NAME");



  /** A bad parameter exception. */

  public static final NetAppException2 LOGIC_BAD_PARAMETER =

    new NetAppException2("LOGIC_BAD_PARAMETER");



  /** An exception caused by failed authorization. */

  public static final NetAppException2 LOGIC_AUTHORIZATION_FAILED =

    new NetAppException2("LOGIC_AUTHORIZATION_FAILED");



  /** Holds the name of this constant. */

  private final String name;



  /** 

   * Creates a new NetAppException2 object.

   *

   * @param name The name for the exception type.

   */

  private NetAppException2(final String name) {

    this.name = name;

  }



  /** 

   * Get the name of this NetAppException2. 

   *

   * @return The name of the NetAppException2.

   */

  public String getName( ) {

    return this.name;

  }
In this code, constants are defined not by integers but by objects. Each constant is initialized during static initialization with a call to the private constructor and passes its name to the constructor for storage in a read-only variable.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Constant Encapsulation
Inhaltsvorschau
One final thing to note about constant objects is that they allow you a great deal of flexibility. You can put any kind of data in a derived constant object. For example, you may want abbreviations of commonly referenced country names. Example 7-14 shows an expanded constant object class with country abbreviations.
Example 7-14. Countries with abbreviations
package oreilly.hcj.constants;

public final class Country7 extends ConstantObject {

  public static final Country7 CANADA = new Country7("CANADA", "CA");

  public static final Country7 CROATIA = new Country7("CROATIA", "HR");

  public static final Country7 GERMANY = new Country7("GERMANY", "DE");

  public static final Country7 ITALY = new Country7("ITALY", "IT");

  public static final Country7 MEXICO = new Country7("MEXICO", "MX");

  public static final Country7 UK = new Country7("UK", "UK");

  public static final Country7 USA = new Country7("USA", "US");

  public static final Country7 VENEZUELA = 

                                 new Country7("VENEZUELA", "VZ");

  

  /** Holds the abbreviation for the Country. */

                 private final String abbreviation;



  private Country7(final String name, final String abbreviation) {

    super(name);

    this.abbreviation = abbreviation;

  }

  

  public final String getAbbreviation( ) {

                   return this.abbreviation;

                 }

}
Adding the abbreviation information is easy. You retain all the benefits of constant objects and can define more information than a simple cryptic number. There is practically no limit to the information you can add to constant objects.
However, like any power tool, constant objects can hurt you if you're not careful. Example 7-15 shows a trap you need to watch out for.
Example 7-15. A country object with a read/write property
package oreilly.hcj.constants;

public final class Country8 extends ConstantObject {

  public static final Country8 CANADA = new Country8("CANADA", 31.90f);

  public static final Country8 CROATIA = new Country8("CROATIA", 4.39f);

  public static final Country8 GERMANY = new Country8("GERMANY", 83.25f);

  public static final Country8 ITALY = new Country8("ITALY", 57.71f);

  public static final Country8 MEXICO = new Country8("MEXICO", 103.40f);

  public static final Country8 UK = new Country8("UK", 59.77f);

  public static final Country8 USA = new Country8("USA", 280.56f);

  public static final Country8 VENEZUELA = 

                                 new Country8("VENEZUELA", 24.28f);



Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 8: Data Modeling
Inhaltsvorschau
A data model is a set of objects designed to hold data concerning a particular business concept or application. A data model object is a component of a data model. A data model object differs from a regular object that holds data in that it must represent a particular business concept.
For example, the class Point from the java.awt package is an object that contains data but is not a data model object. Although it has a role in the system, it doesn't represent any particular business concept. On the other hand, a class such as Customer is a data model object.
This distinction is important because business data is sacred. Regardless of what you do in a software system, you should avoid corrupting a data model at all costs. It is the life blood of a business. Since data model objects are so important to the survivability of a business, you have to spend more time on them than you would on other objects. Your data model objects need to be more solid and bug-free than every other part of the code. This necessarily means that you will have to employ checking and error-detection procedures that you would normally not concern yourself with.
For example, consider the difference between an object that holds the items of a list box and a data model object. If the GUI object is corrupted with false data, the worst that could happen is that the GUI crashes or displays an address where it should display customer names. However, if a data model object is corrupted, your company could accidentally order 50,000 of the wrong type of part and consequently loose millions. Therefore, you can't afford to make mistakes; data should be modeled and checked 50 times by both you and your company before anyone touches a piece of business logic code.
Modeling data is the first step in the implementation of a software system. In fact, the data model forms the core of any software project. Without the data model, there is little point to the software application. Therefore, it is important to examine how data models are created by professional programmers. To explore this concept, let's take a quick break from our pure Java work to discuss some key concepts.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
The Requirements Document
Inhaltsvorschau
Ideally, a data model begins with a requirements document, in which the customer specifies all aspects of the system. Once this document is delivered, the architects analyze it and extract the data model. Unfortunately, real life is usually not so simple.
Customers rarely know enough about what they want in the software to write a full requirements document. While the people you work with may be quite intelligent, it's likely that their minds aren't attuned to organizing information for use in a piece of software. The solution to this problem is that you must write the requirements document for them. By talking to the customer's employees, you can learn a great deal about the company and its operations. This should enable you to write a first draft of the requirements document.
A good example of a company not being able to provide a requirements document is a genetics research company I worked for in Munich. Although the biologists and chemists I worked with were nothing less than brilliant, their minds were oriented toward biology and chemistry and not toward the organization of information within software. Recognizing that each person should stick to their gifts, I knew that I would never get an appropriate requirements document out of this group any more than they would get me to understand the finer points of genetics.
I set about preparing the requirements document by learning all I could about the business. Through long meetings, I discovered the core needs of the business. Since the job was rather large, I broke it up among the various developers working for me. One was responsible for working out the process flow while another was responsible for gathering GUI requirements. I arranged the information and coordinated the effort. Through various presentations and discussions, the information was altered and corrected by the biologists and chemists in an iterative manner until we had a document that was scientifically accurate.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Natural Language Modeling
Inhaltsvorschau
The next step is to turn the requirements document into a data model. For a simple system, it is not really difficult to figure out where to start. However, in a real-world setting, converting a requirements document to a data model can be an extremely daunting task. The natural language modeling approach makes this conversion much easier.
Natural language modeling looks at the requirements document as a discourse on the final state of the application. From this discourse, you can filter various elements, much like straining spaghetti. To do this, you need to be able to recognize which parts of the document to filter and which to leave intact.
I vividly recall a time when I was developing a huge application used in a genetic research company. One morning, I was sitting in my office staring at the 300-page requirements document. Using natural language modeling, I was able to extract a first draft of the more than 100-class data model.
Your data model is the noun set of your application. If you pour the requirements document through the "major noun strainer," you will find the data model of the application. The major nouns are simply the words that define business concepts, such as account or customer. Once you have identified these nouns, start organizing them into hierarchies of objects. Meanwhile, toss out the nonessential items that will inevitably seep in, and you're off to a flying start. By filtering parts of the requirement document, you begin to develop large chunks of your program. The initial picture will not be complete, but it will get you started. In fact, you should actually draw a picture at this point. Grab your modeling tool and start structuring classes and associations.
In the later stages of your development, you can pour the requirements document through a verb filter to organize all of the actions being done to the nouns. These verbs will help you create use cases to figure out which actions need to be supported in the system. For example, the requirement "A user can change her password" suggests that you need a use case to change passwords.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Aspects of Well-Designed Data Models
Inhaltsvorschau
For an application in a book, the data model is fairy substantive. However, it still doesn't encapsulate all of the functionality needed in a real banking application. You have necessarily limited its scope so that you can focus on concepts and not the intricacies of designing banking applications. This section will cover some of the more interesting aspects of data models.
One thing that you should notice about the Online Only Bank data model is that the AssetAccount class has no attributes or relationships (see Figure 8-1). The AssetAccount class is a special type of class called a ghost class . A ghost class marks a conceptual shift.
In your data model, the OnlineCheckingAccount and SavingsAccount classes are both assets to your customers. On the other hand, the CreditCardAccount and LoanAccount classes are liabilities to your customer. Without a marker class separating assets from liabilities, all four of these classes would inherit from Account, removing the important distinction between assets and liabilities.
By creating the ghost class AssetAccount, you gain much more flexibility, power, and long-term survivability in your data model. Since the OnlineCheckingAccount and SavingsAccount classes represent a conceptual difference between the other two accounts, you could conceive of a piece of logic that would have to make decisions based on this conceptual difference. For example, the officers of your bank may want to write a report of all the assets recorded for a particular customer. To complete this report, he would want to examine only the asset account objects.
Of course, you may consider using a flag in each class, and letting the flag differentiate between assets and liabilities. However, placing a flag in the class signaling whether the class is an asset or a liability is the wrong approach because if you think of another category of accounts and want to implement it in later revisions, a simple flag will not work. To fix this, you would have to go through all of the business logic and change the logic to account for the new account type. However, if your classes are conceptually divided with a ghost class, this work would be unnecessary. You would simply create a new ghost class for each new type of account, and the structure would be preserved. With this technique, the structure of the model itself is used to encapsulate information.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Reusable Data Constraints
Inhaltsvorschau
When placing data into the data model, you allow users to place practically anything into the various fields as long as they use the correct data type. For example, the user could input null for the interest rate and crash the system when it tries to calculate the interest. Even worse, a user could easily input -5.45 for the interest rate on her credit card. The bank may find it to be a little expensive to pay the customer 545% interest for running up her credit card! Obviously, you need to put some constraints on the data members throughout the model.
For the problematic interest rate, the natural thing to do would be to add checking code to the setter of the interest rate in the LiabilityAccount class. This code would prevent any negative values and constrain the rate to 1.0 or less. Your modified property setter would look like the following:
public void setInterestRate(final Float interestRate) {

  if ((interestRate.floatValue( ) > 1.0f) || 

                  (interestRate.floatValue( ) < 0.0f)) {

                throw new IllegalArgumentException( );

              }

  final Float oldInterestRate = this.interestRate;

  this.interestRate = interestRate;

  propertyChangeSupport.firePropertyChange("interestRate", 

      oldInterestRate, this.interestRate);

}
Although this checking code will work, it isn't very elegant or reusable. For example, if the GUI for your bank wants to display an entry field for the interest rate, it would have no idea what the legal values are. Also, if you hardcode the legal values into the GUI, you would have two sets of validation code to maintain. Instead, you should write a validator that can validate the interest rate whenever it is needed. Even better, the validator could tell you which rules it is applying so you could display them in a tool tip or in a text field in a web form.
You can do this by designing special-purpose validator objects that can be interrogated by other pieces of code to find out their rules. To start with, you will need a way to indicate that a validation failed.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Persistence
Inhaltsvorschau
Now that you have a data model that is nearly complete, you can start to consider where to store it. Although many developers automatically think of JDBC when someone says "persistence," that isn't the only way you can store your data. In fact, storing data in a relational database management system (RDBMS) may be the wrong choice.
Although RDBMSs are the most common data storage solution used in Java programming, they are often the wrong choice. Many systems would benefit far more from an object-oriented database management system (OODBMS). To understand why, it's important that you know the differences between relational and object databases.
Relational databases organize information into tables. These tables have columns for each piece of data and rows that organize the data into records. Each row is one record, and each column is a piece of that record. Connections between records of different types are made with relations. These databases can be easily expressed with an entity-relationship diagram such as the one in Figure 8-6.
Figure 8-6: An entity-relationship diagram
In this case, there are two tables, one with five columns and one with four. Each customer's purchases are stored in the Purchase table and are linked to the Customer table via a foreign key. To find a customer's purchases, execute a SQL statement such as the following:
select PURCHASE_ID from Purchase 

  where Purchase.CUSTOMER_ID = 80543
Those of you who have worked with relational databases are already familiar with this concept. However, the above statement has a deeper meaning that many developers fail to consider.
In the example, the select statement prompts the database to look through the Purchase table, checking each record for the proper
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 9: Practical Reflection
Inhaltsvorschau
Reflection is one of the least understood aspects of Java, but also one of the most powerful. Reflection is used in almost every major production product on the market, yet it often has to be learned from developers who are already experts, and these developers are often few and far between.
The reason for using reflection is because of the difficulties in building complex projects in a short time. As your projects become more advanced, you may find that you have less time to finish them.
For example, consider the Online Only Bank data model from Chapter 8. It was relatively small and simple to understand. When designing a GUI to manipulate this data model, you may be tempted to fire up a GUI builder tool and start making forms for each of the objects. In the end, you would have as many forms as there are data model objects. In fact, this is how most GUIs are built today.
However, consider if you used the same technique on a data model with over 200 data model objects and thousands of relationships among the items. In this situation, the idea of using the GUI builder tool is much less appealing—you would need to budget months of man-hours to complete the project. Furthermore, whenever any of these objects changes, even slightly, you would need to go back and fix all of the panels of the affected object. The icing on this particularly bitter cake is that you would have to create new panels each time a new object is introduced into the system. Clearly, this is not an effective method of delivering software in a timely manner.
GUI builders certainly have their uses, such as to prototype applications. However, you should be reluctant to rely on them in production products. Since they must be very general to be useful, the code that drives them is often bulky and inefficient. This is a major reason why Java GUIs are considered slow.
Because of this complexity, the current rage in the Java programming industry is to turn everything into a web application. In fact, many applications that should be GUI-based have been turned into web applications for this very reason. However, even if you decide to go the web application route, the problem of building efficient GUIs remains. Now, instead of having to build GUI panels, you have to build JSP pages for each of your data model objects. Again, the 200 data model objects require much more time to develop than you can spare. Clearly, you need a solution to this problem, and reflection may provide that solution.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
The Basics
Inhaltsvorschau
Suppose you are walking down the street and a stranger approaches and hands you an object. What would you do? Naturally, you would look at whatever was handed to you. You inspect the item, and in an instant determine that the object is a piece of paper. Instantly, you know many things about this object: one can write on it; it can have messages such as advertisements, warnings, news, and other useful information printed on it; and you can even crush it into a ball and play basketball when your boss isn't looking.
Though this is a silly example, it does illustrate the basic idea of reflection, which allows you to conduct a similar inspection of Java objects and classes. Using a combination of reflection and introspection, you can determine the nature and possible function of an object that you didn't know about at compile time. Furthermore, you can use this information to execute methods or set field values on the object.
To see how reflection works, let's start with a class from your Online Only Bank data model. In Example 9-1, the Person class from the data model is displayed without any comments or content from its methods.
Example 9-1. A Person in a bank data model
package oreilly.hcj.bankdata;



public class Person extends MutableObject {

  public static final ObjectConstraint GENDER_CONSTRAINT =

    new ObjectConstraint("gender", false, Gender.class);



  public static final StringConstraint FIRST_NAME_CONSTRAINT =

    new StringConstraint("firstName", false, 20);



  public static final StringConstraint LAST_NAME_CONSTRAINT =

    new StringConstraint("lastName", false, 40);



  public static final DateConstraint BIRTH_DATE_CONSTRAINT =

    new DateConstraint("birthDate", false, "01/01/1900", 

                       "12/31/3000", Locale.US);



  public static final StringConstraint TAX_ID_CONSTRAINT =

    new StringConstraint("taxID", false, 40);



  private Date birthDate = Calendar.getInstance( )

                                 .getTime( );



  private Gender gender = Gender.MALE;



  /** The first name of the person. */

  private String firstName = "<<NEW PERSON>>";



  private String lastName = "<<NEW PERSON>>";



  private String taxID = new String( );



  public void setBirthDate(final Date birthDate) {  }



  public Date getBirthDate( ) {  }



  public void setFirstName(final String firstName) {  }



  public String getFirstName( ) {  }



  public void setGender(final Gender gender) {  }



  public Gender getGender( ) {  }



  public void setLastName(final String lastName) {  }



  public String getLastName( ) {  }



  public void setTaxID(final String taxID) {  }



  public String getTaxID( ) {  }



  public boolean equals(final Object obj) {  }



  public int hashCode( ) {  }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Reflection and Greater Reflection
Inhaltsvorschau
The tools available to a reflection programmer are not limited to the java.lang.reflect package, as you may expect. In fact, they are spread all over the core of the JDK. All the tools used in reflection are often referred to as greater reflection . In this section, you will address each of the relevant packages and the tools within those packages and assemble an arsenal of reflection tools that will improve your programming.
Although this book isn't intended as a reference manual explaining how to use the individual methods on the reflection tools, there are several examples in the oreilly.hcj.reflection package. These examples demonstrate many of the methods and basic techniques of reflection.
This package contains the core functionality of reflection as well as the JDK itself. Since all classes ultimately inherit from java.lang.Object, the java.lang package plays a pivotal role in obtaining information about classes and objects.

Section 9.2.1.1: Class java.lang.Class

This class contains information about the object's class, such as which fields and methods it contains, and which package it is in. You used it before to obtain information about the methods in a class. See Example 9-2.

Section 9.2.1.2: Class java.lang.Object

From this class, you can get the Class instance for any object. Also, since the Object class is the basis for all constructed types in Java, it is used to pass parameters or get results from reflexively invoked methods and fields.

Section 9.2.1.3: Class java.lang.Package

This class encapsulates the runtime manifestation of a package. However, Package is not a particularly useful class because you can't ask it for all of the classes in a package. I rarely use this class in my reflection programming.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Applying Reflection to MutableObject
Inhaltsvorschau
Now that you have a thorough understanding of the tools used in reflection, it's time to put them to work to solve a practical problem.
In Chapter 8, we discussed how to implement a data model to encapsulate the data in a hypothetical bank. Most of the data model objects in this model are structurally the same. They differ only in the number, name, and types of properties they contain. Reflection can be used on this data model to implement several key features.
In your Online Only Bank data model, there are many classes that encapsulate data, and each has several properties. Although you can compare them and obtain their hashCode( ), you cannot print them to the console. This is the job of the toString( ) method. However, the default toString( ) method defined on class Object will print only the hash code and type of object. This is not enough information to do any serious debugging with these classes. What you really need is for the toString( ) method to print the values of the properties in the mutable object.
One tactic you could implement is to reabstract the toString( ) method and make each class declare its own toString( ) method. This would force developers to implement the method on each class, which would solve the problem. However, the implementations of the toString( ) method on each class would be the same, except for the names of the properties and classes; also, the method would have to be modified manually if new properties are added or removed. It would be better if you could save the users of MutableObject all of this work by implementing a toString( ) method in the MutableObject class that would print the properties of the derived classes. You can do this with reflection (see Example 9-4).
Example 9-4. The toString( ) method of MutableObject
package oreilly.hcj.datamodeling;

public abstract class MutableObject implements Serializable {



public String toString( ) {

    try {

Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Performance of Reflection
Inhaltsvorschau
Our discussion of caching constraint maps leads to the question of reflection's performance. I am frequently asked how expensive reflection is in terms of CPU and memory.
The answer is that reflection can be cheap or expensive depending on how you use it. No tool in any programming language has zero cost. Example 9-6 shows how reflection performs.
Example 9-6. Measuring reflection's performance
package oreilly.hcj.reflection;

public class ReflexiveInvocation {

  /** Holds value of property value. */

  private String value = "some value";



  public ReflexiveInvocation( ) {

  }



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

    try {

      final int CALL_AMOUNT = 1000000;

      final ReflexiveInvocation ri = new ReflexiveInvocation( );

      int idx = 0;



      // Call the method without using reflection.

      long millis = System.currentTimeMillis( );



      for (idx = 0; idx < CALL_AMOUNT; idx++) {

        ri.getValue( );

      }



      System.out.println("Calling method " + CALL_AMOUNT

                         + " times programatically took "

                         + (System.currentTimeMillis( ) - millis) 

                         + " millis");



      // Call while looking up the method at each iteration.

      Method md = null;

      millis = System.currentTimeMillis( );



      for (idx = 0; idx < CALL_AMOUNT; idx++) {

        md = ri.getClass( )

             .getMethod("getValue", null);

        md.invoke(ri, null);

      }



      System.out.println("Calling method " + CALL_AMOUNT

                         + " times reflexively with lookup took "

                         + (System.currentTimeMillis( ) - millis) 

                         + " millis");



      // Call using a cache of the method.

      md = ri.getClass( )

           .getMethod("getValue", null);

      millis = System.currentTimeMillis( );



      for (idx = 0; idx < CALL_AMOUNT; idx++) {

        md.invoke(ri, null);

      }



      System.out.println("Calling method " + CALL_AMOUNT

                         + " times reflexively with cache took "

                         + (System.currentTimeMillis( ) - millis) 

                         + " millis");

    } catch (final NoSuchMethodException ex) {

      throw new RuntimeException(ex);

    } catch (final InvocationTargetException ex) {

      throw new RuntimeException(ex);

    } catch (final IllegalAccessException ex) {

      throw new RuntimeException(ex);

    }  

  }



  public String getValue( ) {

    return this.value;

  } 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Reflection + JUnit = Stable Code
Inhaltsvorschau
When designing architecture code, unit testing can be an important process. With unit testing, units of the code are tested in a controlled environment before they are incorporated into the entire system. Unit testing allows developers to catch bugs before the code is integrated with several other classes. Also, creating reusable unit tests with frameworks such as JUnit allows the programmer to perform a regression test when other features are implemented; they simply need to run the test to verify that nothing was broken. The most important and common question surrounding unit testing is how much of the code should be tested.
Ideally, you should test everything in the product. Unfortunately, this is often not possible because of deadlines and other pressures. You could easily spend more time writing tests than code. However, all architecture code should be tested because of the weight of the system that will be resting on that code.
You may believe that testing is not worth the effort; after all, these objects are only beans that hold data and not business processes. However, reflection allows you to have your cake and eat it too.
With reflection, you can test a data model object regardless of which properties it declares:
package oreilly.hcj.reflection;

public class TestMutableObject extends TestCase {

  private Class type = null;



  protected TestMutableObject(final Class type, final String testName) {

    super(testName);

    assert (type != null);

    assert (MutableObject.class.isAssignableFrom(type));

    this.type = type;

  }



  public static Test suite(final Class type) {

    if (type == null) {

      throw new NullPointerException("type");  

    }

    if (!MutableObject.class.isAssignableFrom(type)) {

      throw new IllegalArgumentException("type");  

    }



    TestSuite suite = new TestSuite(type.getName( ));

    suite.addTest(new TestMutableObject(type, "testConstraintsExist"));

    return suite;

  }



  public void testConstraintsExist( ) {

    try {

     
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 10: Proxies
Inhaltsvorschau
The concept of a proxy is one of the most important aspects of modern programming. A proxy makes it possible to simplify many tasks that would be difficult and time-consuming otherwise. However, understanding proxies is often a difficult task because they are usually involved in much more complicated topics such as Enterprise JavaBeans (EJB) and remote method invocation (RMI). The developers of these technologies implement proxies for the users of these technologies, who are often not even aware that they are using a proxy.
To put it simply, a proxy is an object that stands in for another object and appears to perform the first object's functions. The object that the proxy imitates is called the implementation object . Instead of using the implementation directly, classes that want to access the features of the implementation use a proxy. For example, in an EJB system, the implementations are across the network from the clients that want to use them. The advantage of proxies is that they allow you to insert code between the implementation and the proxy. A proxy can hide complex tasks such as network communication and transaction management from the proxy user, all without changing the implementation object's functionality.
To better understand how proxies work, let's look at a class that implements two simple methods (see Example 10-1).
Example 10-1. An implementation object
package oreilly.hcj.proxies;

public class SomeClassImpl {

  

  private String userName;



  public SomeClassImpl(final String userName) {

    this.userName = userName;

  }



  public void someMethod( ) {

    System.out.println(this.userName);

  }



  public void someOtherMethod(final String text) {

    System.out.println(text);

  }

}
Since you want a user to be able to use this class without having direct access to it, you need to create a proxy. Let's begin with a simple proxy; we'll add to it as we proceed. Implement a series of methods that are identical to the implementation (see Example 10-2).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
What Is a Proxy?
Inhaltsvorschau
To put it simply, a proxy is an object that stands in for another object and appears to perform the first object's functions. The object that the proxy imitates is called the implementation object . Instead of using the implementation directly, classes that want to access the features of the implementation use a proxy. For example, in an EJB system, the implementations are across the network from the clients that want to use them. The advantage of proxies is that they allow you to insert code between the implementation and the proxy. A proxy can hide complex tasks such as network communication and transaction management from the proxy user, all without changing the implementation object's functionality.
To better understand how proxies work, let's look at a class that implements two simple methods (see Example 10-1).
Example 10-1. An implementation object
package oreilly.hcj.proxies;

public class SomeClassImpl {

  

  private String userName;



  public SomeClassImpl(final String userName) {

    this.userName = userName;

  }



  public void someMethod( ) {

    System.out.println(this.userName);

  }



  public void someOtherMethod(final String text) {

    System.out.println(text);

  }

}
Since you want a user to be able to use this class without having direct access to it, you need to create a proxy. Let's begin with a simple proxy; we'll add to it as we proceed. Implement a series of methods that are identical to the implementation (see Example 10-2).
Example 10-2. A simple proxy
package oreilly.hcj.proxies;

public class SomeClassProxy {

  private final SomeClassImpl impl;



  public SomeClassProxy(final SomeClassImpl impl) {

    this.impl = impl;

  }



  public void someMethod( ) {

    this.impl.someMethod( );

  }



  public void someOtherMethod(String text) {

    this.impl.someOtherMethod(text);

  }

}
This code creates a proxy to SomeClassImpl that looks exactly like SomeClassImpl
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Two Kinds of Proxies
Inhaltsvorschau
When working with proxies, there is a wide array of strategies available to you for implementing them. One strategy is the use of pre-compile-time tools such as those used by CORBA. Also, proxies can be manually written, reflexively driven, or dynamically generated.
Proxies that are written manually are referred to as static proxies . Static proxies are programmed into the system at compile time. Example 10-2 shows a simple static proxy.
Programming static proxies is a just like programming any other class with a couple of new rules:
  • The proxy can never extend the implementation (or vice versa). The proxy and implementation must form a delegation structure in which the proxy delegates to the implementation. This restriction exists because if the proxy simply extended the implementation, a user would be able to cast the proxy to the implementation and bypass the proxy altogether. For example, if you create a proxy to implement a security policy and the user can cast away the proxy, then you would have a huge hole in your security.
  • The user of the proxy should never create the implementation. If the user can create an implementation directly, then he can bypass the proxy and simply use the implementation. For example, the following usage of the proxy from Example 10-2 would be a bad idea because it would bypass any checking code in the proxy:
    package oreilly.hcj.proxies;
    
    public class DemoClientGeneratedProxy {
    
    
    
      public static final void main(final String[] args) {
    
        SomeClassProxy proxy = new SomeClassProxy(new SomeClassImpl("Fred"));
    
        proxy.someMethod( );
    
        proxy.someOtherMethod("Our Proxy works!");
    
      }
    
    }
    Since there is nothing to prevent a user from ignoring the proxy and going directly to the implementation, proxies should always be obtained from a factory or through calls to other proxies. Furthermore, the implementations should be protected from access by the client.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Proxy Gotchas
Inhaltsvorschau
As demonstrated in the previous sections, proxies are an extremely powerful tool in the creation of modern applications. However, as with any powerful tool, you can get into trouble if you aren't careful.
One of the prime dangers of using proxies is the performance hit they put on your application. When using a proxy, every call to the implementation goes through at least one extra method call. In proxies that perform more complex operations, each call may go through a large amount of code before it contacts the implementation and returns the result of the call. The accumulated performance hit in an application can be significant.
A major annoyance of dynamic proxies is that the class has to be designed with the dynamic proxy in mind. If the class does not implement an interface, you can't create a proxy to a class using that interface. Although this is not such a big deal in code over which you have control, in third-party libraries, it can be a pain.
As long as you keep the limitations of proxies in mind, judicious use of static and dynamic proxies can provide quite a bit of power to your development.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 11: References in Four Flavors
Inhaltsvorschau
The java.lang.ref package is criminally underused in Java programming. Despite the enormous power of the classes within this package, you rarely see them in mainstream coding. Most likely, this is because using these classes properly requires a fairly deep understanding of the garbage-collection infrastructure in Java. However, once you understand garbage collection, you can create code that uses resources efficiently.
One of the critical problems in the Online Only Bank data model from Chapter 8 is the high potential for memory leaks. In fact, the process of garbage collection is not enough to protect you from memory leaks. Garbage collection protects you from only one kind of memory leak, known as a lost pointer.
In languages such as C++, a memory leak resembles a lost pointer to an allocated block of memory. In this scenario, an object asks the runtime environment to allocate a block of memory. However, after this allocation, the pointer to the block is lost, perhaps through method return. There is a memory leak because there is a dead stack of allocated memory that the computer cannot use and to which the program can no longer get a pointer. In Java, this type of memory leak is not possible.
However, Java suffers from a different type of memory leak based on the garbage-collection paradigm itself. Since all objects in Java are references, they form an intricate web of associations; these associations can cause a memory leak. Consider the JavaBean data class in Example 11-1.
Example 11-1. A basic JavaBean data class
package oreilly.hcj.references;



import java.beans.PropertyChangeListener;

import java.beans.PropertyChangeSupport;



public class SomeDataClass {



  protected PropertyChangeSupport propertyChangeSupport =

    new java.beans.PropertyChangeSupport(this);



  private int age = 0;



  public void setAge(final int value) {

    final int oldAge = this.age;

    this.age = value;

    this.propertyChangeSupport.firePropertyChange("value", oldAge, this.age);

  }



  public int getAge( ) {

    return this.age;

  }



  public void addPropertyChangeListener(final PropertyChangeListener lst) {

    propertyChangeSupport.addPropertyChangeListener(lst);

  }



  public void removePropertyChangeListener(final PropertyChangeListener lst) {

    propertyChangeSupport.removePropertyChangeListener(lst);

  }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
The Problem
Inhaltsvorschau
One of the critical problems in the Online Only Bank data model from Chapter 8 is the high potential for memory leaks. In fact, the process of garbage collection is not enough to protect you from memory leaks. Garbage collection protects you from only one kind of memory leak, known as a lost pointer.
In languages such as C++, a memory leak resembles a lost pointer to an allocated block of memory. In this scenario, an object asks the runtime environment to allocate a block of memory. However, after this allocation, the pointer to the block is lost, perhaps through method return. There is a memory leak because there is a dead stack of allocated memory that the computer cannot use and to which the program can no longer get a pointer. In Java, this type of memory leak is not possible.
However, Java suffers from a different type of memory leak based on the garbage-collection paradigm itself. Since all objects in Java are references, they form an intricate web of associations; these associations can cause a memory leak. Consider the JavaBean data class in Example 11-1.
Example 11-1. A basic JavaBean data class
package oreilly.hcj.references;



import java.beans.PropertyChangeListener;

import java.beans.PropertyChangeSupport;



public class SomeDataClass {



  protected PropertyChangeSupport propertyChangeSupport =

    new java.beans.PropertyChangeSupport(this);



  private int age = 0;



  public void setAge(final int value) {

    final int oldAge = this.age;

    this.age = value;

    this.propertyChangeSupport.firePropertyChange("value", oldAge, this.age);

  }



  public int getAge( ) {

    return this.age;

  }



  public void addPropertyChangeListener(final PropertyChangeListener lst) {

    propertyChangeSupport.addPropertyChangeListener(lst);

  }



  public void removePropertyChangeListener(final PropertyChangeListener lst) {

    propertyChangeSupport.removePropertyChangeListener(lst);

  }

}
This class is a standard JavaBean with a single bound property named
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Java Reference Concepts
Inhaltsvorschau
Before you turn your attention to solving memory leak problems, it is important that you understand how Java references work. There are four different types of references in Java: strong, weak, soft, and phantom.
Strong references are the mainstay of the Java world. As I look at the stacks and stacks of Java books on my desk, I can't find one code sample that uses anything other than strong references.
A strong reference is a reference type that pins objects in the memory so that the objects can't be garbage collected. Strong references are used whenever an assignment is made to a normal variable. For example, consider the following code:
String name = "Robert Simmons";

Set nameSet = new HashSet( );

set.add(name);
This code creates three different strong references. The reference name points to the memory location holding the contents of the immutable String object. The reference nameSet holds the memory location of the newly created Set. Finally, another strong reference to the same object that name points to is inside the HashSet. This third reference was created when the name was passed to the add( ) method.
In every virtual machine, there is a set of references called the root set, which is created and maintained by the virtual machine. From the root set, each of the objects in the virtual machine is referenced. When you create a main function in your program, all of the objects declared in this method are attached to the root set. Consequently, each of the objects declared in these objects are attached to the root set indirectly.
Whenever a path of strong references cannot be drawn from the root set to an object, the object is marked for garbage collection and is removed on the next pass. This process recurses throughout the network until all garbage-collectible references are gone. To make this a little easier to understand, consider the typical Java program shown in Figure 11-1.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
The Java Reference Classes
Inhaltsvorschau
Now that you understand the concepts, let's reconsider the code. All of the reference types mentioned in the previous section appear in the java.lang.ref package.
The majority of the java.lang.ref package is comprised of reference classes. These classes are the implementation of the actual references covered in the previous section, with one exception. There is no StrongReference class. Since a strong reference is a normal Java variable, there is no need for extra implementation. However, the other types of references all descend from the base class Reference.

Section 11.3.1.1: java.lang.ref.Reference

The Reference class is an abstract class that defines the functionality of the reference classes. The implementation classes—WeakReference, SoftReference, and PhantomReference—implement the functionality of the base class. All of these classes have four methods that they inherit from Reference:
clear( )
Clears the referent, which causes the referent in this reference object to be set to null without affecting the object itself. I have never found a use for this method, since there is no way to reset the referent after it has been cleared.
enqueue( )
Registers a reference with a ReferenceQueue. This allows the user to receive updates about the status of the object in memory. A reference will be placed in a reference queue at garbage-collection time only if it was registered with that queue. If it succeeds in registering the reference, the method will return true; otherwise, it will return false. Keep in mind that a reference can be registered only with one queue at a time, and if you call this method on a reference that is already registered with a queue, the object will be unregistered from the previous queue. If you call
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Practical Applications
Inhaltsvorschau
In the Online Only Bank data model, the JavaBeans mechanism was employed to allow users of the data model to register for property change events. This gives you a great deal of functionality in these classes, but it also introduces the potential memory leak discussed earlier. Your data model objects will be referred to by various parts of the program that will then register themselves as listeners for property change events, creating a cycle of strong-reference dependencies.
Ideally, you should be able to alter your code so that the objects in the data model register their property change listeners in a nonbinding way. To do this, you need to create a different kind of collection in which to store your listeners—a collection based on weak references.
To store your listeners, create a class called WeakHashSet . If you look in the JDK source code for HashSet, you will see that it is implemented by using the keys in the HashMap class. You implement WeakHashSet in a similar way, using the java.lang.util.WeakHashMap class as the backing store for the WeakHashSet contents. The keys in WeakHashMap are stored in weak references instead of in strong references, so this class will fit your needs nicely.
The fact that the class WeakHashSet isn't in the JDK already is something of a mystery. Sun implemented the WeakHashMap class but failed to finish the job with the WeakHashSet.
I won't bore you with all of the implementation details, since most of them are fairly trivial. If you want to read the whole source, you can find it in the oreilly.hcj.references package. However, there are some noteworthy aspects of your new WeakHashSet class. Consider this source snippet:
public class WeakHashSet extends AbstractSet implements Set {



  private static final Object DUMMY = new String("DUMMY");  // $NON-NLS-1$

  WeakHashMap backingStore = new WeakHashMap( );





  public boolean add(final Object o) {

    if (o == null) {

      throw new NullPointerException( );

    }

    return backingStore.put(o, DUMMY) == null;

  }
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
A Weak Listener
Inhaltsvorschau
Now that you have a tested collection that doesn't hold objects in memory, you can use it to break the loop of strong references. Instead of holding the listeners in a normal set, put them in WeakHashSet. To do this, you will have to rewrite the property change support class and invent your own support class that does the same job with weak listeners. The code for PropertyChangeSupport is in the oreilly.hcj.references package.
This PropertyChangeSupport class implements the identical interface as the stock java.beans.PropertyChangeSupport class except that it uses weak references to store the listeners. It allows the same firing of events that the original JDK class does without pinning the listeners in memory. If one of the listeners decides to go away, the property change support class will simply note this fact and move on.
The class is implemented using a regular HashMap to store the listeners. The keys in the HashMap (listenerMap) are the named properties that the listener is interested in. The values in the HashMap are instances of WeakHashSet. This allows the listener to register to receive events on single properties. For listeners that want to receive events on all properties of the class, an ALL_PROPERTIES key is used to store the set. To avoid conflicting keys in your map, the value of the ALL_PROPERTIES key is a string that would be illegal to use as a property name:
package oreilly.hcj.references;

public class PropertyChangeSupport {

  private static final String ALL_PROPERTIES = "**GENERAL**";
The declaration of the constructor initializes the HashMap with the ALL_PROPERTIES key and the keys for the other properties in the class:
  public PropertyChangeSupport(final Object producer) {

    try {

      final BeanInfo info = Introspector.getBeanInfo(producer.getClass( ));

      final PropertyDescriptor[] props = info.getPropertyDescriptors( );

      for (int idx = 0; idx < props.length; idx++) {

        listenerMap.put(props[idx].getName( ), new WeakHashSet( ));

      }

      listenerMap.put(ALL_PROPERTIES, new WeakHashSet( ));

      this.producer = producer;

    } catch (IntrospectionException ex) {

      throw new RuntimeException(ex);

    }

  }
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
When to Use References
Inhaltsvorschau
Now that you have learned how to create a weak listener, you can create weak key listeners, weak action listeners, and so on. In fact, the more strong-reference loops you can eliminate in your reference tree, the better your code will be. However, you should be careful. Like any other powerful tool, weak references can be dangerous if used improperly. Therefore, it is important to know when to use them and when not to. To make this decision, you need to know which stereotype the reference is. References from an object can be classified into one of four stereotypes:
Active references
These need other objects to exist for them to do their job. For example, your GUI panel needs the data object before it can present it. If the data object suddenly went out of scope, the GUI wouldn't be able to display or modify its data. Active references should always be strong references.
Passive references
These are indifferent to the presence of other objects. For example, the data objects are passive to the GUI. If the GUI panel exists, then the data object will do it the courtesy of informing it of its changes. However, the data object doesn't need the GUI object to do its job. Passive references should be weak references.
Convenience references
These exist for the user's convenience but are not necessarily required. For example, in creating data cache management software such as that used by database persistence managers, the cache is designed to return the objects if they are in the cache and fetch them if they aren't. In this case, keeping the objects in memory is desirable because doing so lowers the number of expensive database fetches that the software has to do. However, it isn't required that these objects remain in the cache if they are not being used. Convenience references should be soft references.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 12: Tiger: JDK 1.5
Inhaltsvorschau
A major source of debate in the Java community is whether the new release of the JDK, called "Tiger," should instead be called JDK 2.0. In many ways, Tiger represents a fundamental improvement in the Java language that hasn't been seen since the emergence of the Java Foundation Classes (JFC).
In addition to offering bug fixes and new classes, Tiger offers several new language features and an implementation of parameterized types called generics. The concept of generics brings Java collections into the world of strong typing and is one of the most conspicuous components missing from JDK 1.4. Generics will be covered later in the chapter; first, we will tackle the new language features.
Before you read on, note that the information presented in this chapter is in a state of flux. As of this writing, the information is current. However, the process of defining Tiger is still ongoing, and updates or changes may have been implemented between the writing and publication of this book.
Tiger has several new language features designed to eradicate some of Java's annoyances. Many of these features are borrowed from other languages such as Python or Perl, so if you have experience with these languages, these new features should be familiar to you. However, there are idiosyncrasies of these features that Python and Perl users may not be familiar with.
One useful feature of Tiger is the new for-each syntax. The purpose of this syntax is to make it easier to iterate through collections and arrays. For example, consider the following JDK 1.4 code:
package oreilly.hcj.tiger.

public class ForEach {

  public final static String[] importantPeople = new String[] {

              "Robert", "Jim", "Sacir", "Aida", "Alma",

              "Amila", "Selma", "Nefisa", "Mustafa",

              "Paul", "Debbie", "Marco", "Bettina", "Ana",

              "Maria" };



  public static void someMethod(final String prefix) {

    for (int idx = 0; idx < importantPeople.length; idx++) {

      if (importantPeople[idx].startsWith(prefix)) {

        System.out.print(importantPeople[idx] + " ");

      }

    }

    System.out.println( );

  }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
New Language Features
Inhaltsvorschau
Tiger has several new language features designed to eradicate some of Java's annoyances. Many of these features are borrowed from other languages such as Python or Perl, so if you have experience with these languages, these new features should be familiar to you. However, there are idiosyncrasies of these features that Python and Perl users may not be familiar with.
One useful feature of Tiger is the new for-each syntax. The purpose of this syntax is to make it easier to iterate through collections and arrays. For example, consider the following JDK 1.4 code:
package oreilly.hcj.tiger.

public class ForEach {

  public final static String[] importantPeople = new String[] {

              "Robert", "Jim", "Sacir", "Aida", "Alma",

              "Amila", "Selma", "Nefisa", "Mustafa",

              "Paul", "Debbie", "Marco", "Bettina", "Ana",

              "Maria" };



  public static void someMethod(final String prefix) {

    for (int idx = 0; idx < importantPeople.length; idx++) {

      if (importantPeople[idx].startsWith(prefix)) {

        System.out.print(importantPeople[idx] + " ");

      }

    }

    System.out.println( );

  }

}
Although this code works fine, the for statement is fairly wordy, which makes it annoying to type if it is used hundreds of times in an application. The new for-each feature makes this code much simpler:
package oreilly.hcj.tiger.

public class ForEach {

  public static void someTigerMethod(final String prefix) {

    for (String person: importantPeople) {

                     if (person.startsWith(prefix)) {

                       System.out.print(person + " ");

                     }

                   }

    System.out.println( );

  }

}
If you are wondering why Sun didn't create a new keyword named foreach for the implementation of the for-each functionality, you are not alone. The official explanation from Sun is that they didn't do this because they didn't want to break the code of those who used
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Generics
Inhaltsvorschau
The implementation of parameterized types in Tiger is called generics. Generics allow you to build parameterized types that are checked at compile time. In addition to the new parameterized collection types that are implemented as part of Tiger, you can define new parameterized types to fit your needs.
For those who want to look up the specifications of generics, they can be found under JSR 14. However, as of this writing, the version included in the public prototype implementation is old, and some parts of it are inaccurate.
One of the most persistent problems in Java is the lack of type safety in collections. For example, you can declare a new Set that should contain Address objects in the following manner:
Set addresses = new HashSet( );  // Component type Address
Once this set is declared, you are basically trusting your users never to place anything other than an Address in the set. If a user adds something other than an address to the set, the most fortunate result that could occur would be your program crashing outright on a ClassCastException. However, if you are not so fortunate, you could end up with irreparable data corruption. If this set is used as a part of a business-critical data model, such as the Online Only Bank data model in Chapter 8, you could end up destroying the entire business by implementing this software. Therefore, whenever someone calls a method to set your addresses property, you should check every member of the collection to make sure that it is a legal type (see Chapter 8).
The problem with checking elements in a collection at runtime is that it is extremely expensive; the order of efficiency is only O(n). If you have only 10 addresses in your collection, checking elements is easy. However, if the collection contains 15,000 addresses, then you would incur a significant overhead whenever someone calls the setter.
On the other hand, if you can prevent users from placing anything other than an address in your collection at compile time, then you wouldn't have to check the types at runtime. If they try to give you something that isn't an address, then the compiler will reject the attempt. This is exactly what parameterized types do.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Other Improvements in Tiger
Inhaltsvorschau
There are many improvements in Tiger other than those covered in this chapter. Many of these improvements are under the hood and involve thread scheduling or other virtual-machine issues that are outside the scope of this book. For those of you interested in learning more about Tiger, take a look at JSR 176, which is available on the Java Community Process page at http://www.jcp.org/en/jsr/detail?id=176. These specifications will provide you with all the details you could possibly want about the implementation of the various components of Tiger.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
	

Zurück zu Hardcore Java


Themen

Buchreihen

Special Interest

International Sites

O'Reilly China O'Reilly USA O'Reilly Japan O'Reilly Taiwan