84、Java 格式化输出:Formatter 类的全面解析

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 类,在实际开发中发挥其强大的功能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值