Java 格式化输出:Formatter 类的全面解析
在 Java 编程中,我们经常需要将原始值和对象以文本形式呈现出来。通常,我们会使用对象的
toString
方法或相应包装类的
toString
方法将对象或值转换为字符串。这种方式简单方便,但无法精确控制字符串的格式。例如:
System.out.println("The value of Math.PI is " + Math.PI);
输出结果为:
The value of Math.PI is 3.141592653589793
这个输出虽然准确且信息丰富,但可能包含了超出读者实际需要或显示空间的细节。而使用
Formatter
类,我们可以控制转换后文本的格式,比如将值限制为特定的小数位数,或者用空格填充转换后的字符串,使其占据最小宽度。
1. Formatter 类基础
Formatter
类的主要方法是
format
方法。其最简单的形式是接受一个格式字符串,后面跟着一系列需要格式化的对象和值。为了方便使用,
PrintStream
和
PrintWriter
类提供了
printf
方法,它接受与
format
方法相同的参数,并将它们传递给关联的
Formatter
实例。下面是一个使用
System.out.printf
方法的示例:
System.out.printf("The value of Math.PI is %.3f %n", Math.PI);
输出结果为:
The value of Math.PI is 3.142
格式字符串包含普通文本和格式说明符,格式说明符告诉
Formatter
如何格式化后续的值。格式说明符以
%
字符开头,以表示要执行的转换类型的字符结尾。常见的转换类型如下:
-
f
:表示参数应为浮点值,并以普通十进制格式进行格式化。
-
e
:浮点转换,以科学记数法输出(如
3.142e+00
)。
-
d
:整数的十进制格式。
-
x
:整数的十六进制格式。
-
s
:字符串或一般对象的转换。
除了转换指示符,格式说明符还可以包含其他值来控制转换值的布局。例如,
.3
是一个精度指示符,对于浮点十进制转换,它表示结果中应显示的小数位数,并根据需要进行四舍五入。我们还可以控制输出文本的宽度,确保不同格式化元素正确对齐。此外,还可以指定标志来控制对齐方式(左对齐或右对齐)和填充字符,以保持最小宽度。
有两个特殊的转换需要注意:
-
%
转换:用于输出
%
字符。由于
%
字符标记格式说明符的开始,因此需要使用
%%
来输出实际的
%
字符。
-
%n
转换:输出平台特定的行分隔符。与
println
方法不同,
printf
和
format
方法需要我们手动指定行分隔符。
2. 格式说明符的一般形式
一般、字符或数字转换的格式说明符的通用形式为:
%[argument_index][flags][width][.precision]conversion
其中,除了
%
和转换指示符外,其他部分都是可选的。如果格式字符串中存在错误,或者转换与其他格式化请求不匹配,或者转换与提供的参数类型不匹配,则会抛出
IllegalFormatException
的某个子类异常。
参数索引是一个可选的指示符,用于指定该格式说明符应应用于哪个参数。它有两种形式:一个数字后面跟着
$
字符,表示应用于第几个参数;或者
<
字符,表示与前一个格式说明符应用相同的参数。如果没有指定参数索引,则每个未标记的格式说明符将按顺序编号,并应用于相应的参数。例如:
System.out.printf("%3$d %d %2$d %<d %d %n", 1, 2, 3);
输出结果为:
3 1 2 2 2
3. 不同类型的转换
3.1 整数转换
整数转换适用于
byte
、
short
、
int
和
long
类型的参数。不同的转换类型如下:
-
d
:十进制格式。
-
o
:八进制格式。
-
x
、
X
:十六进制格式。
宽度值指定输出的最小字符数,包括标志可能导致包含的额外字符。不允许指定精度值。适用于每种转换的标志如下表所示:
| Flag | d | o | x/X | Meaning |
| ---- | ---- | ---- | ---- | ---- |
|
-
| 可用 | 可用 | 可用 | 左对齐(否则右对齐) |
|
#
| 不可用 | 可用 | 可用 | 包含基数:八进制为
0
,十六进制为
0x
或
0X
|
|
+
| 可用 | 不可用 | 不可用 | 始终包含符号 |
|
(空格) | 可用 | 不可用 | 不可用 | 正数前包含一个前导空格 |
|
0
| 可用 | 可用 | 可用 | 使用零填充(否则使用空格) |
|
,
| 可用 | 不可用 | 不可用 | 包含分组分隔符 |
|
(
| 可用 | 不可用 | 不可用 | 用括号括住负值 |
例如,要打印一个带基数指示符且最小宽度为 10 的零填充十六进制值,可以使用:
System.out.printf("%0#10x %n", val);
当
val
等于 32 时,输出为:
0x00000020
3.2 浮点数转换
浮点数转换适用于
float
和
double
类型的参数。不同的转换类型如下:
-
e
、
E
:计算机科学记数法(如
3.142e+00
)。
-
f
、
F
:十进制格式(如
3.142
)。
-
g
、
G
:一般科学记数法。
-
a
、
A
:十六进制指数格式。
对于
NaN
或无穷大的值,将输出字符串
"NaN"
或
"Infinity"
。宽度值指定输出的最小字符数,必要时会进行填充。对于除一般科学转换外的所有转换,精度值指定小数点(或十六进制点)后要输出的位数,并根据需要进行四舍五入。如果未指定精度,则默认为 6,十六进制指数形式除外,在这种情况下,不指定精度意味着使用所需的位数。
转换为一般科学记数法时,会根据值的大小和给定的精度选择输出格式。精度表示有效数字的数量(而不是小数位数)。例如:
System.out.printf("%1$.5g %1$.4g %1$.3g %1$.2g %n", Math.PI * 100);
输出结果为:
314.16 314.2 314 3.1e+02
适用于每种转换的标志如下表所示:
| Flag | e/E | f/F | g/G | a/A | Meaning |
| ---- | ---- | ---- | ---- | ---- | ---- |
|
-
| 可用 | 可用 | 可用 | 可用 | 左对齐(否则右对齐) |
|
#
| 可用 | 可用 | 不可用 | 可用 | 始终包含(十六)小数点 |
|
+
| 可用 | 可用 | 可用 | 可用 | 始终包含符号 |
|
(空格) | 可用 | 可用 | 可用 | 可用 | 正数前包含一个前导空格 |
|
0
| 可用 | 可用 | 可用 | 可用 | 使用零填充(否则使用空格) |
|
,
| 不可用 | 可用 | 不可用 | 不可用 | 包含分组分隔符 |
|
(
| 可用 | 可用 | 可用 | 不可用 | 用括号括住负值 |
3.3 字符转换
字符转换适用于
byte
、
short
和
char
类型的参数,以及表示有效 Unicode 代码点的
int
类型参数(否则会抛出
IllegalFormatCodePointException
)。转换指示符为
c
或
C
,用于将参数格式化为 Unicode 字符。宽度值指定输出的最小字符数,必要时使用空格进行填充。不允许指定精度值,唯一可用的标志是左对齐。
3.4 一般转换
一般转换包括三种类型,适用于任何类型的参数:
-
b
和
B
:布尔转换。如果参数为
null
,则输出字符串
"false"
;如果参数为布尔值,则根据其值输出
"true"
或
"false"
;否则输出
"true"
。
-
h
和
H
:哈希码转换。如果参数为
null
,则输出字符串
"null"
;否则,将对象的
hashCode
结果传递给
Integer.toHexString
并输出结果。
-
s
和
S
:字符串转换。如果参数为
null
,则输出字符串
"null"
;否则,如果参数实现了
Formattable
接口,则调用其
formatTo
方法生成自定义输出;否则,调用参数的
toString
方法。
对于所有一般转换,宽度指定最小字符数,精度指定最大字符数。必要时会使用空格填充输出以达到最小宽度。精度优先应用,如果精度小于宽度,输出将被截断为最大尺寸。唯一可用的标志是左对齐。
4. 自定义格式化
一个类可以通过实现
Formattable
接口来支持自定义格式化。该接口定义了一个
formatTo
方法:
public void formatTo(Formatter formatter, int flags, int width, int precision)
这个方法将所需的文本追加到给定
Formatter
的目标对象中,以输出当前对象的表示形式。
flags
、
width
和
precision
参数包含了在字符串转换格式说明符中使用的标志、宽度和精度值。如果未指定宽度或精度,则传递 1。标志以
FormattableFlags
类的常量编码为位掩码,仅支持两个标志:
LEFT_JUSTIFY
表示左对齐,
ALTERNATE
表示
#
标志。每个实现
Formattable
的类可以自由定义
ALTERNATE
的含义。
通过实现
Formattable
接口,类可以在文本表示方面提供比
toString
方法更灵活的输出。例如,它可以根据提供的宽度和精度使用长形式或短形式,或者适应
Formatter
使用的区域设置。
5. 格式异常
在处理格式字符串和给定参数时,如果出现错误,将抛出
IllegalFormatException
的某个子类异常。可能遇到的异常类型如下:
-
DuplicateFormatFlagsException
:标志被多次使用。
-
FormatFlagsConversionMismatchException
:标志与转换不兼容。
-
IllegalFormatCodePointException
:传递的整数值不是有效的 Unicode 代码点。
-
IllegalFormatConversionException
:参数类型与转换不匹配。
-
IllegalFormatFlagsException
:标志组合无效。
-
IllegalFormatPrecisionException
:精度值无效,或者转换不支持精度值。
-
IllegalFormatWidthException
:宽度值无效,或者转换不支持宽度值。
-
MissingFormatArgumentException
:在转换修饰符期望的位置未提供参数。
-
MissingFormatWidthException
:需要指定宽度时未指定。
-
UnknownFormatConversionException
:格式的转换指示符未知。
-
UnknownFormatFlagsExceptions
:给定了未知的标志。
6. Formatter 类的详细信息
Formatter
对象总是与一个用于接收格式化文本的目标对象关联。默认情况下,目标对象是一个
StringBuilder
。其他可以传递给各种构造函数的目标对象包括:
- 任意
Appendable
对象:可以向其追加字符或
CharSequence
对象的对象。
- 文件:可以通过
File
对象或表示文件名的字符串指定。
-
OutputStream
。
面向字节的目标对象(文件和输出流)的构造函数可以使用平台的默认编码,也可以接受一个表示字符集编码名称的额外字符串参数。还有一个构造函数接受
PrintStream
作为目标对象,但仅使用默认的平台编码。
Formatter
类支持本地化。对于每种类型的目标对象,都有一个额外的构造函数接受
Locale
对象,用于指定
Formatter
应使用的区域设置。如果未在构造函数中指定区域设置,则使用默认区域设置。可以通过
locale
方法获取当前使用的区域设置。
可以通过
out
方法获取
Formatter
对象的目标对象,该方法返回一个
Appendable
对象。如果向目标对象写入可能抛出
IOException
,则可以通过
ioException
方法获取最后一次发生的
IOException
(如果有)。
Formatter
类实现了
Closeable
和
Flushable
接口。如果调用
close
或
flush
方法,则如果目标对象也实现了这些接口,也会对其进行关闭或刷新操作。一旦
Formatter
被关闭,除了
ioException
方法外,调用其他方法都会抛出
FormatterClosedException
。
Formatter
类的主要方法是
format
方法:
public Formatter format(String format, Object... args)
public Formatter format(Locale loc, String format, Object... args)
第一个
format
方法根据给定的格式字符串格式化参数,并将它们写入目标对象。该方法返回当前的
Formatter
对象,允许链式调用。第二个
format
方法与第一个类似,但使用指定的区域设置而不是当前
Formatter
的区域设置。
Formatter
类对本地化输出的支持仅限于部分数字转换(以及日期/时间转换)。本地化的数字转换包括整数十进制转换(
d
)和三种主要的浮点转换(
e
、
f
、
g
及其大写形式)。对于这些转换,数字字符、符号字符、小数点字符和分组字符(对于
,
标志)都从活动区域设置中获取。如果请求零填充,零字符也由区域设置定义。
7. 练习
编写一个方法,接受一个浮点值数组和一个表示列数的数字,并打印数组内容。尝试确保每列的条目整齐对齐。假设一行宽度为 80 个字符。以下是一个可能的实现思路:
import java.util.Arrays;
public class FloatArrayPrinter {
public static void printFloatArray(float[] array, int columns) {
int width = 80 / columns;
for (int i = 0; i < array.length; i++) {
System.out.printf("%-" + width + ".3f", array[i]);
if ((i + 1) % columns == 0) {
System.out.println();
}
}
}
public static void main(String[] args) {
float[] array = {1.234f, 2.345f, 3.456f, 4.567f, 5.678f, 6.789f};
printFloatArray(array, 3);
}
}
这个方法首先计算每列的宽度,然后遍历数组,使用
printf
方法格式化每个元素并打印。当打印完一列的元素后,换行继续打印下一列。
通过以上对
Formatter
类的详细介绍,我们可以看到它在 Java 编程中对于格式化输出的强大功能和灵活性。无论是简单的数字格式化,还是复杂的自定义格式化,
Formatter
类都能满足我们的需求。同时,它对本地化的支持也使得我们能够轻松处理不同区域的输出需求。在实际编程中,合理运用
Formatter
类可以让我们的输出更加清晰、规范和易于阅读。
Java 格式化输出:Formatter 类的全面解析
8. 总结与应用场景分析
8.1 总结
Formatter
类为 Java 开发者提供了强大且灵活的格式化输出功能。它允许我们精确控制原始值和对象以文本形式呈现的方式,从简单的数字格式调整到复杂的自定义格式化,都能轻松应对。通过格式说明符,我们可以指定参数索引、标志、宽度、精度和转换类型,从而实现多样化的输出效果。同时,
Formatter
类对本地化的支持,使得在不同区域环境下的输出也能符合当地的习惯。
8.2 应用场景分析
-
数据展示
:在控制台或文件中输出表格数据时,使用
Formatter类可以确保各列数据整齐对齐,提高数据的可读性。例如,在打印财务报表、统计数据等场景中,我们可以使用整数和浮点数转换来精确控制数字的显示格式。
import java.util.Formatter;
public class DataDisplay {
public static void main(String[] args) {
Formatter formatter = new Formatter(System.out);
formatter.format("%-10s %-10s %-10s %n", "Name", "Age", "Salary");
formatter.format("%-10s %-10d %-10.2f %n", "John", 25, 5000.50);
formatter.format("%-10s %-10d %-10.2f %n", "Jane", 30, 6000.75);
formatter.close();
}
}
-
日志记录
:在记录日志时,我们可能需要按照特定的格式输出信息,如日期、时间、日志级别和具体内容。
Formatter类可以帮助我们实现这种格式化输出,使日志信息更加规范和易于查看。
import java.util.Date;
import java.util.Formatter;
public class Logging {
public static void main(String[] args) {
Formatter formatter = new Formatter(System.out);
Date now = new Date();
formatter.format("%tF %tT %-10s %s %n", now, now, "INFO", "This is a log message.");
formatter.close();
}
}
-
国际化应用
:在开发多语言应用时,
Formatter类的本地化支持可以确保输出的数字、日期和时间等信息符合不同地区的语言和文化习惯。例如,在不同国家的用户界面中显示货币金额、日期等。
9. 注意事项和最佳实践
9.1 注意事项
-
异常处理
:在使用
Formatter类时,要注意处理可能抛出的IllegalFormatException及其子类异常。这些异常通常是由于格式字符串错误、参数类型不匹配等原因引起的。在编写代码时,要确保格式字符串和参数的正确性,避免出现异常。 -
资源管理
:
Formatter类实现了Closeable和Flushable接口,在使用完Formatter对象后,要及时调用close方法关闭资源,避免资源泄漏。如果在写入目标对象时可能抛出IOException,要通过ioException方法检查并处理异常。
9.2 最佳实践
- 代码可读性 :在编写格式字符串时,要尽量保持代码的可读性。可以使用注释来解释格式说明符的含义,特别是对于复杂的格式字符串。例如:
// 输出日期和时间,格式为 YYYY-MM-DD HH:MM:SS
System.out.printf("%tF %tT %n", new Date(), new Date());
- 复用格式字符串 :如果在多个地方使用相同的格式字符串,可以将其定义为常量,提高代码的可维护性。例如:
public class FormatConstants {
public static final String DATE_TIME_FORMAT = "%tF %tT";
}
public class Main {
public static void main(String[] args) {
System.out.printf(FormatConstants.DATE_TIME_FORMAT + " %n", new Date(), new Date());
}
}
-
性能优化
:在处理大量数据时,要注意
Formatter类的性能。可以考虑使用StringBuilder来构建格式化字符串,减少Formatter对象的创建和销毁次数。例如:
import java.util.Formatter;
public class PerformanceOptimization {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb);
for (int i = 0; i < 1000; i++) {
formatter.format("%d ", i);
}
System.out.println(sb.toString());
formatter.close();
}
}
10. 与其他格式化工具的比较
10.1 与
String.format
方法的比较
String.format
方法是
Formatter
类的一个便捷封装,它内部使用
Formatter
对象来实现格式化功能。两者的主要区别在于:
-
返回值
:
String.format
方法返回一个格式化后的字符串,而
Formatter
类的
format
方法返回
Formatter
对象本身,允许链式调用。
-
目标对象
:
String.format
方法只能将格式化结果存储在字符串中,而
Formatter
类可以将结果输出到多种目标对象,如
StringBuilder
、文件、
OutputStream
等。
例如:
// 使用 String.format
String result = String.format("The value of Math.PI is %.3f", Math.PI);
System.out.println(result);
// 使用 Formatter
import java.util.Formatter;
public class FormatterExample {
public static void main(String[] args) {
Formatter formatter = new Formatter(System.out);
formatter.format("The value of Math.PI is %.3f %n", Math.PI);
formatter.close();
}
}
10.2 与
SimpleDateFormat
类的比较
SimpleDateFormat
类主要用于格式化日期和时间,而
Formatter
类可以处理更广泛的类型,包括数字、字符和对象等。
SimpleDateFormat
类的使用相对简单,但功能相对单一。例如:
import java.text.SimpleDateFormat;
import java.util.Date;
// 使用 SimpleDateFormat 格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String dateStr = sdf.format(now);
System.out.println(dateStr);
// 使用 Formatter 格式化日期
import java.util.Formatter;
public class DateFormatting {
public static void main(String[] args) {
Formatter formatter = new Formatter(System.out);
formatter.format("%tF %n", new Date());
formatter.close();
}
}
11. 未来发展趋势
随着 Java 语言的不断发展,
Formatter
类可能会在以下方面得到进一步的改进和扩展:
-
更多的转换类型
:可能会增加更多的转换类型,以满足开发者对不同数据类型格式化的需求。例如,支持新的数据结构或自定义类型的格式化。
-
更好的性能优化
:在处理大量数据时,进一步优化
Formatter
类的性能,减少内存开销和处理时间。
-
增强的本地化支持
:提供更全面的本地化支持,包括更多的语言和地区,以及更细致的格式化规则。
在未来的 Java 开发中,
Formatter
类将继续发挥重要作用,为开发者提供强大而灵活的格式化输出功能。通过不断学习和掌握
Formatter
类的使用方法,我们可以更好地应对各种复杂的格式化需求,提高代码的质量和可维护性。
12. 总结与展望
Formatter
类是 Java 中一个非常实用的工具,它为我们提供了丰富的格式化输出功能。通过合理运用格式说明符和相关方法,我们可以实现多样化的输出效果,满足不同场景的需求。同时,
Formatter
类对本地化的支持也使得我们能够轻松应对国际化应用的开发。
在实际编程中,我们要注意异常处理和资源管理,遵循最佳实践,提高代码的可读性和性能。与其他格式化工具相比,
Formatter
类具有更广泛的适用性和更强的灵活性。
展望未来,随着 Java 技术的不断进步,
Formatter
类有望在功能和性能上得到进一步提升,为开发者提供更加便捷和高效的格式化解决方案。我们应该持续关注
Formatter
类的发展动态,不断学习和掌握新的特性和用法,以更好地应对日益复杂的开发需求。
以下是一个简单的 mermaid 流程图,展示了使用
Formatter
类进行格式化输出的基本流程:
graph TD;
A[创建 Formatter 对象] --> B[定义格式字符串和参数];
B --> C[调用 format 方法进行格式化];
C --> D[将格式化结果输出到目标对象];
D --> E{是否需要继续格式化};
E -- 是 --> B;
E -- 否 --> F[关闭 Formatter 对象];
这个流程图清晰地展示了使用
Formatter
类的基本步骤,从创建对象到最终输出结果,以及是否需要继续格式化的判断。通过这个流程图,我们可以更好地理解
Formatter
类的使用流程,提高开发效率。
总之,
Formatter
类是 Java 编程中不可或缺的一部分,掌握它的使用方法对于提高代码质量和开发效率具有重要意义。希望本文的介绍能够帮助你更好地理解和运用
Formatter
类,在实际开发中发挥其强大的功能。
超级会员免费看
404

被折叠的 条评论
为什么被折叠?



