Scala: 字符串 "+" 与 "++" 的区别

13

我是Scala的新手,我看到了像这样在Scala中连接字符串的代码:

"test " ++ "1"

我已经进行了测试,而且在 Scala Doc 中也有相关说明。


"test " + "1"

我理解的是,+ 就像 Java String 中的 +,但 ++ 更强大,可以接受更多类型的参数。而且,++ 似乎也适用于其他类似 List 的东西。我想知道我的理解是否正确。还有哪些区别?在字符串连接时应该使用哪一个?


除了答案之外,我建议不再使用 +。它很可能会被弃用并最终删除。现在Scala有字符串插值,这是组合字符串的首选方式。使用 ++ 可能会更慢,但我认为它是一个很好的替代方案,语法清晰,并且与Scala的集合保持一致。 - 0__
@0__ 两小时前,+ 是来自 Java 字符串对吧?我的意思是在这种情况下,Scala 是否会将其保留为 Scala 字符串? - Junchao Gu
在Java的java.lang.StringAPI中,+未被定义,它只是一个运行字符串构建器的语法糖。在Scala中也是一样的,即使没有Predef.any2stringadd,编译器也会定义使用+进行字符串连接。参见:https://dev59.com/3HE85IYBdhLWcg3wqleE - 0__
5个回答

22

为了看到具体是怎么回事,建议查看scala.Predef

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

那么,如果Scala的String只是Java的String++方法又是怎么存在的呢?你可能会问。(至少我会问。)答案是在Predef中提供了从StringWrappedString的隐式转换,这也是wrapString方法所在的位置。

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

以下是一些例子:

如果你将String添加到List[Char]中,你会得到一个String

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

如果你将一个String添加到一个List[String]中,你会得到一个IndexedSeq[Any]。实际上,前两个元素是Char,但最后三个元素是String,如后续调用所示。

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所指出的那样,这两个工具都不是非常微妙的工具,因此除非有充分的理由,否则最好使用字符串插值StringBuilder


4

String是一个TraversableLike,这意味着它可以被分解成一个元素(字符)序列。这就是为什么可以使用++操作符,否则你不能对字符串使用++。只有当它的右侧(或该函数的参数)是可分解类型(或可遍历类型)时,++才能起作用。

那么String如何成为一个TraversableLike呢? 这就是在Predef中定义的隐式转换发挥作用的地方。其中一个隐式将普通的String转换为WrappedString,其中WrappedString.canBuildFrom具有所有的粘合剂,基本上工作方式如下:

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

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

"test " ++ "1"

现在是你的问题:

我想知道我的理解是否正确,还有其他的区别吗?

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

在什么情况下应该选择一种方法,而不是另一种方法进行字符串连接?

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

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

这里有更易读的内容。

更多细节请参考:


2
我的理解是,+就像Java中的String +,但++更强大,可以接受更多类型的参数。事实上,对于字符串而言,+更加强大,因为它可以接受任何类型的参数,就像在Java中一样。这通常被认为是一个缺陷(特别是它也适用于右侧的字符串),但我们几乎无法摆脱它。++是一个通用的集合方法,更加类型安全("test " ++ 1不能编译)。
什么时候应该使用哪个方法来进行字符串连接呢?我更喜欢使用+。然而,对于许多用法(我甚至会说是大多数用法),你想要的不是这两个方法:而是使用string interpolation
val n = 1
s"test $n"

当然,如果要从许多部分构建字符串,请使用StringBuilder

你说得对,“+”更强大,但我只是想确认在Java中是否发生了从int到string的隐式转换? - Junchao Gu
严格来说,没有隐式转换。在Java中,所发生的被称为字符串转换;在Scala中,String被认为有一个def+(other:Any)方法,因此它可以接受任何参数。仅当左侧是没有适当的+方法的非String值,右侧为String时(参见any2stringAdd),并且在这种情况下,它才会在Scala中发生隐式转换。 - Alexey Romanov

0

++ 不一定是“更强大”的,但通常用作连接/追加操作。但它不执行赋值。例如,listX ++ y将附加到listX,但i++不会增加整数i(因为这是对变量进行赋值而不是突变)。

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


0

scala.Predef中,String隐式转换为StringOps++方法在StringOps类中定义。因此,每当您执行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]

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