说在前面:本文大部分内容和代码来自《Thinking in Java》。这本书真的是一本好书,强烈推荐。

在Java SE5(很久很久以前的版本)引入了format方法,这个方法跟C的printf方法很像。首先用个简单例子说明下如何使用format方法。

public static void main(String[] args) {
	int x = 5;
	double y = 5.129867;
	//旧的方式
	System.out.println("Row 1: [" + x + " " + y + "]");
	//新的方式
	System.out.format("Row 1: [%d %f]\n", x, y);
	//或
	System.out.printf("Row 1: [%d %f]\n", x, y);
}
public static void main(String[] args) {
	int x = 5;
	double y = 5.129867;
	//旧的方式
	System.out.println("Row 1: [" + x + " " + y + "]");
	//新的方式
	System.out.format("Row 1: [%d %f]\n", x, y);
	//或
	System.out.printf("Row 1: [%d %f]\n", x, y);
}

输出结果

Row 1: [5 5.129867]
Row 1: [5 5.129867]
Row 1: [5 5.129867]
Row 1: [5 5.129867]
Row 1: [5 5.129867]
Row 1: [5 5.129867]

使用很简单,一个简单的格式化字符串,加上一串参数即可。formatprintf是等价的。我们可以看下printf方法的实现。

public PrintStream printf(String format, Object ... args) {
    return format(format, args);
}
public PrintStream printf(String format, Object ... args) {
    return format(format, args);
}

再来看下

format方法的实现


private Formatter formatter;
……
public PrintStream format(String format, Object ... args) {
    try {
        synchronized (this) {
            ensureOpen();
            if ((formatter == null)
                || (formatter.locale() != Locale.getDefault()))
                formatter = new Formatter((Appendable) this);
            formatter.format(Locale.getDefault(), format, args);
        }
    } catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    } catch (IOException x) {
        trouble = true;
    }
    return this;
}
private Formatter formatter;
……
public PrintStream format(String format, Object ... args) {
    try {
        synchronized (this) {
            ensureOpen();
            if ((formatter == null)
                || (formatter.locale() != Locale.getDefault()))
                formatter = new Formatter((Appendable) this);
            formatter.format(Locale.getDefault(), format, args);
        }
    } catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    } catch (IOException x) {
        trouble = true;
    }
    return this;
}

可以看到,format是通过Formatter类来实现内容格式化的。

在Java中,所有新的格式化功能都由java.util.Formatter类处理,可以将Formatter看作一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个Formatter 对象的时候,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出。

再看一个例子,这次我们不直接使用

System.out.format,而是使用

java.util.Formatter来实现格式化输出内容的需求:


public class Turtle {
	private String name;
	private Formatter f;
	public Turtle(String name, Formatter f){
		this.name = name;
		this.f = f;
	}
	public void move(int x, int y){
		f.format("%s The Turtle is at (%d, %d)\n", name, x, y);
	}
	public static void main(String[] args) {
		Turtle tommy = new Turtle("Tommy", new Formatter(System.out));
		tommy.move(0, 0);
		tommy.move(3, 2);
		tommy.move(7, 2);
	}
}
public class Turtle {
	private String name;
	private Formatter f;
	public Turtle(String name, Formatter f){
		this.name = name;
		this.f = f;
	}
	public void move(int x, int y){
		f.format("%s The Turtle is at (%d, %d)\n", name, x, y);
	}
	public static void main(String[] args) {
		Turtle tommy = new Turtle("Tommy", new Formatter(System.out));
		tommy.move(0, 0);
		tommy.move(3, 2);
		tommy.move(7, 2);
	}
}

输出结果:

Tommy The Turtle is at (0, 0)
Tommy The Turtle is at (3, 2)
Tommy The Turtle is at (7, 2)
Tommy The Turtle is at (0, 0)
Tommy The Turtle is at (3, 2)
Tommy The Turtle is at (7, 2)

Formatter的构造器可以接受多种输出目的地,比如上例中的PrintStream,或者OutputStreamFile等。有兴趣的可以查看Formatter的构造器。

