String

介绍

Scala 中的 String 其实是 Java 中的 String,因此可以在 Scala 中使用所有 Java 中的相关 API。同时,StringOps 中定义了很多 String 相关的隐式转换,因此可以对 String 使用很多方便的操作,或者将 String 看做是一个有**字符(character)**组成的序列来操作,使其拥有序列的所有方法。

// Predef.scala
type String        = java.lang.String

// Usage
"hello".foreach(println)
for(c <- "hello") println(c)
s.getBytes.foreach(println) // 104,101,108...
"hello world".filter(_ != 'l')

StringOps

在 Scala 中,会自动引入 Predef 对象,Predef 中定义了 String 到 StringOps 的隐式转换,根据需要,String 会被 隐式转换为 StringOps 以拥有所有有序序列的方法。

final class StringOps extends AnyVal with StringLike[String] {
  override protected[this] def thisCollection: WrappedString = new WrappedString(repr)
  override protected[this] def toCollection(repr: String): WrappedString = new WrappedString(repr)
  ...
  def seq = new WrappedString(repr)
}
new StringOps(repr: String)

WrappedString

Predef 中还定义了 String 和 WrappedString:

implicit def wrapString(s: String): WrappedString = if (s ne null) new WrappedString(s) else null
implicit def unwrapString(ws: WrappedString): String = if (ws ne null) ws.self else null

WrappedString 的实现是对 String 的包装容器,将 String 作为一个参数并提供所有有序序列的操作。

class WrappedString extends AbstractSeq[Char] with IndexedSeq[Char] with StringLike[WrappedString]
new WrappedString(self: String)

StringLike

WrappedString 与 StringOps 的不同是,当执行一些类似 filter、map 的转换操作时,该类生成的是一个 WrappedString 对象而不是 String,在 StringOps 中,间接使用了 WrappedString 的封装。二者都混入了 StringLike,区别在于前者的混入方式是 StringLike[WrappedString],后者是StringLike[String]

trait StringLike[+Repr] extends IndexedSeqOptimized[Char, Repr] with Ordered[String]

在 StringLike 特质中,实现了 String 对象的相关集合操作。

检查相等性

两个 String 的相等,实际上是检查两个字符集合的相等。

"hello" == "world"
"hello" == "hello"
"hello" == null		// ok
null == "hello"		// ok

当对一个值为 null 的 String 进行相等性检查时并不会出现空指针异常,但是当使用一个 null 调用方法时则会出现空指针异常:

val test = null
test.toUpperCace == "HELLO"	// java.lang.NullPointerException

忽略大小写的相等性检查:

a.equalsIgnoreCase(b)

Scala 中检查对象相等使用的是==而不是 Java 中的equal

x == y 实际上是:if (x eq null) x eq null else x.equals(y)

因此,使用==进行相等判断时不需要检查是否为null

创建多行 String

val foo = """This is 
a multiline 
String"""
  
val speech = """Four score and 
|seven years ago""".stripMargin

切分 String

"hello world".split(" ")
// Array(hello, world)

将变量带入 String

val name = ???
val age = ???
val weight = ???
println(s"$name is $age years old, and weighs $weight pounds.")

或者在 String 中使用表达式:

println(s"Age next year: ${age + 1}")

逐个处理 String 中的每个字符

val upper = "hello, world".map(c => c.toUpper)
val upper = "hello, world".map(_.toUpper)

同时可以使用集合的方法与字符串方法相结合:

val upper = "hello, world".filter(_ != 'l').map(_.toUpper)

模式查找

如果需要使用正则表达式来对 String 中需要的部分进行匹配,首先使用.r方法创建一个Regex对象,然后使用findFirstInfindAllIn查找第一个或所有匹配的结果。

scala> val numPattern = "[0-9]+".r 
numPattern: scala.util.matching.Regex = [0-9]+

scala> val address = "123 Main Street Suite 101"

scala> val match1 = numPattern.findFirstIn(address) 
match1: Option[String] = Some(123)

scala> val matches = numPattern.findAllIn(address) 
matches: scala.util.matching.Regex.MatchIterator = non-empty iterator

scala> matches.foreach(println)
123
101

处理匹配的结果:

match1 match{
  case Some(result) => ???
  case None =>  ???
}

val match1 = numPattern.findFirstIn(address).getOrElse("no match")

或者另一种方式创建Regex对象:

import scala.util.matching.Regex
val numPattern = new Regex("[0-9]+")

对于正则表达式的使用,可以应用一个名为“JavaVerbalExpressions”的扩展库,以更 DSL 的方式构建Regex对象。

VerbalExpression.regex()

VerbalExpression.regex().startOfLine().then("http").maybe("s")
                        .then("://")
                        .maybe("www.").anythingBut(" ")
                        .endOfLine()
                        .build();

模式替换

由于 String 是不可变的,不可以在原有的 String 上进行修改,可以创建一个新的 String 包含替换后的结果。

val address = "123 Main Street".replaceAll("[0-9]", "x")

val regex = "[0-9]".r
val newAddress = regex.replaceAllIn("123 Main Street", "x")

val result = "123".replaceFirst("[0-9]", "x")

使用正则解析多个部分

如果需要将 String 的多个匹配部分解析到不同的变量,可以使用正则表达式组

val pattern = "([0-9]+) ([A-Za-z]+)".r
val pattern(count, fruit) = "100 Bananas"

count: String = 100
fruit: String = Bananas

或者同事创建多种模式以匹配不同的预期结果:

"movies near 80301" 
"movies 80301" 
"80301 movies" 
"movie: 80301" 
"movies: 80301" 
"movies near boulder, co" 
"movies near boulder, colorado"

// match "movies 80301" 
val MoviesZipRE = "movies (\\d{5})".r

// match "movies near boulder, co" 
val MoviesNearCityStateRE = "movies near ([a-z]+), ([a-z]{2})".r

textUserTyped match { 
  case MoviesZipRE(zip) => getSearchResults(zip) 
  case MoviesNearCityStateRE(city, state) => getSearchResults(city, state) 
  case _ => println("did not match a regex") 
}

访问字符串中的字符

可以以位置索引来访问 String 中的字符:

"hello".charAt(0)
"hello"(0)
"hello".apply(1)

为 String 类添加额外的方法

如果通过给现有的 String 添加额外的方法,使 String 拥有需要的方法,而不是将 String 作为一个参数传入一个需要的方法:

"HAL".increment
// 而不是
StringUtilities.increment("HAL")

可以创建一个隐式类,然后在隐式类中添加需要的方法:

implicit class StringImprovements(s: String) {
  def increment = s.map(c => (c + 1).toChar)
}

val result = "HAL".increment

但是在真实的应用中,隐式类必须在一个 class、object、package 中定义。

在 object 中定义 隐式类

object StringUtils{
  implicit class StringImprovements(val s:String){
    def increment = s.map(c => (c + 1).toChar)
  }
}

在 package 中定义 隐式类

package object utils{
  implicit class StringImporvemrnts(val s:String){
    def increment = s.map(c => (c +1).toChar)
  }
}

使用隐式转换的方式

首先定义一个类,带有一个需要的方法,然后创建一个隐式转换,将 String 转换为这个带有目的方法的对象,就可以在 String 上调用该方法了:

class StringImprovement(val s: String){
  def increment = s.map(c => (c +1).toChar)
}

implicit def stringToStringImpr(s:String) = new StringImprovement(s)