Overloading int considered harmful

When I was writing JavaBeans: Developing Component Software in Java, I lost over a day hunting for one bug. The program compiled and ran; but a label that was supposed to appear in the GUI wouldn’t show up, no matter what I did! The problem turned out to be in this line of code:

Font f = new Font("Serif", 12, Font.BOLD);

Can you see the bug? It’s certainly not obvious. In fact it’s so inobvious that I’ve made this mistake probably a dozen times since then, and it’s been a hard one to find every single time. I almost guarantee you can’t spot the bug without looking at the declaration for the Font constructor:

public Font(String name, int style, int size)

Still don’t see the bug? OK, I’ll let you in on the secret. The problem is that the last two arguments are swapped. It should be:

Font f = new Font("Serif", Font.BOLD, 12);

That’s it! However, since the named constant Font.BOLD is declared to be of type int, the compiler can’t catch this error. The programmer types:

Font f = new Font("Serif", 12, Font.BOLD);

However what the compiler sees is:

Font f = new Font("Serif", 12, 3);

The compiler compiles it without complaining, because all the types match, and the runtime tries to give you a font that’s 3-points high (too small to actually see on many displays) in an unspecified font style.

A better designed Font constructor would allow this error to be caught at compile time. How, you ask? It’s simple really. There’s no reason the style argument to the constructor has to be an int. It doesn’t make sense to add Font.BOLD to Font.PLAIN for example, or multiply Font.ITALIC by 42. The int is simply used as a convenient type for an argument that takes on only one of about four possible values. This is a standard technique in non-object-oriented languages like C and Pascal, but object oriented languages like Java offer programmers a much superior solution. Define a new type whose only purpose is to serve as the argument to the method. In this case, it would be straight-forward to define a FontStyle class like this:

public class FontStyle {

  public static FontStyle PLAIN  = new FontStyle();
  public static FontStyle BOLD   = new FontStyle();
  public static FontStyle ITALIC = new FontStyle();

  private FontStyle() {};

}

This both establishes the type and limits the values the type can take on. Since the constructor is private, no new instances of this class can be created by other classes. The Font constructor could be rewritten like this:

public Font(String name, FontStyle style, int size)

With the constructor, I could still make the error of swapping the two arguments, but the compiler would catch it immediately! It’s always easier to let the compiler catch your errors rather than try to find them at runtime using a debugger or testing tools.

I first encountered this technique in the first edition of Graphic Java by David M. Geary and Alan L. McClellan, though doubtless it’s been kicking around the object oriented community for some time. Sun has begun to come around to this way of thinking, and many of the more recent Java APIs use this technique. For example, the HyperLinkEvent.EventType class in javax.swing.text.event uses this pattern to define the three types of hyperlink events responded to by a HyperLinkListener: HyperlinkEvent.EventType.EXITED, HyperlinkEvent.EventType.ENTERED and HyperlinkEvent.EventType.ACTIVATED. I use this pattern in all of my own code, and I encourage you to do so too.

8 Responses to “Overloading int considered harmful”

  1. Bruce Eckel Says:

    This is what Java 5 enums are for!

    Also, see Josh Bloch’s Effective Java, where he details a pre-5 enumeration idiom. I also cover this in Thinking in Java.

  2. Elliotte Rusty Harold Says:

    Part 2

    I am working on a second half to this article that will consider:

    ClassLoader issues
    Serialization
    Enums

  3. Matt Plumtree Says:

    You would need to have a FontStyle.BOLDITALIC. Or is that FontStyle.ITALICBOLD? Or FontStyle.BOLD_ITALIC? Or a FontStyle.and(FontStyle other) method? Or something.

    This is a slight disadvantage of using true types for combinations of values, that using ints doesn’t have.

    Pre-canned combinations are going to be more efficient than using a combining method, since they will be created once then assigned straight into classes as they are loaded, but their number increases exponentially as more mutually inclusive options are added. Consider all the constants to enter (and their names) if you had 8 options that could be combined in any way.

    You may also need to have big switch statements in the implementation of Font, to deal with each combination, possibly just to turn them back into a bitfield. Alternatively you could embiggen FontStyle to include isBold() and isItalic() methods. This would require initializers for each style, probably bringing us back to bitfields again. However, at least they would be localized to the FontStyle code.

  4. Elliotte Rusty Harold Says:

    Combined definitions

    Using bitwise constants and bitwise arithmetic to pick font styles is pretty old, C-style thinking. It’s a neat hack; but it’s not really necessary here. The right way to solve this problem is to replace setStyle with applyStyle or addStyle or some such method. You may want a removeStyle method too. An added advantage of this is you’re no longer limited to a maximum of 8 or 32 constant values. You can define as many as you need.

  5. paulm Says:

    Typesafe Enum

    Joshua Bloch clearly defined the technique as item 21 of Effective Java. The only time it is less than convenient is when you want to use a switch statement. Even then you can have the object generate/return a unique integer.

  6. edavies Says:

    Pascal enumerations

    Pascal had enumerated types whose values were disjoint from the integers over twenty years ago.

  7. Matt Plumtree Says:

    Bitwise constants

    I’m not really disagreeing with you. The whole API could do with a thorough going over to use more appropriate idioms. Presumably your applyStyle / addStyle methods would use a Set internally? Overall this will add overhead relative to the bitwise operations, but it must be argued that this is quite acceptable overhead. The resultant API will be clearer and, as you point out in the original posting, less fragile.

    The usual OOP way to avoid the switch would be to have an actual method of FontStyle to do the work. Then, the style set would be iterated over, getting each one to apply its feature. I’m not sure this would be entirely appropriate for this example, but it would depend on just how font styles were implemented.

    I suppose additional styles you could add would be things like underline, strike through, superscript, subscript and so on, much as you might see in a word processor’s font characteristics dialog.

    I think it just has to be accepted that there are many parts of the Java API that are immature. I know you have argued before for a proper cleanup, but Sun’s preference for compatibility generally win out.

  8. andyjbs Says:

    Switches & bitwise constants

    >Paulm:

    >The only time it is less than convenient is when you want to use a
    >switch statement.

    In Java 5 the new language enum construct can be used directly
    with a switch statement. In Java 1.4 with the enum pattern you’d
    just include an integer constant as an internal member of your enum
    class and switch on ENUM_VALUE.intValue() instead.

    >Matt Plumtree:
    >Presumably your applyStyle / addStyle methods would use a Set
    >internally? Overall this will add overhead relative to the bitwise
    >operations, but it must be argued that this is quite
    >acceptable overhead.

    Similarly, if you really need bitwise operations, each enum
    value (either the new Java 5 ones or the old 1.4 enum pattern) can
    be given an integer value of your choice to do bitwise operations
    with. You’d need to worry about serialization and forward
    compatibility, but they are not difficult problems to solve. I’d
    probably just use a Set or something similar
    though.