Java.next -- Four languages that represent the future of Java
Blogger Stuart Halloway has begun a series of posts on trends that point to the future of the Java platform. In his first post, he compares Clojure, Groovy, JRuby, and Scala -- four wildly different languages that nonetheless all play together in the JRE. Find out what unites these languages and what they can tell us about the future of Java-based development ...

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Steer clear of Java pitfalls

Three tips to keep you from falling into a Java trap

Though the Java language and platform strive to make programmers' lives easier, unfortunately, as in all systems, overly complex and poorly designed areas crop up. Every other month, I will present workarounds to these pitfalls in this new column, Java Traps.

The book Java Pitfalls (John Wiley & Sons, 2000), written by Eric Monk, J. Paul Keller, Keith Bohnenberger, and myself, defines a pitfall as "code that compiles fine but when executed produces unintended and sometimes disastrous results." This broad definition encompasses improper language usage, overly complex APIs, and less effective implementation options. The book details 50 pitfalls. Here, we will discuss three new ones: two API pitfalls and one long-standing bug. All of these problems arose during real-world development projects.

Pitfall 1: When setSize() doesn't work

Most developers stumble upon pitfalls sequentially as they become more experienced with Java. The setSize() pitfall usually presents itself soon after Java developers begin serious GUI development -- specifically, when they first attempt to set the size of a custom component. BadSetSizeApplet below intends to create a simple custom button, sized at 100 pixels by 100 pixels. Here is the code to create our custom button:

class CustomButton extends Button
{
    public CustomButton(String title)
    {
        super(title);    
        setSize(100,100);
    }
}


In the constructor, developers often assume that they can use setSize()(width, height), as they do when sizing a frame. But this code will only work in certain situations -- here, setSize() will fail to correctly size the component. When we place our custom button in the applet with other components using a simple grid layout, we get the results below. Our button is 66 by 23, not 100 by 100! What happened to our call to setSize()? The method was executed, of course. However, it did not give the final word on the size of our component.

You will find the complete source code for BadSetSizeApplet.java, and for all applets discussed in this article, in Resources.

Let's examine the correct approach to sizing a component.

Our code failed because after we created the component, the layout manager (GridLayout) reshaped the component in accordance with its own rules. This presents us with several solutions. We could eliminate the layout manager by calling setLayout(null), but since the layout manager provides numerous benefits to our code (it allows us to automatically resize our user interface, even if the user resizes the window), this is a poor remedy. Another alternative would be to call setSize() after the layout manager has completed its work. This is just a quick fix: calling repaint() would change the size, but it would change again when the browser was resized. That leaves us with only one real option: work only with the layout manager to resize the component. Below we rewrite our custom component:

class CustomButton2 extends Button
{
    public CustomButton2(String title)
    {
        super(title);    
        // setSize(100,100); - unnecessary
    }
    public Dimension getMinimumSize()
    { return new Dimension(100,100); }
    
    public Dimension getPreferredSize()
    { return getMinimumSize(); }
}


Our custom component overrides the getMinimumSize() and getPreferredSize() methods of the Component class to set the component size. The layout manager implements those methods to decide how to size an individual component. Some layout managers will disregard the methods' advice, if their pattern calls for that. For example, if this button was placed in the center of a BorderLayout, the button would not be 100 by 100, but would stretch to fit the available center space. GridLayout will abide by those sizes and anchor the component in the center. The GoodSetSizeApplet below uses the CustomButton2 class.

Interestingly, our solution did not involve the setSize() method. This pitfall stems from the design complexity of a cross-platform user interface, and a developer's unfamiliarity with the chain of events necessary to display and resize an interface. Unfortunately, the supplied documentation of setSize() fails to suggest these prerequisites.

This solution also highlights the importance of properly naming methods and parameters. Should you use setSize() when you only need to set internal values that may or may not be used by your display mechanisms? A better choice would be setInternalValues(), which at least clearly warns a developer of the limited guarantee this method offers.

Pitfall 2: A misleading StringTokenizer parameter

