Scala基础(二)
单例类
一个object是一个只有一个实例的类,它在被引用的时候才创建,像一个lazy val。
作为一个顶层的值,一个object就是一个单例。
作为一个封闭类的成员或局部变量,它表现的就很像一个lazy val。
定义一个单例对象
一个object是一个值,定义一个object就像一个类,只是用object关键字
object Box
这里有一个简单的只有一个方法的object。
package logging object Logger { def info(message: String): Unit = println(s"INFO: $message") }
这个info方法可以在程序的任意地方被引入。像这样创建工具方法是单例对象的常见用例。让我们看看如何在另外的包中使用info
import logging.Logger.info class Project(name: String, daysToComplete: Int) class Test { val project1 = new Project("TPS Reports", 1) val project2 = new Project("Website redesign", 5) info("Created projects") // Prints "INFO: Created projects" }
由于这个import logging.Logger.info import语句,info方法就变得可见了。
import需要一个稳定路径(stable path)指向被引入的符号,一个object就是一个稳定路径。
注意:如果一个object不是顶层的,而是嵌入在另一个class或object中。那么这个object就是和其他任何成员一样的路径相关(path-dependent)的。这意味着给定两种饮料,class Milk和class OrangeJuice,一个类成员object NutritionInfo 依赖于封闭实例,milk或orange juice。milk.NutritionInfo和oj.NutritionInfo是完全不同的。
伴随对象(Companion object)
和类的名字一样的对象称为伴随对象(companion object)。相反的,这个类是这个对象的伴随类(companion class)。一个伴随类或伴随对象可以访问它的同伴的私有成员。为伴随类中和特定对象无关的方法和值用一个伴随对象。
import scala.math._ case class Circle(radius: Double) { import Circle._ def area: Double = { calculateArea(radius) } } object Circle { private def calculateArea(radius: Double): Double = { Pi * pow(radius, 2.0) } def main(args: Array[String]): Unit = { val circle1 = new Circle(5.0) println(circle1.area) } }
类Circle有一个对于每个实例都不一样的成员area,单例object Circle有一个对于每个实例都可用的方法calculateArea。
伴随对象也可以包含工厂方法(factory method):
package tour.singleton_objects class Email(val username: String, val domainName: String) object Email { def fromString(emailString: String): Option[Email] = { emailString.split('@') match { case Array(a, b) => Some(new Email(a, b)) case _ => None } } def main(args: Array[String]): Unit = { val scalaCenterEmail = Email.fromString("scala.center@epfl.ch") scalaCenterEmail match { case Some(email) => { println( s"""Registered an email |Username: ${email.username} |Domain name: ${email.domainName} """) } case None => { println("Error: could not parse email") } } } }
object Email包含一个工厂方法fromString,从一个字符串创建一个Email实例。考虑到解析错误,我们返回一个Option[Email]。
注意:如果一个类或对象有一个伴随,它们必须都定义在同一个文件中。为了在REPL中定义伴随,可以在同一行定义它们,或者进入:paste模式。
Java中的static成员可以作为Scala中伴随对象的普通成员的模型。
当使用Java代码中的伴随对象时,成员将会在一个伴随类中用static修饰符定义。这个称谓static forwarding。即使你没有为你自己定义一个伴随类也会发生这种情况。
正则表达式匹配
正则表达式是可以用来在数据中查找模式的字符串。任何字符串都可以通过 .r 方法转变成正则表达式。
package tour.regex import scala.util.matching.Regex object RegexExample { def main(args: Array[String]): Unit = { val numberPattern: Regex = "[0-9]"r numberPattern.findFirstMatchIn("awesomepassword") match { case Some(_) => println("Password OK") case None => println("Password must contain a number") } } }
在上面的例子中,numberPattern是一个Regex,我们用它来确保密码中包含数字。
我们也可以用小括号来搜索正则表达式的组(group)。
package tour.regex import scala.util.matching.Regex object RegexExample { def regexTest() = { val numberPattern: Regex = "[0-9]"r numberPattern.findFirstMatchIn("awesomepassword") match { case Some(_) => println("Password OK") case None => println("Password must contain a number") } } def regexGroupTest(): Unit = { val keyValuePattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r val input: String = """background-color: #A03300; |background-image: url(img/header100.png); |background-position: top center; |background-repeat: repeat-x; |background-size: 2160px 108px; |margin: 0; |height: 108px; |width: 100%;""".stripMargin for (patternMatch <- keyValuePattern.findAllMatchIn(input)) { println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") /** * key: background-color value: #A03300 * key: background-image value: url(img * key: background-position value: top center * key: background-repeat value: repeat-x * key: background-size value: 2160px 108px * key: margin value: 0 * key: height value: 108px * key: width value: 100 */ } } def main(args: Array[String]): Unit = { regexTest() regexGroupTest() } }
这里我们解析字符串中键值对,每个匹配有一组子匹配。
提取对象(Extractor Objects)
一个extractor object是一个有一个unapply方法的对象。apply方法像一个构造函数,接受参数并创建对象,但是unapply接受一个对象,然后尝试恢复参数。这个在pattern matching和部分函数(partial function)中经常用到。
package tour.class_related import scala.util.Random object CustomerID { def apply(name: String) = { println("call apply") s"$name--${Random.nextLong()}" } def unapply(customerID: String): Option[String] = { println("call unapply") val name = customerID.split("--").head if (name.nonEmpty) Some(name) else None } def main(args: Array[String]): Unit = { val customerID = CustomerID("Sukyoung") println("next call match") customerID match { case CustomerID(name) => println(name) case _ => println("Could not extract a CustomerID") } } }
上面这段程序会打印:
call apply
next call match
call unapply
Sukyoung
apply方法从一个name创建了一个CustomerID, unapply完成了相反的动作,把name找回来了。当我们调用CustomerID("Sukyoung")时,这是调用CustomerID.apply("Sukyoung")的简写语法。当我们调用case CustomerID(name) => println(name)时,我们调用了unapply方法。
unapply方法也可以被用于赋值。
val customer2ID = CustomerID("Nico") val CustomerID(name) = customer2ID println(name) // prints Nico
这等于val name = CustomerID.unapply(customer2ID).get,如果没有匹配的,会抛出scala.MatchError。
val CustomerID(name2) = "--asdfasdfasdf"
unapply的返回值应该从以下选项中选择一个:
- 如果只是一个测试,可以放回一个Boolean。例如case even()
- 如果他们返回类型的一个次值(sub-value),返回一个Option[T]
- 如果你想要放回多个次值T1,T2,...,Tn,用一个Option元组将它们组合起来Option[(T1,T2,...,Tn)]
有的时候,次值的数量不是固定的,我们想要返回一个列表(sequence)。为了这个原因,你也可以通过unapplySeq来定义模式,返回Option[Seq[T]]。这个机制在模式case List(x1,...,xn)的实例中使用。