快学Scala(第二版)-06-对象

这一讲,你会学到何时使用Scala的object语法结构。在你需要某个类的单个实例时,或者想为其他值或函数找一个可以挂靠的地方时,就会用到它。
要点:

  • 用对象作为单例或存放工具方法
  • 类可以拥有一个同名的伴生对象
  • 对象可以扩展类或特质
  • 对象的apply方法通常用来构造伴生类的新实例
  • 如果不想显式定义main方法,可以用扩展App特质的对象
  • 你可以通过扩展Enumeration对象来实现枚举

6.1 单例对象

Scala没有静态方法或静态字段,你可以用object这个语法结构来达到同样的目的。对象定义了某个类的单个实例,包含了你想要的特性。例如:

object Accounts{
	private var lastNumber=0
	def newUniqueNumber()= { lastNumber +=1; lastNumber}
}

当你在应用程序中许哟啊一个新的唯一账号时,调用Account.newUniqueNumber()即可。

对象的构造器,在该对象第一次被调用时执行。如果一个对象从未被使用,那么其构造器也不会被执行。

对象本质上可以拥有类的所有特性——他甚至可以扩展其他类或特质(参见6.3节)。只有一个例外:你不能提供构造器参数

  • 作为存放工具函数或常量的地方
  • 高效地共享单个不可变实例
  • 需要用单个实例来协调某个服务时(参考单例模式)

6.2 伴生对象

在Java中,你通常既会用到实例方法又有静态方法地类。在Scala中,你可以通过类和与类同名的“伴生”(companion)对象来达到同样目的。例如:

class account{
	val id= account.newUniqueNumber()
	private var balance=0.0
	def deposit (amount:Double){
	banlance+=amount)
	...
}
object account{
	private var lastNumeber=0
	private def newUniqueNumber()={ lastNumber+=1; lastNumber}
}

注意,类的伴生对象的功能特性并不在类的作用域内。举例来说,account类必须通过account.newUniqueNumber()而不是直接用newUniqueNumber()来调用伴生对象的方法。

提示: 在REPL中,要同时定义类和对象,必须使用粘贴模式,即输入:paste

说明: 伴生对象包含与类密切相关的功能特性。在第7讲,你将看到如何使用包对象(package object) 给包添加功能特性。

6.3 扩展类或特质的对象

一个object可以扩展类以及一个或多个特质。其结果是一个扩展了指定类以及特质的类的对象,同事拥有在对象定义中给出的所有特性。
一个有用的场景是给出可被共享的默认对象。
举例来说,考虑程序中引入一个可以撤销动作的类。

abstract class undoAbleAction(val description:String){
	def undo(): Unit
	def redo(): Unit
}

默认情况下可以是“什么都不做”。当然了,对于这个行为我们只需要一个实例即可。

object DoNothing extends undoAbleAction("Do nothing"){
	override def undo() {}
	override def redo() {}
}

DoNothing对象可以被所有需要这个默认行为的地方共用。

val actions= Map("open"->DoNothing, "save"->DoNothing,.......)
//打开和保存功能尚未实现

6.4 apply方法

我们通常会定义和使用兑现过的apply方法。当遇到如下形式的表达式时,apply方法就会被调用:

object(参数1,......,参数n)

通常,这样一个apply方法返回的时伴生类的对象。

举例来说,Array对象定义了apply方法,让我们可以用下面这样的表达式来创建数组:

Array("Mary","Chris","Bob")

为什么不用构造器来创建数组?
对于嵌套表达式而言,省去new关键字会方便很多:

Array(Array(1,2), Array(3,5)) //这是一个多维数组

注意: Array(10)和new Array(10)很容易搞混。前一个表达式调用的时apply(10),交出一个单元素(整数10)的Array[Int];而第二个调用的时构造器this(10),结果时Array[Nothing],包含了10个null元素。

scala> val b=new Array[Int](10)
b: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

scala> val c =Array(10)
c: Array[Int] = Array(10)

scala> b(9)=9

scala> b
res6: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 9)

