[Scala基础系列 06]Scala类和对象

1.类和构造函数

Scala中的类,基本概念与其他面向对象语言是一致的,不过在语法上有些不一样的地方。与Java等语言相比,Scala的类语法更简洁,使用起来也更方便。

1.1.类的基本语法

我们先来看一个简单的类定义和使用的代码。

class ScoreCalculator {
  private var total, count = 0

  def report(score: Int) {
    total += score
    count += 1
  }

  def score = if (count == 0) 0 else total / count
}

val sc = new ScoreCalculator()
sc.report(80)
sc.report(90)
sc.report(92)
sc.report(86)

println("The average score is " + sc.score)

从以上代码可见,定义类的方式,与其他语言相似,使用class关键字即可。

请注意

  1. Scala中,默认的访问修饰符是public,也就是说,没有指定访问修饰符的成员,都是public的。
  2. 一个源文件可包含多个public的类,或接口等。这与Java不一样。

1.2.构造函数

1.2.1.主构造函数

以上示例中,没有定义构造函数,但是可以使用new来创建对象,这是因为Scala的类都有默认的基本构造函数,而且该构造函数是跟类标识符出现在一起的,并没有显示的定义。 基本构造函数的参数就是类参数,类内部除属性方法之外的其他语句,组成了基本构造函数的函数体。以上示例的类没有参数,我们可以将运动员的名字放入计分类中,将类修改如下。

class ScoreCalculator(athlete: String) {
  private var total, count = 0

  println("This is in the primary constructor")

  def report(score: Int) {
    total += score
    count += 1
  }

  def score = if (count == 0) 0 else total / count 

  override def toString() = athlete + "‘s score is " + score
}

val sc = new ScoreCalculator("Yao")
println("\nJust created an object, now you can use it.\n")
sc.report(80)
sc.report(90)
println(sc)

以上class ScoreCalculator(athlete: String)相当于下面的Java代码。

class ScoreCalculator {
  private String athlete;
  public ScoreCalculator(String athlete) {
    this.athlete = athlete;
  }
}

这也从一个侧面体现了Scala的简洁。

请注意

在类定义中,所有不属于方法和字段的语句,都属于主构造函数,比如上面的println("This is in the primary constructor")。 类参数(或默认构造函数参数)默认的访问级别是对象私有,即private[this] val,若想要在类外部也能使用,只需显示注明为val或var,比如class ScoreCalculator(val athlete: String)

1.2.2.私有构造函数

在某些情况下,我们不希望外界访问构造函数,比如,仅允许通过提供的工厂方法来构造对象,这时候,我们就需要将构造函数私有化,以防止外界访问。

在Java或C#中,由于构造函数都有显示的定义,将其定义为私有的就可以了。Scala基本构造函数没有显示的定义,私有的标志放在哪里呢? 很简单,放在类参数列表之前,如下所示。

class ScoreCalculator private(val athlete: String) 

val sc = new ScoreCalculator("Yao")

1.2.3.辅助构造函数

与其他语言一样,Scala也允许类有多个构造函数,除了主构造函数外,还可以定义若干个辅助构造函数。如下例所示。

class ScoreCalculator {
  var athlete = ""
  var degreeOfDifficulty = 0.0

  def this(athlete: String) {
    this()       //Call primary constructor
    this.athlete = athlete
  }

  def this(athlete: String, degreeOfDifficulty: Double) {
    this(athlete)   //Call another auxiliary constructor
    this.degreeOfDifficulty = degreeOfDifficulty
  }

  override def toString = "Athlete: " + athlete + ", degree of difficulty: " + degreeOfDifficulty
}

val sc1 = new ScoreCalculator("Gao Min")
sc1.degreeOfDifficulty = 3.7
println(sc1)

val sc2 = new ScoreCalculator("Fu Mingxia", 3.5)
println(sc2)

你发现了以上代码与你熟悉的语言有什么不同之处吗?是的,Scala中辅助构造函数与Java或者C++等语言有明显区别。

