J2SE 1.4 版原來的計劃是包括對格式化輸出的支持。可能由於時間限制及這個功能對新版本的發布不起決定作用的緣故,在發表的版本中沒有加入這些功能。現在有了 Tiger,對打印帶格式的字符串有了內在支持。
對於那些從一開始就使用 Java 編程而從沒有接觸過 C 的人,或者,對那些對 C 沒有足夠了解的人,格式化字符串是一些古怪的文本串,它們指定一組變量的輸出特性。不是用加號將字符串連接在一起(如 firstName + " " + lastName ),而是提供一個字符串描述輸出,並提供參數以在方法調用結束時,替換字符串中的占位符: String s = String.format("%1$s %2$s", firstName, lastName) 。
Formatter 類
首先,讓我們分析新的 java.util.Formatter 類。您可能不會經常直接使用這個類,但是它提供了所要進行的格式化的內部機制。在這個類的 Javadoc 中,會看到一個描述所支持的格式化選項的表。這些選項的范圍從以類似 %7.4f 這樣的格式指定浮點數的精度和位數,到格式化時間的 %tT ,到格式化第三個參數 %3$s 。
用 Formatter 格式化輸出分為兩步:創建一個 Appendable 對象以存儲輸出,用 format() 方法將帶格式的內容放到這個對象中。下面列出了 Appendable 接口的實現器:
BufferedWriter
CharArrayWriter
CharBuffer
FileWriter
FilterWriter
LogStream
OutputStreamWriter
PipedWriter
PrintStream
PrintWriter
StringBuffer
StringBuilder
StringWriter
Writer
在使用 Formatter 類時,可以將實現了這個接口的對象傳遞給構造函數 Formatter 以把它作為目標。大多數這種類看起來很相似,除了 StringBuilder 類。 StringBuilder 與 StringBuffer 類幾乎相同,只有一個大的區別:它不是線程安全的。如果知道要在單線程中構建字符串,就使用 StringBuilder 。如果構建過程會跨越多個線程,則使用 StringBuffer 。清單 1 顯示了通常如何開始使用 Formatter :
清單 1. formatter 的典型用法
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
創建了 Formatter 類後,用格式化字符串和參數調用其 format() 方法。如果需要使用與傳遞給出構造函數的不同的 Locale 作為格式化輸出的一部分,還可以向 format() 方法傳遞一個 Locale 對象。清單 2 顯示了兩種不同的 format() :
清單 2. Formatter 的 format() 方法
public Formatter format(String format,
Object... args)
public Formatter format(Locale l,
String format,
Object... args)
如果希望得到精度為 10 位數字的 Pi 值,清單 3 中的代碼會將這個值放到 StringBuilder 中並打印輸出。打印 formatter 對象將顯示 Appendable 對象的內容。
清單 3. 演示一個 Formatter
import java.util.Locale;
import java.util.Formatter;
public class Build {
public static void main(String args[]) {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("PI = %12.10f", Math.PI);
System.out.println(formatter);
}
}
不要忘記用 -source 1.5 選項編譯,否則編譯器不會識別變量參數列表。因為格式化輸出並將它發送給控制台是一項常見的任務,所以對這種操作提供了方便的方法。我們將在下面對此進行討論。
PrintStream 支持
PrintStream 類中定義了常見的、分別用於寫入標准輸出和標准錯誤的 System.out 和 System.err 對象。Tiger 引入了兩個新的構造函數(用於直接寫入文件)和六個方法以提供對格式化的支持(三對)。第一對是另一版本的 append() 方法。這一對方法實現了新的 java.lang.Appendable 接口。一般不會直接調用這些方法。直接調用的是 format() 和 printf() ,其中 printf() 版本只是 format() 版本的方便的包裝器,如清單 4 所示:
清單 4. PrintStream.format 方法
public PrintStream format(String format,
Object... args)
public PrintStream format(Locale l,
String format,
Object... args)
要記住新的變量參數支持,它是由 ... 指派的,如上面清單 4 所示。
清單 5 演示了用 PrintStream 的 format() 方法打印今天的日期:
清單 5. 使用 PrintStream.format 的例子
/
import java.util.Calendar;
public class Now {
public static void main(String args[]) {
System.out.format(
"Today is %1$tB %1$te, %1$tY.",
Calendar.getInstance()
);
}
}
運行這個程序的輸出是 Today is April 2, 2004. ,當然實際的輸出取決於運行這個程序的日期。上面代碼中的格式化字符串 %1$tB 告訴程序使用第一個參數並打印 date 對象的完整月名。格式化字符串 %1$te 意味著顯示月中的日期,而格式化字符串 %1$tY 是四位數字的年。在 Formatter 對象的 Javadoc 中列出了打印日期的其他選項。
String 支持
String 類有兩個新的 static format() 方法,它們的作用與相應的 printf() 類似。發送一個格式化字符串和參數(還可能有 Locale )、並使用在格式化字符串中指定的格式轉換參數。如果是這個方法的 String 版本,那麼是以 String 對象而不是經過流返回結果。這些方法不是很顯眼,但是有了它們就可以避免直接使用 Formatter 對象並創建中間的 StringBuilder 。
格式化任意對象
到目前為止看到的每項內容都是描述如何使用新的格式化能力對已有對象和基本數據進行格式化。如果希望用 Formatter 提供對自己的對象的支持,那麼就要用到 Formattable 接口。通過在自己的類中實現如清單 6 所示的一個 formatTo() 方法,可以對自已的類使用格式化字符串:
清單 6. Formattable 接口
void formatTo(Formatter formatter,
int flags,
Integer width,
Integer precision)
清單 7 演示了通過提供一個具有 name 屬性的簡單類來使用 Formattable 接口。這個 name 顯示在輸出中,它支持控制輸出的寬度和對齊。
清單 7. formattable 使用示例
import java.util.Locale;
import java.util.Formatter;
import java.util.Formattable;
public class MyObject implements Formattable {
String name;
public MyObject(String name) {
this.name = name;
}
public void formatTo(
Formatter fmt,
int f,
Integer width,
Integer precision) {
StringBuilder sb = new StringBuilder();
if (precision == null) {
// no max width
sb.append(name);
} else if (name.length() < precision) {
sb.append(name);
} else {
sb.append(name.substring(0, precision - 1)).append('*');
}
// apply width and justification
if ((width != null) && (sb.length() < width)) {
for (int i = 0, n=sb.length(); i < width - n; i++) {
if ((f & Formattable.LEFT_JUSTIFY) == Formattable.LEFT_JUSTIFY) {
sb.append(' ');
} else {
sb.insert(0, ' ');
}
}
}
fmt.format(sb.toString());
}
public static void main(String args[]) {
MyObject my1 = new MyObject("John");
MyObject my2 = new MyObject("Really Long Name");
// First / Using toString()
System.out.println("First Object : " + my1);
// Second / Using Formatter
System.out.format("First Object : '%s'\\n", my1);
// Second / Using Formatter
System.out.format("Second Object: '%s'\\n", my2);
// Second / Using Formatter with width
System.out.format("Second Object: '%10.5s'\\n", my2);
// Second / Using Formatter with width and left justification
System.out.format("Second Object: '%-10.5s'\\n", my2);
}
}
運行這個程序會生成如清單 8 所示的輸出。前兩行展示了使用 toString 和 Formatter 的不同結果。後三行顯示寬度和對齊控制的選項。
清單 9. formattable 輸出示例
First Object : MyObject@10b62c9
First Object : 'John'
Second Object: 'Really Long Name'
Second Object: ' Real*'
Second Object: 'Real* '
結束語
除非通過使用 C 對它們已經很熟悉了,否則掌握 Formatter 中提供的所有格式化選項要花一些時間。有一些小的區別,但是大部分的行為是非常類似的。Java 平台上的一個重要的不同是當格式化字符串無效時會拋出異常。
一定要仔細查看 Formatter 類的 Javadoc 中所列出的可用的格式化字符串。在創建自己的自定義類時,不僅提供一個 toString() 實現,而且實現 Formattable 接口通常會有幫助。
本文配套源碼