scala> c(9)=9
java.lang.ArrayIndexOutOfBoundsException: 9                  //报错,因为c的只有一个元素,那就是10
  ... 32 elided

scala> c.length
res10: Int = 1

6.5 应用程序对象

每个Scala程序都必须从一个对象的main方法开始,这个方法的类型为Array[String]=>Unit:

object Hello{
	def main(args:Array[String]){
		print("Hello, World!")
	}
}

scalac 命令执行.scala文件:

C:\Users\Desktop\scala_practice>scalac helloWorld.scala
Picked up JAVA_TOOL_OPTIONS: -Duser.home=C:\Users\45072885

C:\Users\Desktop\scala_practice>scala -Dscala.time Hello Chris
Picked up JAVA_TOOL_OPTIONS: -Duser.home=C:\Users\45072885
Hello, World!

除了每次都提供自己的main方法,你还可以扩展App特质(你可能不明白),然后讲程序代码放入构造器方法体内:

object Hello extend App{
	print("Hello, World!")
}

如果你需要命令行参数,则可以通过args属性得到:

object Hello extends App{
	if(args.length>0)
		println(f"Hello, ${args(0)}")
	else
		println("Hello, World!")
}

如果你在调用该应用程序设置了scala.time选项的话,程序退出时会显示逝去的时间。

C:\Users\Desktop\scala_practice>scalac scalatime.scala
Picked up JAVA_TOOL_OPTIONS: -Duser.home=C:\Users\45072885

C:\Users\Desktop\scala_practice>scala -Dscala.time Hello Chris
Picked up JAVA_TOOL_OPTIONS: -Duser.home=C:\Users\45072885
Hello, Chris
[total 109ms]

所有这些操作涉及一些小小的魔法。App特质扩展自另一个特质DelayedInit,编译器对该特质有特殊处理。所有带有该特质有特殊处理。所有带有该特质的类,其初始化方法都会被挪到delayedInit方法中。App特质的main方法捕获到命令行参数,调用delayInit方法,并且根据要求打印出逝去的时间。

6.6 枚举

和Java中不同,Scala并没有枚举类型。不过,标准库提供了一个Enumeration助手类,可以用于产出枚举。
定义一个扩展Enueration类的对象并以Value方法调用初始化枚举中的所有可选值。例如:

object TraficLightColor extends Enumeration {
	val Red,Yellow,Green= Value
}

在这里我们定义了三个字段:Red、Yellow和Green,然后用Value调用将它们初始化。这是如下代码的简写:

val Red= Value
val Yellow=Value
val Green=Value

每次调用Value方法都返回内部类的新实例,该内部类也叫做Value

或者你也可以向Value方法传入ID、名称,或两个参数都传

val Red= Value(0,"stop")
val Yellow=Value(10)        //名称为”Yellow“
val Green=Value("go")      //ID为11

如不指定,则ID将在前一个枚举值的基础上加一,从0开始。默认名称为字段名。

完成定义后,你就可以用TraficLightColor.Red、TraficLightColor.Yellow、TraficLightColor.Green来引用枚举值了。你可以用如下语句直接引入枚举值:

import TraficLightColor._

(关于引入类或对象的成员更多信息,参加下一讲)

记住枚举的类型时TraficLightColor.Value而不是TraficLightColor——后者是握有这些值的对象。

有人推荐增加一个类型别名:

object TraficLightColor extends Enumeration {
	type TraficLightColor
	val Red,Yellow,Green= Value
}

现在枚举的类型变成了TraficLightColor.TraficLightColor,但需要你使用import语句才有意义。例如:

import TraficLightColor._
def dowhat(color: TraficLightColor)={
	if (color==Red) "stop"
	else if (color==Yellow) "hurry up"
	else "go"
}

枚举值的ID可以通过id方法返回,名称通过toString方法返回。对TraficLightColor.values的调用将交出所有枚举的集:

for(c<-TraficLightColor.values) println(s"${c.id}: $c")

最后你可以通过枚举的ID或名称来进行查找定位,以下两段代码都将交出TraficLightColor.Red对象:

TraficLightColor(0)   //将调用Enumeration.apply
TraficLightColor.withName("Red")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值