码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-08-11 15:35:40      阅读:134      评论:0      收藏:0      [点我收藏+]

标签:

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

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

标签:

原文地址:http://www.cnblogs.com/molyeo/p/4720760.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!