Java中如何格式化字符串

288

这是一个基础问题,但我应该如何使用Java格式化字符串,例如:

"第{1}步,共{2}步"

在C#中很容易实现。


3
请参见此问题 - The Scrum Meister
http://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - ℛɑƒæĿᴿᴹᴿ
12个回答

311

请查看String.format。但请注意,它使用与C的printf函数族类似的格式说明符,例如:

String.format("Hello %s, %d", "world", 42);

将返回“Hello world, 42”。当学习格式说明符时,您可能会发现这个链接有所帮助。Andy Thomas-Cramer在下面的评论中留下了这个链接,似乎指向官方规范。最常用的是:

  • %s - 插入字符串
  • %d - 插入有符号整数(十进制)
  • %f - 插入实数,标准表示法

这与使用具有可选格式说明符的位置引用的C#截然不同。这意味着您无法执行以下操作:

String.format("The {0} is repeated again: {0}", "word");

如果你只想直接输出结果,你可能会发现System.out.printf(PrintStream.printf)很适合你。


(请参见Scrum Meister的下方评论)


2
另一个选项是java.text.MessageFormat,它接受{1}样式的格式符号。String.format()的格式符号可能类似于C的printf()格式符号--但也可能不同。请参见http://download.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax获取完整的语法。 - Andy Thomas
69
String.format 可以使用数字位置:String.format("%2$s %1$s", "foo", "bar"); 将输出 bar foo - The Scrum Meister
10
从Java 15开始,可以直接在字符串上使用String.format,使用String.formatted()。例如:"Hello %s, %d".formatted("world", 42)。 - twobiers

179

除了String.format,还可以看看java.text.MessageFormat。它的格式不太简洁,但与您提供的C#示例更接近,并且您也可以用它来解析。

例如:

int someNumber = 42;
String someString = "foobar";
Object[] args = {new Long(someNumber), someString};
MessageFormat fmt = new MessageFormat("String is \"{1}\", number is {0}.");
System.out.println(fmt.format(args));

一个更好的示例利用了Java 1.5中的可变参数和自动装箱功能,将上述内容转化为一行代码:

MessageFormat.format("String is \"{1}\", number is {0}.", 42, "foobar");

MessageFormat对于使用选择修饰符进行国际化复数形式的处理要更加方便。为了指定一个正确使用单数形式当变量为1时,否则使用复数形式的消息,您可以像这样操作:

String formatString = "there were {0} {0,choice,0#objects|1#object|1<objects}";
MessageFormat fmt = new MessageFormat(formatString);
fmt.format(new Object[] { new Long(numberOfObjects) });

30
MessageFormat 用于本地化,因此在使用时需要注意。例如,以下代码 MessageFormat.format("Number {0}", 1234)); 根据默认语言环境的不同,可能会产生 Number 1,234 而不是 Number 1234 - pavel_kazlou
@ataylor:你好,抱歉我有点困惑。我想要做的是传递一个包含数据的类对象,当{0}时它将获取名字,当{1}时获取姓氏,以此类推。是否可以像这样使用{0,choice,0.getFirstName()}或类似的方式? - user3145373 ツ
@user3145373ツ 我不这么认为。 - ataylor
好的,我参考了一个类似我想要的 .Net 项目,链接为 http://www.codeproject.com/Articles/42310/Custom-Formatting-in-NET-Enhancing-String-Format-t,请参考这个项目。如果你知道有类似的项目或包可用,请告诉我。谢谢。 - user3145373 ツ
1
❗️ MessageFormatString.format 慢了数倍,除非你打算永生,否则应避免使用它。这不应该是被接受的答案。 - ccpizza
MessageFormat 可能会慢一些,但这取决于它的使用方式是否重要,而且这是对原始问题更好的答案。 - M_M

42

String#format

使用此静态方法是格式化字符串的最常见方式,从Java 5以来已经存在,并且有两个重载方法:

该方法易于使用,其format模式由底层格式化器定义。

