scala-basic 基础篇

@author 鲁伟林
Scala基础知识介绍,主要包括《Scala编程实战》的基础章节

GitHub地址: https://github.com/thinkingfioa/scala-in-action
本人博客地址: http://blog.csdn.net/thinking_fioa/article/details/78265745

4. 类和属性

与Java相比,Scala在类的声明、构造和字段的访问控制存在很大的差异。Java更啰嗦,Scala更简洁。第4章项目源码阅读

4.1 创建一个主构造函数

Scala构造函数分为:主构造函数 + 辅助构造函数。一个主构造函数是以下的组合:

  1. 构造函数参数
  2. 在类内部被调的方法
  3. 类内部执行的语句和表达式
class Person(var firstName : String, var lastName : String) {
  println("the constructor begins")

  // some class field
  private val HOME = System.getProperty("user.name")
  var age = 0

  override def toString: String = s"$firstName $lastName is $age years old"

  def printHome(): Unit = {
    println(s"HOME = $HOME")
  }

  printHome()
}

解释:

  1. Scala主构造函数相当模糊,Person类的整个都是主构造函数部分
  2. Person类的构造函数中两个参数firstName、lastName被定义为var字段,scala会默认为其添加get/set方法。而属性HOME则没有,private val相当于private final,不可以被其他对象直接访问
  3. Scala中描述符: private val,相当于Java中的private final
  4. Scala中默认添加的get/set方法,比如上诉代码中age属性,会添加方法名叫age() ----- getter方法,age_$eq(int age) ----- setter方法

4.2 控制构造函数字段的可见性(var/val/private)

Scala中构造函数参数可见性,使用var/val/private关键字修饰所控制。可通过下列口诀记住:

  1. 如果一个字段被声明为var,Scala会为该字段生成getter和setter方法
  2. 如果字段是val,Scala只会生成getter方法
  3. 如果一个字段没有var或val修饰符,Scala比较保守,不会生成getter方法和setter方法
  4. 字段有使用var或val修饰符,且同时使用private关键字修饰,也不会生成getter方法和setter方法

4.2.1 Case类

case类中构造函数参数的生成方式与其他的类略有不同。Case类的构造函数参数默认是val,生成getter方法,其他类默认不会生成getter方法和setter方法

代码:

class OtherPerson(name :String) {

}

case class CasePerson(name : String) {

}

object Class4P2 {
  val otherPerson : OtherPerson = new OtherPerson("fioa")
  val casePerson : CasePerson = CasePerson("ppp")

//  println(s"otherPerson ${otherPerson.name}") 报错
  println(s"casePerson ${casePerson.name}")
}

4.3 定义辅助构造函数

定义多个辅助构造函数,方便用户通过不同的方式创建对象实例。在类内部以this为名的方法定义辅助构造函数。通过不同的参数列表,定义多个辅助构造函数。提醒:每个构造函数必须调用之前已经定义定义好的构造函数,也就是说,任何辅助构造函数都会调用主构造函数

  1. 辅助构造函数必须使用this命名创建
  2. 每个辅助构造函数必须调用之前定义的构造函数开始
  3. 每个构造函数必须有不同的签名(参数列表)

代码:

class Pizza(var crustSize : Int, var crustType : String) {

  def this(crustSize: Int) {
    this(crustSize, Pizza.DEFAULT_CRUST_TYPE)
  }

  override def toString: String = s"Thinking eat Pizza $crustSize, $crustType"
}

object Pizza {
  val DEFAULT_CRUST_SIZE = 12
  val DEFAULT_CRUST_TYPE = "THIN"
}

4.3.1 为case类生成辅助构造函数

Case类是一个会自动生成很多模版代码的特殊类。使用类的伴生对象中的apply方法,来为Class类添加一个辅助构造函数

代码

case class Person4P2(var name : String, var age : Int)

object Person4P2 {
  def apply(name : String) = new Person4P2(name, 0)
}

object Class4P3 {
  var p : Person4P2 = Person4P2("thinking")
}

4.4 定义私有的主构造函数 ----- 单例模式需要

为了使用单例,需要建立一个私有主构造函数,使用private关键字。Scala中使用单例,需要用到伴生对象

代码

class Brain private (var name : String, var speed : Long) {

  override def toString: String = s"$name have $speed ms"

  def printBrain() : Unit = {
    print(toString())
  }
}

object Brain {
  val brain = new Brain("thinking", 1)
  def getIntance: Brain = brain
}

object Class4P4 {
  def main(args: Array[String]): Unit = {
    Brain.getIntance.printBrain()
  }
}

