Scala中更好的字符串格式化

68

当参数过多时,String.format 很容易变得混乱。有没有更强大的方法来格式化字符串呢?就像这样:

"This is #{number} string".format("number" -> 1)

或者这不可能是因为类型问题(format需要采用Map [String,Any],我想; 不知道这会让事情变得更糟)。

或者更好的方法是这样做:

val number = 1
<plain>This is { number } string</plain> text

即使它污染了命名空间?
编辑:
虽然在许多情况下简单的 pimping 可能足够,但我也正在寻找与 Python 的 format() 相同方向的东西(参见:http://docs.python.org/release/3.1.2/library/string.html#formatstrings

就我个人而言,我不同意有人需要使用过多参数的情况下才能使用“String.format”。可以使用两个或更多字符串进行连接。但是,如果有人愿意克隆Python格式以取代C风格,那当然是很好的。 - Rex Kerr
当然,应用程序并不是很多,但有时您只想将模板字符串外部化,那么优点在我看来就显而易见了。或者用于国际化的东西(尽管我不知道例如Python格式是否足够强大)。 - Debilski
1
但是 Java 不应该已经有这样的东西了吗? - Debilski
@Always-Asking(通过suggested edit):当这个问题被发布时,这些标签可能还不存在;不过添加它们确实是有意义的。 - Pokechu22
7个回答

88

在Scala 2.10中,您可以使用字符串插值

val height = 1.9d
val name = "James"
println(f"$name%s is $height%2.2f meters tall")  // James is 1.90 meters tall

46

如果你唯一的问题是使参数的顺序更加灵活,那么这很容易实现:

scala> "%d %d" format (1, 2)
res0: String = 1 2

scala> "%2$d %1$d" format (1, 2)
res1: String = 2 1

还可以通过map来进行正则表达式替换:

scala> val map = Map("number" -> 1)
map: scala.collection.immutable.Map[java.lang.String,Int] = Map((number,1))

scala> val getGroup = (_: scala.util.matching.Regex.Match) group 1
getGroup: (util.matching.Regex.Match) => String = <function1>

scala> val pf = getGroup andThen map.lift andThen (_ map (_.toString))
pf: (util.matching.Regex.Match) => Option[java.lang.String] = <function1>

scala> val pat = "#\\{([^}]*)\\}".r
pat: scala.util.matching.Regex = #\{([^}]*)\}

scala> pat replaceSomeIn ("This is #{number} string", pf)
res43: String = This is 1 string

18

这是我在这里寻找的答案:

"This is %s string".format(1)

18
你可以轻松地自行实现更丰富的格式(采用“增强我的库”方法):
scala> implicit def RichFormatter(string: String) = new {
     |   def richFormat(replacement: Map[String, Any]) =
     |     (string /: replacement) {(res, entry) => res.replaceAll("#\\{%s\\}".format(entry._1), entry._2.toString)}
     | }
RichFormatter: (string: String)java.lang.Object{def richFormat(replacement: Map[String,Any]): String}

scala> "This is #{number} string" richFormat Map("number" -> 1)
res43: String = This is 1 string

或者在最新版本的Scala上,自原回答以来:

implicit class RichFormatter(string: String) {
  def richFormat(replacement: Map[String, Any]): String =
    replacement.foldLeft(string) { (res, entry) =>
      res.replaceAll("#\\{%s\\}".format(entry._1), entry._2.toString)
    }
}

是的,我甚至可能会使用“%”(就像其他一些语言一样)。但是它仍然可以更强大一些......(例如Python的格式) - Debilski

17

看起来非常有趣(文档和插件都是)。但仍然有点疯狂(也是两者),因为有时语法不使用任何分隔符... - Debilski
我正在生产环境中使用它,非常棒!只有当要插值的表达式除了变量名之外还有很多语法时才需要分隔符。 - Alex Cruise
对于2.10+用户,一定要查看内置的字符串插值功能,网址为http://docs.scala-lang.org/overviews/core/string-interpolation.html。感谢Andrej指出这一点。 - emragins

5

如果您使用的是2.10版本,则建议使用内置插值。否则,如果您不关心极致性能,并且不害怕函数式单行代码,可以使用fold和几个正则表达式扫描:

val template = "Hello #{name}!"
val replacements = Map( "name" -> "Aldo" )
replacements.foldLeft(template)((s:String, x:(String,String)) => ( "#\\{" + x._1 + "\\}" ).r.replaceAllIn( s, x._2 ))

3

你可能还考虑使用模板引擎来处理非常复杂和长的字符串。我能想到的是Scalate,它实现了Mustache模板引擎等功能。

对于简单的字符串来说这可能有点过度,会损失性能,但你似乎已经处于需要真正的模板的领域了。


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