我是Scala的新手,我看到了像这样在Scala中连接字符串的代码:
"test " ++ "1"
我已经进行了测试,而且在 Scala Doc 中也有相关说明。
"test " + "1"
我理解的是,+
就像 Java String 中的 +
,但 ++
更强大,可以接受更多类型的参数。而且,++
似乎也适用于其他类似 List 的东西。我想知道我的理解是否正确。还有哪些区别?在字符串连接时应该使用哪一个?
为了看到具体是怎么回事,建议查看scala.Predef
。
如果你到那里查看,你会发现Scala中的String
只是java.lang.String
的别名。换句话说,String
上的+
方法被翻译成Java的+
运算符。
那么,如果Scala的String
只是Java的String
,++
方法又是怎么存在的呢?你可能会问。(至少我会问。)答案是在Predef
中提供了从String
到WrappedString
的隐式转换,这也是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
。
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"
这里有更易读的内容。
更多细节请参考:
+
就像Java中的String +,但++
更强大,可以接受更多类型的参数。事实上,对于字符串而言,+
更加强大,因为它可以接受任何类型的参数,就像在Java中一样。这通常被认为是一个缺陷(特别是它也适用于右侧的字符串),但我们几乎无法摆脱它。++
是一个通用的集合方法,更加类型安全("test " ++ 1
不能编译)。+
。然而,对于许多用法(我甚至会说是大多数用法),你想要的不是这两个方法:而是使用string interpolation。val n = 1
s"test $n"
StringBuilder
。++ 不一定是“更强大”的,但通常用作连接/追加操作。但它不执行赋值。例如,listX ++ y
将附加到listX,但i++
不会增加整数i(因为这是对变量进行赋值而不是突变)。
至少我是这样理解的。我不是Scala专家。
在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]
+
。它很可能会被弃用并最终删除。现在Scala有字符串插值,这是组合字符串的首选方式。使用++
可能会更慢,但我认为它是一个很好的替代方案,语法清晰,并且与Scala的集合保持一致。 - 0__java.lang.String
API中,+
未被定义,它只是一个运行字符串构建器的语法糖。在Scala中也是一样的,即使没有Predef.any2stringadd
,编译器也会定义使用+
进行字符串连接。参见:https://dev59.com/3HE85IYBdhLWcg3wqleE - 0__