伴生对象 ----- 讨论

简单来说,一个伴生对象就是定义在与类的同一个文件中,同时对象和类有相同的名字。如上object Brain是类Brain的伴生对象。伴生对象类中的方法都是该对象的静态方法,类似于Java中的静态类

4.5 设置构造函数参数的默认值

给构造函数参数提供一个默认值,在调用构造函数时可以指定或者也可以不指定参数。 eg: class Socket(val timeout : Int = 1000)

代码

class Socket (val timeout : Int = 1000) {

}

object Class4P5 {

  def main(args: Array[String]): Unit = {
    val socket : Socket = new Socket(timeout = 3000)
  }
}

4.6 覆写默认的访问和修改方法

覆写Scala自动生成的getter或者setter。Scala中这个优点麻烦,推荐下列方法来实现覆写默认的方法

class Stock(var _symbol : String) {
  def symbol() : String = _symbol

  def symbol_ (s :String) : Unit =  {
    this._symbol = symbol
  }
}

4.7 阻止生成getter和setter方法

Scala中如果某个变量定义为var,将会自动生成getter和setter方法;如果变量定义成val,将会自动生成getter方法

  1. 如果是class,字段没有使用var/val修饰,则不会生成getter和setter方法。如果是case class,字段没有使用var/val修饰,默认为val,生成getter方法
  2. 使用private或private[this]访问修饰符定义字段

private 和 private[this]对比

  1. 使用private修饰字段,则Scala不会自动生成getter和setter方法。
  2. 定义一个private[this]字段让私有化更进一步,让字段对象私有化
  3. 如下代码,price字段无法被相同类型的其他实例访问

代码

class Book {
  private[this] var price : Double = _

  def setPrice(price:Double ): Unit = {
    this.price = price
  }

  def isHigher(that : Book): Boolean = {
    // 报错,无法在this对象中访问that对象的price
//    this.price > that.price
    true
  }
}

4.8 将代码块或者函数赋给字段

用代码块或者调用函数给类里面的字段初始化赋值,如果算法要求较长的运行时间,可加上lazy关键字

代码

class FooTest {

  var text = {
    var lineText="unkonw text"
    try {
      lineText = "try code"
    } catch {
      case e : Exception => lineText = "catch code"
    }
    lineText
  }
}

4.9 设置未初始化的var字段类型

在一个类中设置一个未初始化的var字段类型

  1. 非String和数字类型的字段,使用Option和Some
  2. String类型字段,缺省为空串。var str : String = ""
  3. 数值类型 ----- 给定合适的类型和默认值。varB : Byte = 0; varC : Char = 0; varl : Long = 0;

代码

case class Person4P9(var userName : String, var passwd : String) {
  var age : Int = 0
  var address: Option[Address] = None : Option[Address]
}

case class Address(var city : String, var state : String) {
}

object Class4P9 {
  def main(args: Array[String]): Unit = {
    var person : Person4P9 = Person4P9("thinking", "fioa")
    person.address = Some(Address("beijin", "nanjinRoad"))

    person.address.foreach { a => {
        println(a.city)
        println(a.state)
      }
    }
  }
}

4.10 在继承类时处理构造函数参数 ----- 继承必看

继承一个基类,需要处理定义在基类中构造函数的参数,同时也需要处理子类中的新参数。

通过将基类的构造函数参数定义为var或val。当定义在子类构造函数时,不要用var或val声明类间的公用的字段; 在子类中使用var或者val字段定义新的构造函数

代码

class Person4P11(var name : String , var age : Int) {
  override def toString: String = s"name is $name, age is $age"
}

class Employee(name : String, age : Int, var address : Address) extends Person4P11(name, age){
  override def toString: String = s"name is $name, age is $age, address is $address"
}

object Class4P10 {
  def main(args: Array[String]): Unit = {
    val employee : Employee  = new Employee("ppp", 26, Address("Anhui", "anqin"))
    println(employee)
  }
}

4.11 调用父类的构造函数

当子类构造函数需要调用超类构造函数,这个Scala语言和Java语言有较大的不同。

Scala中只有子类的主构造函数才有资格调用超类的构造函数,子类的辅构造函数第一行必须是调用当前类的另一个构造函数,所以子类的辅构造函数不能直接调用超类的任何一个构造函数

4.12 何时使用抽象类

Scala中的特质比抽象类更灵活。基于下面两个原因使用抽象类:

  1. 需要创建一个有构造函数参数的基类,特质(trait)不允许有构造函数参数
  2. 需要被Java调用