String step1 = "one";
String step2 = "two";

// results in "Step one of two"
String string = String.format("Step %s of %s", step1, step2); 

您可以传递一个Locale来遵循语言和区域规范。有关更多信息,请参见此答案:https://dev59.com/u2w15IYBdhLWcg3wu-I9#6431949(感谢Martin Törnwall)。

MessageFormat

自Java第一版以来,MessageFormat类已经可用,并且适用于国际化。在最简单的形式中,有一个静态方法进行格式化:
String step1 = "one";
String step2 = "two";

// results in "Step one of two"
String string = MessageFormat.format("Step {0} of {1}", step1, step2);

请记住,MessageFormat 遵循与 String#format 不同的特定模式,请参阅其 JavaDoc 以获取更多详细信息:MessageFormat - patterns
可以使用 Locale,但是,由于上面的静态方法使用默认语言环境的默认构造函数,因此必须实例化该类的对象并将其传递给构造函数。有关更多信息,请参见此答案:https://dev59.com/u2w15IYBdhLWcg3wu-I9#6432100(感谢ataylor)。
非标准 JDK 解决方案
有很多使用外部库格式化字符串的方法。如果仅导入这些库来进行字符串格式化,则它们几乎没有任何好处。以下是一些示例:
- Apache Commons:StringSubstitutor,在其 JavaDoc 中提供了示例。 - Cactoos:FormattedText此处提供了示例。 - 有趣的是,Guava 不打算添加格式化或模板功能:#1142。 - ...和其他自定义实现。 欢迎添加更多,但我认为没有理由进一步扩展此部分。 Java 15 以来的替代方法
Java 15 中有一个名为 String#formatted(Object... args) 的新实例方法。
内部实现与 String#format(String format, Object... args) 相同。

使用此字符串作为格式字符串,并使用提供的参数进行格式化。

String step1 = "one";
String step2 = "two";

// results in "Step one of two"
String string = "Step %s of %s".formatted(step1, step2);     
优点:与 static 方法不同,该方法的格式化模式是一个字符串本身,基于 args 创建一个新的字符串。这允许链式组合来首先构建格式本身。 缺点:没有使用 Locale 的重载方法,因此默认使用。如果需要使用自定义的 Locale,则必须使用 String#format(Locale l, String format, Object... args)。请注意保留 HTML 标签。

1
不错!可惜我现在还在使用Java 8。我们正在讨论在未来一年左右迁移到Java 11或Java 14...所以可能还需要3-5年才能使用这个功能。 - ArtOfWarfare
我会更加乐观。LTS版本将于2021年9月成为Java 17,因此这个功能可能会变得方便。我没有注意到8和9版本之间有像模块化系统这样的结构性变化,因此我相信迁移将更快:)) - Nikolas Charalambidis

14
如果你选择不使用String.format,另一个选项是+二进制运算符。
String str = "Step " + a + " of " + b;

这等同于:

new StringBuilder("Step ").append(String.valueOf(1)).append(" of ").append(String.valueOf(2));

使用哪种方式取决于你。StringBuilder 更快,但速度差别微不足道。我更喜欢使用 + 运算符(它会执行 StringBuilder.append(String.valueOf(X)))),并且觉得它更容易阅读。


9
如果你要给我负面评分,请解释原因。 - Ryan Amos
12
1)针对字符串格式化的问题,你解释了+运算符的工作原理。 2)你的解释甚至不准确。+等同于使用StringBuilder,而不是String.concat。(此处提供的信息过多。) - Søren Løvborg

9
我已经为此编写了我的简单方法:
public class SomeCommons {
    /** Message Format like 'Some String {0} / {1}' with arguments */
    public static String msgFormat(String s, Object... args) {
        return new MessageFormat(s).format(args);
    }
}

所以您可以将它用于:

SomeCommons.msgFormat("Step {1} of {2}", 1 , "two");

6
public class StringFormat {

