The Java alternative to the C printf() function is java.text.MessageFormat, a rarely used class to create parameterised strings. It is, in fact, considerably more powerful than printf, but whereas a C programmer would regularly use printf as they standard string output method, few Java programmers even know they can format text easily. I can't count how often I've seen Java classes with code specific to padding numbers or adding an s for plurals in a string.
Why? The three reasons in order of excuse are:
- Most developers don't know that a text format class exists. It was introduced in a later Sun release in 2003 and doesn't appear to be a common part of basic Java training.
- It has a complex syntax for both code and template string that are hard to remember.
- The syntax isn't very readable.
- And if a developer actually looks at the implementation, he/she will realise how expensive it is. Every time you create a new MessageFormat instance it compiles the pattern. This means that the best way to use it is with static final in the class. I dislike this as it separates the message from the code, making the code less readable.
The
com.marringtons.string.Format class is a wrapper that resolves all but the first of the problems listed above.
- We can't help here. Since the wrapper isn't part of the base Sun Java library, it is unlikely to ever be included in general training. By meeting the next 2 challenges, at least it can be used as a project standard if the principals of said project deem it so.
- MessageFormat is created with a new and a format method to produce the formatted string. This isn't very readable when used in-line. Format uses a static factory method called pattern(). MessageFormat also has a static format method, but it's very expensive to use as it recompiles the pattern every time. The patterns are hard to remember. I have created examples of each below for your reference.
- MessageFormat takes an Object array for parameters, while Format has a with() method for objects and primatives.
- The Format.pattern() factory method is the only way to create a format. It caches the MessageFormat instances so that they are only compiled once per program run.
Enough words - time for an example of formatted strings both ways. Both ways below
will produce the same result.
private static MessageFormat myMessage
= new MessageFormat(
"Result is: Integer {0}, String {1}");
...
System.out.println(
myMessage.format( new Object[]
{ new Integer( 12), "twelve" }));
// is the same as:
System.out.println(
Format.pattern(
"Result is: Integer {0}, String {1}")
.with( 12).with( "twelve"));
Format and MessageFormat Pattern Templates
The Sun documentation is correct, but not very clear. The best references are by example, so I'll provide an example from my unit tests that show each format type. The check method takes the pattern in the first parameter and the value in the second and tests then in the expected result in the 3rd.
check( "{0}", 1234, "1,234");
check( "{0,number}", 1234, "1,234");
check( "{0,number,integer}", 1234, "1,234");
check( "{0,number,percent}", 0.16, "16%");
// @see java.text.DecimalFormat
check( "{0,number,#,##0.00;(#,##0.00)}",
-1234, "(1,234.00)");
Calendar calendar = new GregorianCalendar(
2005, 2, 13, 20, 38, 55);
Date date = new Date( calendar.getTimeInMillis());
check( "{0,date}", date, "13/03/2005");
check( "{0,date, short}", date, "13/03/05");
check( "{0,date, medium}", date, "13/03/2005");
check( "{0,date, long}", date, "13 March 2005");
check( "{0,date, full}", date, "Sunday, 13 March 2005");
// @see java.text.SimpleDateFormat
check( "{0,date,yyyy.MM.dd G ''at'' HH:mm:ss z}",
date, "2005.03.13 AD at 20:38:55 EST");
check( "{0,time}", date, "20:38:55");
check( "{0,time, short}", date, "20:38");
check( "{0,time, medium}", date, "20:38:55");
check( "{0,time, long}", date, "20:38:55");
check( "{0,time, full}", date, "08:38:55 PM EST");
String choice = // @see java.text.ChoiceFormat
"{0,choice,-1#negative ({0})|0#none|1#one"
+"|1<{0,number,integer}|101#greater than 100}";
check( choice, -23, "negative (-23)");
check( choice, 0, "none");
check( choice, 1, "one");
check( choice, 11, "11");
check( choice, 111, "greater than 100");
Why Internationalisation?
You may have been wondering why the title of this article proclaimed MessageFormat as "A Step Towards Internationalisation". There are many aspects to internationalisation with respect to software development, but translation of text would have to be one of the core ones. I'll discuss all the options as the subject of another essay, but my preferred method is to develop with in-line strings as part of the code where applicable, then use the English strings as a key into a dictionary of translated text when it is needed. This will only work if you use a
Format class to separate templates from variable data. The same restriction applies to keeping messages in a database or configuration file.
In short, using
message = "I think there are "
+aNumber+" people in the group";
cannot easily be translated, while
message = Format.pattern(
"I think there are {0} people in the group")
.with( aNumber);
can.