Scala:字符串“+”与“++”

Scala: String "+" vs "++"

我是 Scala 的新手,我已经看到在 Scala 中连接字符串的代码如下:

"test " ++ "1"

而且我测试过,也是写在Scala Doc

"test " + "1"

所以我的理解是 + 就像 Java String + 但是 ++ 更强大,可以接受更多类型的参数。 ++ 似乎也适用于 List 等其他事物。我想知道我的理解是否正确。还有其他区别吗?什么时候应该一个接一个只是为了字符串连接?

So my understanding is that + is like the Java String + but ++ is more powerful, can take in more types of parameters

问题是,+ on Strings 在这个意义上更强大:它可以接受任何参数,就像 Java 一样。这通常被认为是一个错误功能(特别是因为它也适用于右侧的字符串),但我们几乎坚持使用它。正如您所说,++ 是一种通用的收集方法,并且类型更安全("test " ++ 1 无法编译)。

When should one over another just for string concatenation?

我更喜欢 +。但是,对于许多(我什至会说大多数)用法,您想要的都不是:改用 string interpolation

val n = 1
s"test $n"

当然,当从 多个 部分构建字符串时,请使用 StringBuilder.

++ 不一定是 "more powerful",但它通常用作 concatenation/appending 操作。但是,它不执行分配。 IE listX ++ y 将附加到 listX,但 i++ 不会增加整数 i(因为这是分配给变量而不是变异)。

至少这是我的理解。我不是 Scala 专家。

scala.Predef 中存在从 StringStringOps 的隐式转换。 ++ 方法在StringOps class 中定义。因此,每当您执行 str1 ++ str2 时,scala 编译器基本上(从编码器的角度来看)将 str1 包装在 StringOps 中并调用 ++ 方法 StringOps.请注意 StringOps 本质上是一种 IndexedSeq,因此 ++ 运算符非常灵活,例如

"Hello, " ++ "world!"  //results in "Hello, world" with type String
"three" ++ (1 to 3)    //results in Vector('t', 'h', 'r', 'e', 'e', 1, 2, 3) with type IndexedSeq[AnyVal]

查看 scala.Predef 以了解到底发生了什么会有所帮助。

如果你在那里查看,你会发现 Scala 中的 String 只是 java.lang.String 的别名。换句话说,String 上的 + 方法被翻译成 Java 的 + 运算符。

所以,如果 Scala String 只是一个 Java String,那么 ++ 方法是如何存在的,您可能会问。 (好吧,至少我会问。)答案是 wrapString 方法提供了从 StringWrappedString 的隐式转换,它也在 Predef.

请注意,++ 采用任何 GenTraversableOnce 实例并将该实例中的所有元素添加到原始 WrappedString。 (请注意,文档错误地指出方法 returns 是 WrappedString[B]。这一定是不正确的,因为 WrappedString 不接受类型参数。)你会得到的结果是一个 String(如果你添加的是 Seq[Char])或一些 IndexedSeq[Any](如果不是)。

这里有一些例子:

如果您将 String 添加到 List[Char],您将得到一个字符串。

scala> "a" ++ List('b', 'c', 'd')
res0: String = abcd

如果将 String 添加到 List[String],则会得到 IndexedSeq[Any]。事实上,前两个元素是 Chars,但后三个元素是 Strings,如后续调用所示。

scala> "ab" ++ List("c", "d", "e")
res0: scala.collection.immutable.IndexedSeq[Any] = Vector(a, b, c, d, e)

scala> res0 map ((x: Any) => x.getClass.getSimpleName)
res1: scala.collection.immutable.IndexedSeq[String] = Vector(Character, Character, String, String, String)

最后,如果您将 String 添加到带有 ++String,您将得到 String。这样做的原因是 WrappedString 继承自 IndexedSeq[Char],所以这是一种将 Seq[Char] 添加到 Seq[Char] 的复杂方法,它会返回 Seq[Char] ].

scala> "abc" + "def"
res0: String = abcdef

正如 Alexey 指出的那样,这些都不是非常微妙的工具,因此您最好使用 string interpolation or a StringBuilder 除非有充分的理由不这样做。

String是一个TraversableLike,表示可以分解成一个元素序列(字符)。这就是 ++ 的来源,否则你不能对 String 做 ++++ 仅当它的右侧(或该函数的参数)是可分解类型(或可遍历类型)时才有效。

现在 String 是如何变成 TraversableLike 的?这就是 Predef 中定义的隐含函数发挥作用的地方。其中一个隐式将正常 String 转换为 WrappedString,其中 WrappedString.canBuildFrom 具有基本上以这种方式工作的所有胶水:

WrappedString.canBuildFrom -> StringBuilder -> StringLike -> IndexedSeqOptimized -> IndexedSeqLike -> SeqLike -> IterableLike -> TraversableLike

由于 Predef 中定义的隐式已经在范围内,因此可以编写如下代码:

"test " ++ "1"

现在你的问题:

I want to know if my understanding is correct. and any other differences?

是的,你的理解是正确的。

When should one over another just for string concatenation?

对于字符串连接,显然 "test " + "1" 创建的对象更少,函数调用的次数也更少。但是,我总是更喜欢这样的字符串插值:

val t1 = "test"
val t2 = "1"
val t3 = s"$t1 $t2"

哪个更具可读性。

更多详情: