类是对象的蓝图。一旦你定义了类,你就可以用关键字new从类的蓝图里创建对象。比方说,如果给出了类的定义:

class ChecksumAccumulator {
// class definition goes here
}

你就能创建ChecksumAccumulator对象:

new CheckSumAccumulator

类定义里,可以放置字段和方法,这些被笼统地称为成员:member。字段,不管是用val或是用var定义的,都是指向对象的变量方法,用def定义,包含了可执行的代码。字段保留了对象的状态或者数据,而方法使用这些数据对对象做运算工作。当你实例化类的时候,执行期环境会设定一些内存来保留对象状态的镜像——也就是说,变量的内容。举例来说,如果你定义了ChecksumAccumulator类并给它一个叫做sum的var字段:

class ChecksumAccumulator { var sum = 0 }

并实例化两次:

val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator

对象在内存里的镜像看上去大概是这样的:

  

由于在类ChecksumAccumulator里面定义的字段sum是var,而不是val,你之后可以重新赋值给它不同的Int值,如:

acc.sum = 3

现在,图像看上去会变成:

这张图里第一件要注意的事情是这里有两个sum变量,一个在acc指向的对象里,另一个在csa指向的对象里。字段的另一种说法是实例变量:instance variable,因为每一个实例都有自己的变量集。总体来说,对象实例的变量组成了对象的内存镜像。你不仅可以因为看到两个sum变量来体会关于这个的演示,同样可以通过改变其中一个时,另一个不变来发现这点。本例中另外一件需要注意的事情是,尽管acc是val,你仍可以改变acc指向的对象。你对acc(或csa)不能做的事情是由于它们是val,而不是var,你不可以把它们再次赋值为不同的对象。例如,下面的尝试将会失败:

// 编译不过,因为acc是val
acc = new ChecksumAccumulator

于是你可以总结出来,acc将永远指向初始化时指向的同一个ChecksumAccumulator对象,但是包含于对象中的字段可以随时改动。

想让对象具有鲁棒性的一个重要的方法就是保证对象的状态——实例变量的值——在对象整个生命周期中持续有效。第一步就是通过把字段变为私有的:private去阻止外界直接对它的访问,因为私有字段只能被定义在同一个类里的方法访问,所有能更新字段的代码将被锁定在类里。要声明字段是私有的,可以把访问修饰符private放在字段的前面,就像这样:

class ChecksumAccumulator {
private var sum = 0
}

有了这个ChecksumAccumulator的定义,任何从类外部访问sum的尝试都会失败:

val acc = new ChecksumAccumulator
acc.sum = 5 //编译不过,因为sum是私有的

注意 在Scala里把成员公开的方法是不显式地指定任何访问修饰符。换句话说,你在Java里要写上“public”的地方,在Scala里只要什么都不要写就成。Public是Scala的缺省访问级别。

现在sum是私有的,所以唯一能访问sum的代码是定义在类自己里面的。这样,除非我们定义什么方法,否则ChecksumAccumulator对任何人都没什么用处:

class ChecksumAccumulator {
private var sum = 0 def add(b: Byte): Unit = {
sum += b
}
def checksum(): Int = {
return ~(sum & 0xFF) + 1 }
}

现在ChecksumAccumulator有两个方法了,add和checksum,两个都以基本的的函数定义方式展示。

传递给方法的任何参数都可以在方法内部使用。Scala里方法参数的一个重要特征是它们都是val,不是var如果你想在方法里面给参数重新赋值,结果是编译失败:

def add(b: Byte): Unit = {
b += 1 // 编译不过,因为b是
val sum += b
}

尽管在这个ChecksumAccumulator版本里的add和checksum方法正确地实现了预期的功能,你还是可以用更简洁的风格表达它们。首先,checksum方法最后的return语句是多余的可以去掉。如果没有发现任何显式的返回语句,Scala方法将返回方法中最后一个计算得到的值。对于方法来说推荐的风格实际是避免显式的尤其是多个返回语句。代之以把每个方法当作是创建返回值的表达式。这种哲学将鼓励你制造很小的方法,把较大的方法分解为多个更小的方法。另一方面,设计选择取决于设计内容,Scala使得编写具有多个,显式的return的方法变得容易,如果那的确是你期望的。

因为checksum要做的只有计算值,不需要return。所以这个方法的另一种简写方式是,假如某个方法仅计算单个结果表达式,则可以去掉大括号。如果结果表达式很短,甚至可以把它放在def同一行里。这样改动之后,类ChecksumAccumulator看上去像这样:

class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0xFF) + 1
}

像ChecksumAccumulator的add方法那样的结果类型为Unit的方法,执行的目的就是它的副作用。通常我们定义副作用为在方法外部某处改变状态或者执行I/O活动。比方说,在add这个例子里,副作用就是sum被重新赋值了。表达这个方法的另一种方式是去掉结果类型和等号,把方法体放在大括号里。这种形式下,方法看上去很像过程:procedure,一种仅为了副作用而执行的方法。代码4.1的add方法里演示了这种风格:

// 文件ChecksumAccumulator.scala
class ChecksumAccumulator {
private var sum = 0 def add(b: Byte) { sum += b }
def checksum(): Int = ~(sum & 0xFF) + 1
}

应该注意到令人困惑的地方是当你去掉方法体前面的等号时,它的结果类型将注定是Unit。不论方法体里面包含什么都不例外,因为Scala编译器可以把任何类型转换为Unit。例如,如果方法的最后结果是String,但方法的结果类型被声明为Unit,那么String将被转变为Unit并失去它的值。下面是这个例子:

scala> def f(): Unit = "this String gets lost"
f: ()Unit

例子里,String被转变为Unit因为Unit是函数f声明的结果类型。Scala编译器会把一个以过程风格定义的方法,就是说,带有大括号但没有等号的,在本质上当作是显式定义结果类型为Unit的方法。例如:

scala> def g() { "this String gets lost too" }
g: ()Unit

因此,如果你本想返回一个非Unit的值,却忘记了等号时,那么困惑就出现了。所以为了得到你想要的结果,你需要插入等号:

scala> def h() = { "this String gets returned!" }
h: ()java.lang.String scala>
h res0: java.lang.String = this String gets returned!

分号推断

Scala程序里,语句末尾的分号通常是可选的。如果你愿意可以输入一个,但若一行里仅有一个语句也可不写。另一方面,如果一行里写多个语句那么分号是需要的:

val s = "hello"; println(s)

如果你想输入一个跨越多行的语句,多数时候你只需输入,Scala将在正确的位置分隔语句。例如,下面的代码被认为是一个跨四行的语句:

if (x < 2)
println("too small")
else
println("ok")

然而,偶尔Scala也许没有按照你的愿望把句子分割成两部分:

x
+ y

这会被分成两个语句x和+ y。如果你希望把它作为一个语句x + y,你可以把它包裹在括号里:

(x
+ y)

或者,你也可以把+放在行末。正是由于这个原因,当你在串接类似于+的中缀操作符,把操作符放在行尾而不是行头是普遍的Scala风格

x +
y +
z

分号推断的规则

分割语句的精确规则非常有效却出人意料的简单。那就是,除非以下情况的一种成立,否则行尾被认为是一个分号:

1.疑问行由一个不能合法作为语句结尾的字结束,如句点或中缀操作符。

2.下一行开始于不能作为语句开始的字。

3.行结束于括号(...)或方框[...]内部,因为这些符号不可能容纳多个语句。

Singleton对象

Scala比Java更面向对象的一个方面是Scala没有静态成员。替代品是,Scala有单例对象:singleton object。除了用object关键字替换了class关键字以外,单例对象的定义看上去就像是类定义。代码4.2展示了一个例子:

// 文件ChecksumAccumulator.scala import scala.collection.mutable.Map
object ChecksumAccumulator {
private val cache = Map[String, Int]()
def calculate(s: String): Int = if (cache.contains(s))
cache(s)
else {
val acc = new ChecksumAccumulator
for (c <- s)
acc.add(c.toByte)
val cs = acc.checksum()
cache += (s -> cs)
cs
}
}

表中的单例对象被叫做ChecksumAccumulator,与前一个例子里的类同名。当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。

如果你是Java程序员,考虑单例对象的一种方式是把它当作是或许你在Java中写过的任何静态方法之家。可以在单例对象上用类似的语法调用方法:单例对象名,点,方法名。例如,可以如下方式调用ChecksumAccumulator单例对象的calculate方法:

ChecksumAccumulator.calculate("Every value is an object.")

然而单例对象不只是静态方法的收容站。它同样是个第一类的对象。因此你可以把单例对象的名字看作是贴在对象上的“名签”

定义单例对象不是定义类型(在Scala的抽象层次上说)。如果只是ChecksumAccumulator对象的定义,你就建不了ChecksumAccumulator类型的变量。宁愿这么说,ChecksumAccumulator类型是由单例对象的伴生类定义的。然而,单例对象扩展了超类并可以混入特质。由于每个单例对象都是超类的实例并混入了特质,你可以通过这些类型调用它的方法,用这些类型的变量指代它,并把它传递给需要这些类型的方法.

类和单例对象间的一个差别是,单例对象不带参数,而类可以。因为你不能用new关键字实例化一个单例对象,你没机会传递给它参数。每个单例对象都被作为由一个静态变量指向的虚构类:synthetic class的一个实例来实现,因此它们与Java静态类有着相同的初始化语法特别要指出的是,单例对象会在第一次被访问的时候初始化。

不与伴生类共享名称的单例对象被称为孤立对象:standalone object。由于很多种原因你会用到它,包括把相关的功能方法收集在一起,或定义一个Scala应用的入口点.

Scala程序

要执行Scala程序,你一定要提供一个有main方法(仅带一个参数,Array[String],且结果类型为Unit)的孤立单例对象名。任何拥有合适签名的main方法的单例对象都可以用来作为程序的入口点。代码4.3展示了一个例子:

// 文件Summer.scala
import ChecksumAccumulator.calculate
object Summer {
def main(args: Array[String]) {
for (arg <- args) println(arg + ": " + calculate(arg))
}
}

代码4.3单例对象的名字是Summer。它的main方法具有合适的签名,所以你可以把它用作程序。文件中的第一个语句是引用定义在前例中ChecksumAccumulator对象中的calculate

方法。这个引用语句允许你在文件之后的部分里使用方法的简化名。5
不论执行scalac还是fsc命令,都将创建Java类文件,然后你可以用scala命令,就像之前的例子里调用解释器那样运行它。不过,不是像前面每个例子里那样把包含了Scala代码的带有.scala扩展名的文件交给它解释执行,main方法体简单地打印输出每个参数和参数的校验和,用冒号分隔。要执行Summer应用程序,把代码4.3的代码放在文件Summer.scala中。因为Summer使用了ChecksumAccumulator,把ChecksumAccumulator的代码,包括代码4.1的类和代码4.2里它的伴生对象,放在文件ChecksumAccumulator.scala中。

Scala和Java之间有一点不同,Java需要你在跟着类命名的文件里放上一个公共类——如文件SpeedRacer.java里要放上类SpeedRacer——Scala里,你可以任意命名.scala文件,而不用考虑里面放了什么Scala类或代码。然而通常情况下如果不是脚本,推荐的风格是像在Java里那样按照所包含的类名来命名文件,这样程序员就可以通过查看文件名的方式更容易地找到类。这就是我们在本例中文件ChecksumAccumulator.scala和Summer.scala上使用的方式。

无论ChecksumAccumulator.scala还是Summer.scala都不是脚本,因为他们是以定义结束的。反过来说,脚本必然以一个结果表达式结束.

正确的做法是,你需要用Scala编译器真正地编译这些文件,然后执行输出的类文件。其中一种方式是使用scalac,Scala的基本编译器。输入:

$ scalac ChecksumAccumulator.scala Summer.scala

这将编译你的源文件,不过在编译完成之前或许会有一个可感知的停顿。原因是每次编译器启动时,都要花一些时间扫描jar文件内容,并在即使你提交的是新的源文件也在查看之前完成其他初始化工作。因此,Scala的发布包里还包括了一个叫做fsc(快速Scala编译器)的Scala编译器后台服务:daemon。你可以这样使用

$ fsc ChecksumAccumulator.scala Summer.scala

第一次执行fsc时,会创建一个绑定在你计算机端口上的本地服务器后台进程。然后它就会把文件列表通过端口发送给后台进程去编译,后台进程完成编译。下一次你执行fsc时,后台进程就已经在运行了,于是fsc将只是把文件列表发给后台进程,它会立刻开始编译文件。使用fsc,你只需要在第一次等待Java运行时环境的启动。如果想停止fsc后台进程,可以执行fsc -shutdown来关闭。

不论执行scalac还是fsc命令,都将创建Java类文件,然后你可以用scala命令,就像之前的例子里调用解释器那样运行它。不过,不是像前面每个例子里那样把包含了Scala代码的带有.scala扩展名的文件交给它解释执行在这里你要给它包含了正确签名的main方法的孤立对象名。因此,你可以这样运行Summer应用程序:

$ scala Summer of love

你会看到两个命令行参数的校验和被打印出来:

of: -
love: -

Application特质

Scala提供了一个特质,scala.Application,可以节省你一些手指的输入工作.代码4.4展示了一个例子:

import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends Application {
for (season <- List("fall", "winter", "spring"))
println(season +": "+ calculate(season))
}

使用这个特质的方法是,首先在你的单例对象名后面写上“extends Application” 。然后代之以main方法,你可以把想要放在main方法里的代码直接放在单例对象的大括号之间。就这么简单。之后可以像对其它程序那样编译和运行。这种方式之所以能奏效是因为特质Application声明了带有合适的签名的main方法,并由你的单例对象继承,使它可以像个Scala程序那样用。大括号之间的代码被收集进了单例对象的主构造器:primary constructor,并在类被初始化时被执行继承自Application比写个显式的main方法要短,不过它也有些缺点。首先,如果想访问命令行参数的话就不能用它,因为args数组不可访问。比如,因为Summer程序使用了命令行参数,所以它必须带有显式的main方法,如代码4.3所示。第二,因为某些JVM线程模型里的局限,如果你的程序是多线程的就需要显式的main方法。最后,某些JVM的实现没有优化被Application特质执行的对象的初始化代码。因此只有当你的程序相对简单和单线程情况下你才可以继承Application特质。