请注意

  1. 构造函数用this标识,而不是类名。
  2. 辅助构造函数必须先调用主构造函数,或者在它之前定义的其他构造函数。这意味着,主构造函数是唯一创建对象的途径,不论你调用的是哪个构造函数。
  3. 辅助构造函数不能调用父类的构造函数,只有主构造函数可以。这一点将在继承的章节介绍到。

2.类的属性

类的属性,语法上比Java和C#更简洁一些,不过基本概念与其他面向对象语言是一致的。

2.1.属性的基本语法

在Java中,定义属性是比较麻烦的,必须遵从下面的格式。

// This is Java
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }

调用时,必须得使用getName和setName。不得不说,这是一种巨大的浪费。到C#时,稍有改进。

//This is C#
private string name;
public string Name {
  get {return name}
  set {name = value}
}

调用时,set和get都直接用Name,这比Java有进步,但是,在get和set都遵从默认行为时,还是有浪费。而且,实践表明,大部分情况下,属性都没有特别的处理。 这种情况下,Scala就简洁多了。

var name = ""

这就是一个合法的属性,编译器会自动生成一个private字段和getter,setter方法。

val name = ""

这是一个只读的属性,编译器会自动生成getter方法,但是不会生成setter方法。

请注意
Scala中没有只写(write-only)的属性。要实现类似的功能,需要在自定义的getter中加入特殊的处理,比如抛出异常。

2.2.自定义getter和setter

如果你想自己写特殊的getter或setter方法,就像Java或C#里那样,Scala也提供了这样的途径,但是语法更紧凑。

private var _name = ""
def name = { _name.toUpperCase }
def name_= (newName: String) {_name = newName.trim()}

以上代码,在getter和setter中实现了一些特殊的处理,get到的是全大写的姓名,set的时候,会去掉前后空格。

请注意

  1. Scala的getter方法,语法有些特别,格式如name_= (parameters),名称,下划线和等号是一个整体,之间不能有空格。
  2. setter方法必须与getter成对出现,也就是不能只写不读。相反,getter可以单独出现,也就是说只读是可能的。

一个实际的例子如下:

class Address {
  val country = "China" //readonly

  private var _city = ""
  def city = _city  //readonly, since no setter defined

  var address = ""  //will have both getter and setter

  //Self-defined getter and setter
  private var _zipCode = ""
  def zipCode = _zipCode
  def zipCode_= (v: String) {
    if (v.length == 6) {
      _zipCode = v
      _city = if(v(0) == ‘1‘) "Beijing" else "Shanghai"
    }
    else {
      _zipCode = "000000"
      _city = "Other"
    }
  }
}

val addr = new Address
addr.zipCode = "100128"
addr.address = "Suite 101"
println(addr.address)
println(addr.city)

3.单例对象

单例对象是Scala中特有的概念,用来消除像其他面向对象语言那样对静态(static)的需求。 静态,Java和C#等语言中,一直被视为不纯粹面向对象的标志之一,因而,消除静态,算是Scala更面向对象的一个标志。

单例对象可分为两种,不与某个类共享源文件和名称的,称为独立对象(Standalone Object),与此相反,与某个类共享名称的,称为伴生对象(Companion Object)。

3.1.独立对象

独立对象类似于静态类,在一个运行环境中,只会有唯一一个这个类型的对象,它是单例设计模式的天然实现。其定义方式与类相似,只是将关键字换成object。 其他的方面也跟类相似,比如可以继承其他类和特质。只是有一个区别,不能有类参数,也就是构造函数参数。这一点也容易理解,因为你不能显示的实例化他们。

单例对象在第一次被使用的时候,由运行环境将它实例化,如果一直没有被用到,那么就一直不会被实例化。也就是说,单例对象最多只会实例化一次。

import scala.collection.mutable.ListBuffer

object Logger {
  private val list = new ListBuffer[String]()
  println("list is created")

  def log(msg: String) {
    list += msg
  }

  def flush() {
    list.foreach(println)
  }
}

println("Process started.")
Logger.log("The first call") //This is when Logger got created
Logger.log("The second call")
Logger.log("The third call")
println("Let‘s print out what the logger gets")
Logger.flush()

3.2.伴生对象

如果一个单例对象跟类有相同的名字,而且它们在同一个源文件里,那么就称之为这个类的伴生对象。 在Java或C#中,经常会有一个类,既有静态成员,又有实例成员,伴生对象就是用来放置静态成员的地方。

