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.
November 17th, 2004 at 9:06 pm
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.
November 21st, 2004 at 10:35 am
Part 2
I am working on a second half to this article that will consider:
ClassLoader issues
Serialization
Enums
November 29th, 2004 at 7:13 pm
You would need to have a
FontStyle.BOLDITALIC
. Or is thatFontStyle.ITALICBOLD
? OrFontStyle.BOLD_ITALIC
? Or aFontStyle.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 embiggenFontStyle
to includeisBold()
andisItalic()
methods. This would require initializers for each style, probably bringing us back to bitfields again. However, at least they would be localized to theFontStyle
code.November 29th, 2004 at 3:17 pm
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
withapplyStyle
oraddStyle
or some such method. You may want aremoveStyle
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.November 29th, 2004 at 6:07 pm
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.
November 29th, 2004 at 6:13 pm
Pascal enumerations
Pascal had enumerated types whose values were disjoint from the integers over twenty years ago.
November 30th, 2004 at 4:46 am
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 aSet
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.
November 30th, 2004 at 11:12 am
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 similarthough.