Problem:
How to print Java object using custom format? Since Java 5 there’s Formattable interface exactly for that purpose. Here’s an example of how to use it.
Solution:
Java has very flexible formatting syntax and allows to output text in very different ways. Since Java 5 there’s a java.util.Formattable interface that allows to apply custom formatting for printing own objects, which is very cool. :-) The interface has only one method formatTo(…) that should be implemented.
In the following example we create two books and print them applying different formatting rules: justification, alternate format, locales, applying size constraints:
package com.farenda.java.util; import java.util.Formattable; import java.util.Formatter; import java.util.List; import java.util.Locale; import static java.util.Arrays.asList; import static java.util.FormattableFlags.*; public class FormattableExample { public static class Book implements Formattable { private String symbol; private String bookName; private String polishName; public Book(String symbol, String bookName, String polishName) { this.symbol = symbol; this.bookName = bookName; this.polishName = polishName; } @Override public void formatTo(Formatter fmt, int flags, int width, int precision) { StringBuilder sb = new StringBuilder(); String name = computeName(fmt, flags, precision); applyPrecision(precision, sb, name); applyFilling(flags, width, sb); fmt.format(sb.toString()); } private void applyFilling(int flags, int minWidth, StringBuilder sb) { int currentLength = sb.length(); if (currentLength < minWidth) { // prepend or append spaces depending on justification boolean leftJustified = isEnabled(flags, LEFT_JUSTIFY); for (int i = 0, toAdd = minWidth - currentLength; i < toAdd; i++) { if (leftJustified) { sb.append(' '); } else { sb.insert(0, ' '); } } } } private void applyPrecision(int precision, StringBuilder sb, String out) { if (unspecified(precision) || fitsInPrecision(precision, out)) { sb.append(out); } else { sb.append(out.substring(0, precision-1)).append('*'); } } private boolean fitsInPrecision(int precision, String out) { return out.length() < precision; } private boolean unspecified(int precision) { return precision == -1; } private String computeName(Formatter fmt, int flags, int precision) { return shouldUseSymbol(flags, precision) ? symbol : getLocalizedName(fmt); } private String getLocalizedName(Formatter fmt) { return fmt.locale().equals(Locale.forLanguageTag("pl_PL")) ? polishName : bookName; } private boolean shouldUseSymbol(int flags, int precision) { return isEnabled(flags, ALTERNATE) || (precision != -1 && precision < 10); } private boolean isEnabled(int flags, int leftJustify) { return (flags & leftJustify) == leftJustify; } @Override public String toString() { return String.format("[%s] %s", symbol, bookName); } } public static void main(String[] args) { List<Book> books = asList( new Book("GOF", "Design Patterns: Elements of Reusable Object-Oriented Software", "Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku"), new Book("Wizard Book", "Structure and Interpretation of Computer Programs", "Struktura i interpretacja programów komputerowych")); for (Book book : books) { printBook(book); } } private static void printBook(Book book) { System.out.println("Format: formatted output"); System.out.printf("\"%%s\"(toString()): '%s'%n", book.toString()); System.out.printf("\"%%s\": '%s'%n", book); // Alternate format: System.out.printf("\"%%#s\"(alternate): '%#s'%n", book); System.out.printf("\"%%-10.5s\"(left,width,precision): '%-10.5s'%n", book); System.out.printf("\"%%.13s\": '%.13s'%n", book); // With custom Locale: System.out.printf(Locale.forLanguageTag("pl_PL"), "\"%%25s\"(pl_PL): '%25s'%n", book); System.out.println(); } }
And here’s the output of the program:
Format: formatted output "%s"(toString()): '[GOF] Design Patterns: Elements of Reusable Object-Oriented Software' "%s": 'Design Patterns: Elements of Reusable Object-Oriented Software' "%#s"(alternate): 'GOF' "%-10.5s"(left,width,precision): 'GOF ' "%.13s": 'Design Patte*' "%25s"(pl_PL): 'Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku' Format: formatted output "%s"(toString()): '[Wizard Book] Structure and Interpretation of Computer Programs' "%s": 'Structure and Interpretation of Computer Programs' "%#s"(alternate): 'Wizard Book' "%-10.5s"(left,width,precision): 'Wiza* ' "%.13s": 'Structure an*' "%25s"(pl_PL): 'Struktura i interpretacja programów komputerowych'
The output is pretty clear. One thing to note is how precision and width work together. This can be seen in the second book %-10.5s format. Even though width is 10 characters, the output contains only 5 (precision size) of them from the book, the rest is padding. So the way to interpret that is: width is the size of the output and precision is the part of output designated to use.