    public static void main(String[] args) {
            Scanner sc=new Scanner(System.in);
            System.out.println("================================");
            for(int i=0;i<3;i++){
                String s1=sc.next();
                int x=sc.nextInt();
                System.out.println(String.format("%-15s%03d",s1,x));
            }
            System.out.println("================================");

    }
}

输出结果为

================================
ved15space123
ved15space123
ved15space123
=================================

Java解决方案

  • "-"用于左对齐缩进

  • "15"将字符串最小长度设置为15

  • "s"(在%后面的一些字符)将被我们的字符串替换
  • 0在左侧填充我们的整数为0
  • 3使我们的整数最小长度为3

1
你是正确的 http://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - ℛɑƒæĿᴿᴹᴿ

4
你可以使用Java的字符串模板功能。 它在JEP 430中有详细描述,并且作为JDK 21的预览功能出现。以下是一个示例用法:
String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan");   // true

Java的字符串模板比其他语言中的功能(如C#的字符串插值和Python的f-strings)更加灵活且更安全。例如,字符串拼接或插值可能导致SQL注入攻击的发生。
String query = "SELECT * FROM Person p WHERE p.last_name = '" + name + "'";
ResultSet rs = conn.createStatement().executeQuery(query);

但是这个变种(来自JEP 430)可以防止SQL注入:

PreparedStatement ps = DB."SELECT * FROM Person p WHERE p.last_name = \{name}";
ResultSet rs = ps.executeQuery();

为什么要等这么久呢,真是让人伤心啊...上帝啊,请... :') Java 17居然还没有这个功能... - undefined
1
@BenyaminLimanto 目前还没有任何版本的Java支持这个功能。只有在Java 21的预览版中才有。要了解预览版的含义,请参阅JEP 12: 预览功能 - undefined

2
这个解决方案对我很有用。我需要动态地为REST客户端创建URL,所以我创建了这个方法,因此您只需像这样传递restURL。
/customer/{0}/user/{1}/order

并添加您需要的任意数量的参数:

public String createURL (String restURL, Object ... params) {       
    return new MessageFormat(restURL).format(params);
}

您只需要像这样调用该方法:
createURL("/customer/{0}/user/{1}/order", 123, 321);

The output

"/customer/123/user/321/order"


2

Java有java.util.Formatter,它在String格式化方法中被内部使用。它非常强大,尽管使用起来更加复杂。以下是一个指南,根据您的Java版本,您应该使用什么:

Java 15+

// Sequential string arguments
"foo %s baz %s".formatted("bar", "foobar");

// Indexed string arguments
"foo %1$s baz %2$s".formatted("bar", "foobar");

任何早于Java 15的版本:

// Sequential string arguments
String.format("foo %s baz %s", "bar", "foobar");

// Indexed string arguments
String.format("foo %1$s baz %2$s", "bar", "foobar");

早于Java 5:

MessageFormat.format("foo {0} baz {1}", "bar", "foo");

最后一个例子是为了完整性而提供的,但你不应该使用它,因为它是所有选项中最慢的。


2

Apache Commons TextStringSubstitutor提供了一种简单易读的方式来使用命名变量格式化String

import org.apache.commons.text.StringSubstitutor;
// ...
Map<String, String> values = new HashMap<>();
values.put("animal", "quick brown fox");
values.put("target", "lazy dog");
StringSubstitutor sub = new StringSubstitutor(values);
String result = sub.replace("The ${animal} jumped over the ${target}.");
// "The quick brown fox jumped over the lazy dog."

该类支持为变量提供默认值。

String result = sub.replace("The number is ${undefined.property:-42}.");
// "The number is 42."

要使用递归变量替换,请调用setEnableSubstitutionInVariables(true);

Map<String, String> values = new HashMap<>();
values.put("b", "c");
values.put("ac", "Test");
StringSubstitutor sub = new StringSubstitutor(values);
sub.setEnableSubstitutionInVariables(true);
String result = sub.replace("${a${b}}");
// "Test"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接