伴生对象的特别之处是,它和伴生类能互相访问对方的私有成员。这使得它能完全实现静态成员的功能,而不会带来额外的风险。

object Connection {
  private val connString = "localhost"

  //Better do lazy init in real project
  private val items = Array(new Connection("C1"),new Connection("C2"))

  def get(): Connection = {
    for (c <- items) {
      if (c.isFree) {   //accessed private member isFree
        println("Dispatch " + c.name)
        return c
      }
    }

    //Should handle this properly, not this rude way
    println("Force release and dispatch C1")
    items(0).release()
    items(0)
  }
}

class Connection private(val name: String)  {
  private var isFree = true

  def connect() {
    //accessed private member connString
    println(name + " connected to " + Connection.connString)
    isFree = false
  }

  def release() {
    isFree = true
  }
}

val conn1 = Connection.get()
conn1.connect()

val conn2 = Connection.get()
conn2.connect()

val conn3 = Connection.get()
conn3.connect()

请注意 虽然伴生对象和类之间可以互相访问对方的私有成员,但是他们并不处于同一范围,因此访问时需要提供访问对象,比如

  • 在伴生对象里访问伴生类对象,需要使用c.isFree,而不是isFree。这一点很容易理解。
  • 在伴生类中访问伴生对象的私有成员,需要写明对象,如Connection.connString,而不能直接使用connString

3.3.apply方法

apply方法是对象的一类特有方法,一般可用于创建伴生类。apply方法可以用简洁的方式调用,形如Object(args..), 当然,你也可以跟其他方法一样调用,Object.apply(args...),这两种写法的结果是一样的。

现在,当你看到List(1,2,3)这样的语句就不会感到奇怪了,这只是List.apply(1,2,3)的简写而已。

应用程序

[Scala Level: A1]

要运行一个Scala程序,与Java一样,需要一个程序入口,就是main方法。也与Java的main方法类似,Scala的main方法可接受一系列字符串作为参数。 比Java方便的是,你可以通过继承App特质(Trait)来省去自己定义main方法的麻烦。

应用程序规范

要使一个Scala程序可运行,需要一个符合如下格式的main方法作为应用程序入口。

def main(args: Array[String])

你知道,在Java中,main方法得是static的,Scala中也需要类似的实现,那就是包含main方法的,要求是独立对象(Standalone Object)。

以下示例就是一个合法的可运行的应用程序了。

object MyApplication {
  def main(args: Array[String]) {
    args.foreach(println)
  }
}

App特质

App特质(在2.9以前是Application特质)可以帮我们省去定义main方法的麻烦。以上MyApplication可改写为:

object MyApplication extends App {
  args.foreach(println)
}

在整个对象的内部,独立的语句都会被放入main方法中而执行。当然,如果你想要自己写main方法,也是可以的,不过要注意,main方法之外的字段,不会被初始化。 我们看下面的例子。

object AppWithMain extends App {
  //Fields will not be initiated before the main method
  val plainVal = "plain val will not be initiated here"
  lazy val lazyVal = "lazy val will be initiated the first time accessed"
  final val finalVal = "final val will be initiated"
  def method = "Of cause, method will be evaluated every time being called"

  override def main(args: Array[String]) {
    println(plainVal) //will be null
    println(lazyVal)
    println(finalVal)
    println(method)
  }
}

以上plainVal不会被初始化,因此会打印出null。如果有需要,可以使用lazy或者final字段,或定义为方法。

请注意
虽然可以重载main方法,但是,非必要情况下,不建议这么做。直接将语句写在函数体内,即简洁又不容易出错。

参考文献:

http://meetfp.com/zh/scala-basic/application

时间: 2024-10-08 09:09:30

[Scala基础系列 06]Scala类和对象的相关文章

[Scala基础系列 05]Scala控制结构