看到这里你可能会想:在上面的例子中,只是简单的输出字符串句子,以前常用的字符串拼接也可以做到,而且并没有多麻烦呀?

那我们现在来看下Formatter的更强大的地方:控制空格与对齐

解释再多,不如来一个实例,我们看例子:

public class Receipt {
	private double total = 0;
	private Formatter f = new Formatter(System.out);
	public void printTitle(){
		f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
		f.format("%-15s %5s %10s\n", "----", "---", "-----");
	}
	public void print(String name, int qty, double price){
		f.format("%-15.15s %5d %10.2f\n", name, qty, price);
		total = total + price;
	}
	public void printTotal(){
		f.format("%-15s %5s %10.2f\n", "Tax", "", total*0.06);
		f.format("%-15s %5s %10s\n", "", "", "-----");
		f.format("%-15s %5s %10.2f\n", "Total", "", total*1.06);
	}
	public static void main(String[] args) {
		Receipt receipt = new Receipt();
		receipt.printTitle();
		receipt.print("Jack's Magic Beans", 4, 4.25);
		receipt.print("Princess Peas", 3, 5.1);
		receipt.print("Three Bears Proridge", 1, 14.29);
		receipt.printTotal();
	}
}
public class Receipt {
	private double total = 0;
	private Formatter f = new Formatter(System.out);
	public void printTitle(){
		f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
		f.format("%-15s %5s %10s\n", "----", "---", "-----");
	}
	public void print(String name, int qty, double price){
		f.format("%-15.15s %5d %10.2f\n", name, qty, price);
		total = total + price;
	}
	public void printTotal(){
		f.format("%-15s %5s %10.2f\n", "Tax", "", total*0.06);
		f.format("%-15s %5s %10s\n", "", "", "-----");
		f.format("%-15s %5s %10.2f\n", "Total", "", total*1.06);
	}
	public static void main(String[] args) {
		Receipt receipt = new Receipt();
		receipt.printTitle();
		receipt.print("Jack's Magic Beans", 4, 4.25);
		receipt.print("Princess Peas", 3, 5.1);
		receipt.print("Three Bears Proridge", 1, 14.29);
		receipt.printTotal();
	}
}

输出结果:

Item              Qty      Price
----              ---      -----
Jack's Magic Be     4       4.25
Princess Peas       3       5.10
Three Bears Pro     1      14.29
Tax                         1.42
                           -----
Total                      25.06
Item              Qty      Price
----              ---      -----
Jack's Magic Be     4       4.25
Princess Peas       3       5.10
Three Bears Pro     1      14.29
Tax                         1.42
                           -----
Total                      25.06

是不是很整齐?通过简洁的语法,Formatter提供了对空格与对齐的强大控制能力。

看完例子,我们来解释下上面的格式化字符串“%-15s %5s %10.2f\n”是啥意思。以下是其抽象的语法:

%[argument_index$][flags][width][.precision]conversion
%[argument_index$][flags][width][.precision]conversion

最常见的就是通过width来控制一个域的大小。比如“%-15s”中的15。默认情况下,数据是右对齐,不过可以通过使用“-”来改变对齐方向。precision则有点特别,首先该值前面必须带小数点。比如“%10.2f”;其次应用于不同类型的数据转换时,precision的意义也不同。应用于String时,它表示打印String时输出字符的最大数量。而在将precison应用于浮点数时,则表示小数部分要显示出来的位数(默认是6位小数),如果小数位数过多则舍入,太少则在尾部补零。应用于整数,触发异常,因为整数没有小数部分。

最后补充下常用的类型转换字符:

类型转换字符表      
--------------------
d    整数型(十进制)      
c    Unicode字符     
b    Boolean值      
s    String        
f    浮点数(十进制)      
e    浮点数(科学计数)     
x    整数(十六进制)      
h    散列码(十六进制)     
%    字符“%”
类型转换字符表      
--------------------
d    整数型(十进制)      
c    Unicode字符     
b    Boolean值      
s    String        
f    浮点数(十进制)      
e    浮点数(科学计数)     
x    整数(十六进制)      
h    散列码(十六进制)     
%    字符“%”