代码

abstract class BaseController {
  def save(): Unit = {
    // save action
  }

  def update() : Unit = {
    // update action
  }

  // abstract method
  def connect()
}

4.12.1 Scala中类和对象的总结

  1. object ----- 伴生对象,类似于Java中的静态类
  2. class ----- 基本的类,与Java中类似
  3. abstract classs ----- 抽象类,关于何时使用抽象类,参见4.12
  4. case class ----- case类,比较特殊,适用于模式匹配场景使用。与普通的class有如下不同:缺省val和var修饰符,属性的可见性不同; 
  5. trait ----- 特质,类似于Java1.8中接口。可以有属性,方法,抽象方法
  6. 继承Enumeration ----- 枚举类,代码参见如下

代码

object HttpMethod extends Enumeration {
  type HttpMethod = Value
  val UNKNOWN = Value("UNKNOWN")
  val GET = Value("GET")
  val OCCUR = Value("OCCUR")
  val POST = Value("POST")
}

4.13 在抽象基类(或者特质)定义属性

在一个抽象类(或特质)中声明var 或者 val字段,这些字段可以是抽象的,也可以是具体的实现。在一个抽象类(或特质)里把抽象字段声明成var 或 val,取决于具体的需求

  1. 抽象的var生成getter和setter方法
  2. 抽象的val生成getter方法
  3. 在一个抽象类(或特质)中定义一个抽象字段时,Scala编译器不会在结果代码中创建这个字段,只会生成该var或val字段的响应方法
  4. override关键字不是必须的
  5. 开发人员可以在抽象类中定义了一个def字段,而不是val也是可以的

代码

abstract class Animal4P13 {
  var greeting : String = "Hello"
  val age : Int = 0

  val sayHello: Unit = {println(s"Animal4P13, say $greeting")}
}

class Dog4P13 extends Animal4P13 {
  greeting = "Dog"

  override val sayHello: Unit = {println(s"Dog4P13, say $greeting")}
}

object Class4P13 {

  def main(args: Array[String]): Unit = {
    val dog : Dog4P13 = new Dog4P13()
  }
}

4.14 用Case类生成模版代码

Scala生成模版代码,模版代码包括了访问和修改方法,apply,unapply,toString,equals,和hashCode等方法。通过将类定义为Case类会自动生成模版代码的诸多好处:

  1. 自动生成一个apply方法,这样就不用new关键字创建新的实例
  2. Case类的构造函数参数默认是val,自动生成getter方法
  3. 自动生成一个toString方法
  4. 自动生成一个unapply方法,在模式匹配是很好用
  5. 自动生成equals方法和hashCode方法
  6. 自动生成copy方法

4.14.1 提醒

Case类主要是为了创建"不可变的记录",非常适用于模式匹配场景使用。建议Case类的参数默认是val,不允许写成var,否则就违背了"不可变的记录"的本意

4.15 定义一个equals方法(对象的相等性)

Scala中的对象相等性判断和Java具有较大不同点。众所周知,Java语言中==方法是比较两个对象的引用的相等性;而在Scala中==方法等同于equals方法

代码

class Person4P14(val name : String, val age : Int) {

  def canEqual(a : Any) : Boolean = a.isInstanceOf[Person4P14]

  override def equals(that : Any) : Boolean = that match {
    case that : Person4P14 => this.canEqual(that) && this.hashCode() == that.hashCode()
    case _ => false
  }

  override def hashCode() : Int = {
    // TODO 补充hashCode值
    31
  }
}

4.16 创建内部类

Scala内部类从属于外部类对象的,有以下四种内部类的表现形式。(java类中内部类从属于外部类)

  1. 外部类class ----- 内部类class
  2. 外部类class ----- 内部对象object
  3. 内部对象object ----- 外部类class
  4. 内部对象object ----- 内部对象object

代码

class OuterClass {
  class InnerClass {
    var x = 1
  }

  object InnerObject {
    val y = 2
  }
}

object OuterObject {
  class InnerClass {
    var m =3
  }

  object InnerObject {
    var n = 4
  }
}

object Class4P16 {

  //调用外部类中的内部类
  val oc = new OuterClass
  var ic = new oc.InnerClass
  println(ic.x)

  //调用外部类中的内部类对象
  println(new OuterClass().InnerObject.y)

  //调用外部对象的内部类
  println((new OuterObject.InnerClass).m)

  //调用外部对象的内部对象
  println(OuterObject.InnerObject.n)
}

 

10-04 12:02