Scala: String "+" vs "++"
String
is a TraversableLike
, which means it can be decomposed into a sequence of elements ( characters ). That is where ++
comes from, otherwise you can not do ++
on String. ++
would only work when the right hand side of it ( or the parameter to that function ), is a de-composable type ( or a traversable ).
Now how does String
become a TraversableLike
? This where the implicits defined in Predef
come into play. One of the implicit converts normal String
into a WrappedString
where WrappedString.canBuildFrom
has all the glue which basically works in this way:
WrappedString.canBuildFrom
-> StringBuilder
-> StringLike
-> IndexedSeqOptimized
-> IndexedSeqLike
-> SeqLike
-> IterableLike
-> TraversableLike
Since the implicits defined in Predef are already in scope, it is possible to write code like this:
"test " ++ "1"
Now your questions:
I want to know if my understanding is correct. and any other differences?
Yes your understanding is in the right direction.
When should one over another just for string concatenation?
For string concatenation, clearly "test " + "1"
is creating less objects, and less number of function calls. However, I would always prefer string interpolation like so:
val t1 = "test"
val t2 = "1"
val t3 = s"$t1 $t2"
which is more readable.
For more details:
- Scala source code: https://github.com/scala/scala
- http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html
It helps to take a look in scala.Predef
to see what exactly is going on.
If you check there, you see that String
in Scala is just an alias for java.lang.String
. In other words, the +
method on a String
gets translated into Java's +
operator.
So, if a Scala String
is just a Java String
, how does the ++
method even exist, you might ask. (Well, I'd ask, at least.) The answer is that there's an implicit conversion from String
to WrappedString
provided by the wrapString
method, which is also in Predef
.
Notice that ++
takes any GenTraversableOnce
instance and adds all of the elements in that instance to the original WrappedString
. (Note that the docs incorrectly state that the method returns a WrappedString[B]
. This has to be incorrect, because WrappedString
doesn't take type parameters.) What you'll get back is either a String
(if the thing you add is a Seq[Char]
) or some IndexedSeq[Any]
(if it's not).
Here are some examples:
If you add a String
to a List[Char]
, you get a String.
scala> "a" ++ List('b', 'c', 'd')
res0: String = abcd
If you add a String
to a List[String]
, you get an IndexedSeq[Any]
. In fact, the first two elements are Char
s, but the last three are String
s, as the follow-up call shows.
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)
Finally, if you add a String
to a String
with ++
, you get back a String
. The reason for this is that WrappedString
inherits from IndexedSeq[Char]
, so this is a convoluted way of adding a Seq[Char]
to a Seq[Char]
, which gives you back a Seq[Char]
.
scala> "abc" + "def"
res0: String = abcdef
As Alexey noted, neither of these is a very subtle tool, so you're probably better off using string interpolation or a StringBuilder
unless there's a good reason not to.