Scala 编程---类和对象的更多相关文章

  1. scala学习-类与对象

    类 / 对象 [<快学Scala>笔记] 一.类 1.Scala中的类是公有可见性的,且多个类可以包含在同一个源文件中: class Counter{ private var value ...

  2. Scala:类,对象和特征(接口)

    http://blog.csdn.net/pipisorry/article/details/52902609 Scala类和对象 类是对象的抽象,而对象是类的具体实例.类是抽象的,不占用内存,而对象 ...

  3. Python记录14:面向对象编程 类和对象

    '''现在主流的编程思想有两种,一种是面向对象,一种是面向过程面向过程编程 核心是过程二字,过程指的是解决问题的步骤,即先干什么.再干什么.最后干什么... 基于该思想编写程序就好比再设计一条流水线, ...

  4. Scala学习之路 (六)Scala的类、对象、继承、特质

    一.类 1.类的定义 scala语言中没有static成员存在,但是scala允许以某种方式去使用static成员这个就是伴生机制,所谓伴生,就是在语言层面上,把static成员和非static成员用 ...

  5. js面向(基于)对象编程—类(原型对象)与对象

    JS分三个部分: 1. ECMAScript标准--基础语法 2. DOM  Document Object Model 文档对象模型 3. BOM  Browser Object Moldel 浏览 ...

  6. 13_Python的面向对象编程-类class,对象object,实例instance

    1.面向对象概述 1.类是用来描述对象的工具,把拥有相同属性和行为的对象分为一组     2.对象是由类实例化出来的一个具体的对象         属性: 对象拥有的名词,用变量表示         ...

  7. Python10/22--面向对象编程/类与对象/init函数

    类: 语法: class关键字 类名# 类名规范 大写开头 驼峰命名法class SHOldboyStudent: # 描述该类对象的特征 school = "上海Oldboy" ...

  8. python -- 面向对象编程(类、对象)

    一.类 类是用来描述具有相同的属性和方法的对象的集合. 它定义了该集合中每个对象共同拥有的属性和方法. 类是一个独立的单位,它有一个类名,其内部包括成员变量和成员方法,分别用于描述对象的属性和行为. ...

  9. Spark记录-Scala类和对象

    本章将介绍如何在Scala编程中使用类和对象.类是对象的蓝图(或叫模板).定义一个类后,可以使用关键字new来创建一个类的对象. 通过对象可以使用定义的类的所有功能. 下面的图通过一个包含成员变量(n ...

随机推荐

  1. 使用Yii框架完整搭建网站流程入门

    下载地址: http://www.yiiframework.com/ http://www.yiichina.com/ 由美籍华人薛强研究而出, Yii 这个名字(读作易(Yee))代表 简单(eas ...

  2. laravel old

    最近做一个laravel框架下的一个网页.遇到了old 无法点击选中的问题,捉摸好久,原来,laravel下的old 是基于seesion下的. 如果想用old必须要在session有的情况下.

  3. DNS域名解析服务器

    域名解析服务器,靠它把你要访问的网址找到然后把信息送到你电脑上.DNS 是域名系统 (Domain Name System) 的缩写,它是由解析器和域名服务器组成的.域名服务器是指保存有该网络中所有主 ...

  4. BZOJ2933:POI1999地图

    Description     一个人口统计办公室要绘制一张地图.由于技术的原因只能使用少量的颜色.两个有相同或相近人口的区域在地图应用相同的颜色.例如一种颜色k,则A(k) 是相应的数,则有: 在用 ...

  5. usb中的传输模式

    别人总结的一个usb传输模式,保存一下 usb中的endpoint(端点)和传输模式 端点: 端点位于USB 外设内部,所有通信数据的来源或目的都基于这些端点,是一个可寻址的FIFO. 每个USB 外 ...

  6. Linux Apache配置多个站点同时运行

    这样一种场景:我们有一台服务器:但是想挂多个网站:那么Apache下配置虚拟主机可以满足这个需求: 比较简单的是基于主机名的配置步骤如下: 示例环境 ip:115.28.17.191 域名:baiju ...

  7. a.redhat系统如何卸载默认jdk

    Redhat系统安装之后,会默认有openjdk在安装(下图已经是卸载掉了) 这个openjdk下面的需要进行卸载,你可以通过命令进行卸载,例如首先查看JDK,然后默认直接通过rpm命令进行卸载L 卸 ...

  8. 在linux上部署web环境

    1.升级python到2.7版本(通过源码包重新安装一个2.7版本的python):wget https://www.python.org/ftp/python/2.7.9/Python-2.7.9. ...

  9. Spring的注解

    Action想使用serviceImpl时,都需要最原始的方法New一个接口,Service service = new serviceImpl();去实例化service了.都需要Action主动创 ...

  10. 获取$(this)子节点对象的方法

    获取$(this)子节点对象的方法: 1.children()方法: children() 方法返回被选元素的所有直接子元素. 该方法只会向下一级对 DOM 树进行遍历. 2.find()方法: fi ...