This pitfall, also a result of poor naming conventions, revealed itself when a junior developer needed to parse a text file that used a three-character delimiter (his was the string ###) between tokens. In his first attempt, he used the StringTokenizer class to parse the input text. He sought my advice when he discovered what he considered to be strange behavior. The applet below demonstrates code similar to his.

The developer expected six tokens, but if a single # character was present in any token, he received more. He wanted the delimiter to be the group of three # characters, not a single # character.

Here is the key code used to parse the input string into an array of tokens:

    public static String [] tokenize(String input, String delimiter)
    {
        Vector v = new Vector();
        StringTokenizer t = new StringTokenizer(input, delimiter);
        String cmd[] = null;
        while (t.hasMoreTokens())
            v.addElement(t.nextToken());
        
        int cnt = v.size();
        if (cnt > 0)
        {
            cmd = new String[cnt];
            v.copyInto(cmd);
        }
        return cmd;        
    }


The tokenize() method is a wrapper for the StringTokenizer class. The StringTokenizer constructor takes two String arguments: one for the input and one for the delimiter. The junior developer incorrectly inferred that the delimiter parameter would be treated as a group of characters, not a set of single characters. I don't think that's such a poor assumption. With thousands of classes in the Java APIs, the burden of design simplicity rests on the designer's shoulders, not the application developer's. It is reasonable to assume that a String would be treated as a single group. After all, a String commonly represents a related grouping of characters.

A more correct StringTokenizer constructor would require the developer to provide an array of characters, which would clarify the fact that the delimiters for the current implementation of StringTokenizer are only single characters -- though you can specify more than one. This particular API designer was more concerned with his implementation's rapid development than its intuitiveness.

To fix the problem, we create two new static tokenize() methods: one that takes an array of characters as delimiters, one that accepts a Boolean flag to signify whether the String delimiter should be regarded as a single group. Here is the code for those two methods:

    // String tokenizer with current behavior
    public static String [] tokenize(String input, char [] delimiters)
    {
        return tokenize(input, new String(delimiters), false);
    }
    public static String [] tokenize(String input, String delimiters, 
               boolean delimiterAsGroup)
    {
        Vector v = new Vector();
        String toks[] = null;
        if (!delimiterAsGroup)
        {
            StringTokenizer t = new StringTokenizer(input, delimiters);
            while (t.hasMoreTokens())
                v.addElement(t.nextToken());
        }
        else
        {
            int start = 0;
            int end = input.length();
            while (start < end)
            {
                    int delimIdx = input.indexOf(delimiters,start);
                    if (delimIdx < 0)
                    {
                            String tok = input.substring(start);
                            v.addElement(tok);
                            start = end;
                    }
                    else
                    {
                            String tok = input.substring(start, delimIdx);
                            v.addElement(tok);
                            start = delimIdx + delimiters.length();
                    }
            }
        }
        int cnt = v.size();
        if (cnt > 0)
        {
            toks = new String[cnt];
            v.copyInto(toks);
        }
        
        return toks;
    }


Below is an applet demonstrating the new static method, tokenize(), that treats the token String ### as a single delimiter.

While some may consider the above pitfall relatively harmless, the next is extremely dangerous and should be seriously considered in any Java development project.

Pitfall 3: Don't mix floats and doubles when generating text or XML messages

While developing an order execution system for an online brokerage, I stumbled across a serious bug that incorrectly converted certain values from doubles to strings. Here is the scenario: The Website presents a stock-trade form to the user. A Java servlet processes the form and sends the trade information to the order execution server, a Java RMI server. The Java RMI server formats the message as either XML or another text format -- the common message switch (CMS) format, for example -- and passes it to one of several executing agents. One of the fields in the stock-trade message is the stock price, which is stored as a double. For certain double values, the Java platform incorrectly converts the price when formatting the order message, and the trade is rejected. Customers don't like that!

What if this was embedded software in a medical device, and the double value represented the amount of radiation administered to a patient? A low-level bug like this can be extremely dangerous.

Below is an applet that simulates the above scenario and generates two stock transaction messages. The first price formats correctly, while the second value -- 100.28 -- formats incorrectly.

Resources