1.If语句 Scala的If语句可以完成其他语言中If语句,于此同时,if/else通常还有值,可以用来赋值,或者代替三元条件运算符(?:).不过它可以比条件运算符更强大,因为你可以在if-else里面写很复杂的程序块. 1.1.普通的If语句 package com.tv189.foundation /** * Created by molyeo on 2015/7/30. */ object IfCondition { def main(args: Array[String]) { val

[Scala基础系列 04]Scala基本类型

1.Scala的数值类型 Scala的数值类型与Java类似,他们的范围与Java也是一致的.与Java不同的是,他们都是对象,是相应的数值类的实例.Scala通过富包装(Rich Wrapper)类给这些数值类型提供了强大的支持. 1.1.数值类型 Scala的数值类型和取值范围,见下表. Boolean: true 或者 false Byte: 8位, 有符号(2-7 ~ 27 - 1) Short: 16位, 有符号 (2-15 ~ 215 - 1) Int: 32位, 有符号 (2-31

[Scala基础系列 08]Scala继承、抽象类、接口trait以及AOP实现

1.继承 和java一样,scala采用extends关键字继承基类.代码实例如下: /** * Created by molyeo on 2015/8/11. */ class Person(val name: String, var age: Int) { println("The primary constructor of Person") val school = "BJU" def sleep = "8 hours" override

[Scala基础系列 10]Scala泛型、类型边界

1.泛型和类型边界 1.1.上限(upper bounds) 我们先看一个简单的实例,用于判断两个变量中较大的值,其中两个变量的类型均为Int型 package com.tv189.advanced /** * Created by molyeo on 2015/8/12. */ class PairInt(val first: Int, val second: Int) { def bigger = { if (first.compareTo(second) > 0) first else s

[Scala基础系列 03]Scala操作符

1.Scala操作符简介 首先,请记住,Scala没有操作符!也没有通常意义上的表达式.你所见到的类似操作符和表达式的语句,其实是方法(函数),它们只是方法的一种比较直观的写法,可以叫做操作符记法. 1.1.二元操作符(中缀表达式) 二元操作符是最常见的操作符,比如,一个简单的表达式1 + 2.其实,“+”是定义在Int类的一个方法,你完全可以用普通方法调用的写法1.+(2).相应的,其他的方法,比如"Hello".drop(2),也可以用操作符记法,"Hello"

[Scala基础系列 07]Scala集合

Scala有一个非常通用,丰富,强大,可组合的集合库:集合是高阶的(high level)并暴露了一大套操作方法.很多集合的处理和转换可以被表达的简洁又可读,但不审慎地用它们的功能也会导致相反的结果.每个Scala程序员应该阅读 集合设计文档:通过它可以很好地洞察集合库,并了解设计动机. 1.数组(Array&ArrayBuffer) 1.1.Array 数组(Array)其实并不在scala.collection包里面,它属于scala包,直接对应于Java的数组,比如,Scala中的Arra

[Scala基础系列 02]Scala函数

本文主要内容如下: 变量和不变量 函数和过程 函数的参数 分号 1.变量和不变量 1.1.变量 Scala的变量分两种,var和val.var,即variable,类似于我们在Java等其他语言中接触到的变量,而val,是value,类似于我们在其他语言中用到的不可重新赋值的常量,或者final变量. 为什么会有这种区别,这是由于很多情况下,其实你不需要一个可变的var,尤其在函数式编程中,更为明显.不变性能给程序带来很多便利,因此Scala非常强调不可变(immutable)的概念.关于不可变

Scala 编程(二)类和对象

类,字段和方法 类是对象的蓝图.一旦定义了类,就可以用关键字new从类的蓝图里创建对象,类的定义: class ChecksumAccumulator { // class definition goes here } 就能创建对象: scala> new ChecksumAccumulator res0: ChecksumAccumulator = [email protected] 类定义里,可以放置字段和方法,这些被笼统地称为成员:member.字段,不管是用 val 或是用 var 定义

scala编程(四)——类和对象

 类,字段和方法 在scala里定义一个典型的类,代码如下: class ChecksumAccumulator { private var sum = 0 def add(b: Byte): Unit = { sum += b } def checksum(): Int = { return ~(sum & 0xFF) + 1 } } 1.在 Scala 里成员公开的方法是不显式地指定任何访问修饰符.换句话说,你在 Java 里要写上 “public”的地方,在 Scala 里只要什么都不要写