学习Scala类的定义,字段和方法

开发 后端
本文节选自Martin Odersky,Lex Spoon和Bill Venners所著,Regular翻译的《Programming in Scala》的第四章。Scala是一种针对 JVM 将函数和面向对象技术组合在一起的编程语言。

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

51CTO编辑推荐:Scala编程语言专题

  1. class ChecksumAccumulator {  
  2.  // class definition goes here  
  3. }  

你就能创建ChecksumAccumulator对象:

  1. new CheckSumAccumulator 

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

  1. class ChecksumAccumulator {  
  2.  var sum = 0 
  3. }  

并实例化两次:

  1. val acc = new ChecksumAccumulator  
  2. val csa = new ChecksumAccumulator  

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

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

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

  1. acc.sum = 3 

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

 

这张图里***件要注意的事情是这里有两个sum变量,一个在acc指向的对象里,另一个在csa指向的对象里。字段的另一种说法是实例变量:instance variable,因为每一个实例都有自己的变量集。总体来说,对象实例的变量组成了对象的内存镜像。你不仅可以因为看到两个sum变量来体会关于这个的演示,同样可以通过改变其中一个时,另一个不变来发现这点。

本例中另外一件需要注意的事情是,尽管acc是val,你仍可以改变acc指向的对象。你对acc(或csa)不能做的事情是由于它们是val,而不是var,你不可以把它们再次赋值为不同的对象。例如,下面的尝试将会失败:

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

于是你可以总结出来,acc将永远指向初始化时指向的同一个ChecksumAccumulator对象,但是包含于对象中的字段可以随时改动。
想让对象具有鲁棒性的一个重要的方法就是保证对象的状态——实例变量的值——在对象整个生命周期中持续有效。***步就是通过把字段变为私有的:private去阻止外界直接对它的访问,因为私有字段只能被定义在同一个类里的方法访问,所有能更新字段的代码将被锁定在类里。要声明字段是私有的,可以把访问修饰符private放在字段的前面,就像这样:

  1. class ChecksumAccumulator {  
  2.  private var sum = 0 
  3. }  

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

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

注意

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

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

  1. class ChecksumAccumulator {  
  2.  private var sum = 0 
  3.  def add(b: Byte): Unit =  {  
  4.   sum += b  
  5.  }  
  6.  def checksum(): Int = {  
  7.   return ~(sum & 0xFF) + 1 
  8.  }  
  9. }  

现在ChecksumAccumulator有两个方法了,add和checksum,两个都以基本的的函数定义方式展示,参见第38页的图2.1。
传递给方法的任何参数都可以在方法内部使用。Scala里方法参数的一个重要特征是它们都是val,不是var。参数是val的理由是val更容易讲清楚。你不需要多看代码以确定是否val被重新赋值,而var则不然。 如果你想在方法里面给参数重新赋值,结果是编译失败:

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

尽管在这个ChecksumAccumulator版本里的add和checksum方法正确地实现了预期的功能,你还是可以用更简洁的风格表达它们。首先,checksum方法***的return语句是多余的可以去掉。如果没有发现任何显式的返回语句,Scala方法将返回方法中***一个计算得到的值。

对于方法来说推荐的风格实际是避免显式的尤其是多个返回语句。代之以把每个方法当作是创建返回值的表达式。这种哲学将鼓励你制造很小的方法,把较大的方法分解为多个更小的方法。另一方面,设计选择取决于设计内容,Scala使得编写具有多个,显式的return的方法变得容易,如果那的确是你期望的。

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

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

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

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

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

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

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

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

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

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

【相关阅读】

  1. 学习Scala脚本:从文件里读取行记录
  2. 学习识别Scala的函数式风格
  3. Scala编程实例:使用Set和Map
  4. Scala编程实例:使用List和Tuple
  5. Scala编程实例:带类型的参数化数组

【责任编辑:王苑 TEL:(010)68476606】

责任编辑:book05 来源: Artima
相关推荐

2009-07-22 08:34:47

Scala方法和字段

2009-07-21 12:47:04

Scala私有字段定义操作符

2009-12-11 10:42:00

Scala讲座类定义构造函数

2009-07-08 15:35:18

Case类Scala

2009-09-09 11:28:40

Scala类

2009-07-21 13:54:55

Scala重载方法隐式转换

2009-07-22 09:22:20

Scala工厂对象

2009-07-21 17:21:57

Scala定义函数

2009-09-09 11:37:08

Scala的模式匹配

2009-06-16 17:54:38

Scala类语法语义

2009-07-22 07:50:00

Scala二维布局库抽象类

2009-07-22 07:53:00

Scala无参数方法

2009-07-21 11:25:03

ScalaRational类

2009-07-22 08:45:35

Scala超类构造器override修饰符

2010-11-23 15:27:00

MySQL添加字段

2011-07-06 14:12:14

Objective-C

2009-07-22 07:53:00

Scala扩展类

2009-11-16 17:04:46

Inside Scal

2009-07-21 16:58:31

Scala变量范围

2009-07-22 07:43:00

Scala闭包
点赞
收藏

51CTO技术栈公众号