Scala语法易混淆点

引用书目

  • Programming in Scala 2nd Edition, Martin Odersky
  • Programming Scala, Dean Wampler
  • Scala Puzzlers, Andrew Philips

以下问题的产生根源都是没有掌握 Scala Language Specification。而Scala语言本身仍在演进,所以未来仍有变化。

配图系用PlantUML编写,博客园无法自动渲染,我又懒得取图,将就这样吧。

类的初始化路径

Scala不支持多重继承,而是改用trait作为类的扩展(混入),这就给继承路径上出现的成员初始化带来了挑战。但任何一个class在其初始化时,都遵循以下原则:

  • 成员总是按其声明出现的顺序进行初始化。
  • 父类总是优先于子类构造。
  • 按主构造子里的语句顺序完成初始化。
trait A {
  val audience: String
  println("Hello " + audience)
}

trait AfterA {
  val introduction: String
  println(introduction)
}

abstract class Base(val introduction: String) {
}

class BEvery(val audience: String)
  extends Base(introduction = {println("Evaluating early def"); "Are you there?" })
  with A
  with AfterA {
  println("I repeat: Hello " + audience)
}

val b = new BEvery({ println("Evaluating param"); "Readers" })

换个视角,这几乎等价于按当前类的参数声明、继承声明、从左至右的若干父class或trait的构造/初始化、当前类主构造函数的顺序完成初始化。

class BEveryEquivalent(val audience: String = ({ println("Evaluating param"); "Readers" })) {
  val introduction = {println("Evaluating early def"); "Are you there?" }

  // val audience: String = "Readers"
  println("Hello " + audience)

  // val introduction: String = "Are you there?"
  println(introduction)

  println("I repeat: Hello " + audience)
}

另外要特别注意的,是def与val在应用覆写时的差异:

  • 所有被覆写的val成员,将在其父class/trait初始化期间保持类型缺省值,直到在子类中覆写时才完成赋值。
  • 所有被覆写的无参def函数,因为本就不属于初始化的一部分,所以和lazy val一样,都是在被引用时才返回值。同时,由于父class/trait被覆写,所以会直接调用最新的被覆写后的函数。
trait A {
  val foo: Int
  val bar = 10
  def din = 12
  lazy val wah = 5

  println("In A: foo: " + foo + ", bar: " + bar + ", din: " + din + ", wah: " + wah)
}

class B extends A {
  val foo: Int = 25

  println("In A: foo: " + foo + ", bar: " + bar + ", din: " + din + ", wah: " + wah)
}

class C extends B {
  override val bar = 99
  override def din: Int = 24
  override lazy val wah = 30

  println("In A: foo: " + foo + ", bar: " + bar + ", din: " + din + ", wah: " + wah)
}

new C()

super调用

class与trait中的super调用,总是沿着一条特定的序列进行查找。这与构造顺序密切相关,它总是与继承声明的顺序相反,超靠后声明继承关系的base class或trait将越早被调用,而这些base class或trait本身在继承树上的父类也都将被纳入这条序列。

trait Animal {
  override def toString: String = "I'm animal."
}

trait Feature {
  override def toString: String = "It's feature."
}

trait Furry extends Feature {
  override def toString: String = "I'm furry. -> " + super.toString
}

trait HasLegs extends Feature {
  override def toString: String = "I has legs. -> " + super.toString
}

trait FourLegged extends HasLegs {
  override def toString: String = "I has four legs. -> " + super.toString
}

class Cat extends Animal with FourLegged with Furry {
  override def toString: String = "I'm a cat. -> " + super.toString
}

val cat = new Cat

构造这个序列的过程,就象每次取继承声明最靠后的class或trait及其父类向列表里追加:

  • 彼此有继承关系的,则子类优先于父类。
  • 彼此是同宗亲戚时,则先来的优先于后来的。
  • 彼此无继承关系时,则新来的将截断旧有的。
  1. Cat extends Animal
(*) -> Cat
-> Animal #lightblue 
-> (*)
  1. Cat extends Animal with ... with Furry
(*) -> Cat
-> Furry #lightblue
-> Feature #lightblue
-> (*)
Cat -[dashed]-> [被截断] Animal
  1. Cat extends Animal with FourLegged with Furry
(*) -> Cat
Cat -> Furry
-> FourLegged #lightblue 
-> HasLegs #lightblue 
-> Feature
-> (*)
Cat -[dashed]-> [被截断] Animal

会反复尝试的lazy val

lazy val会反复尝试初始化,直到其成功获取赋值。(这一特性可用于反复争取某个资源。)

def trylazy = {
  var x = 0
  lazy val y = 1 / x
  try {
    println(y)
  } catch {
    case _: Exception =>
      x = 1
      println(y)
  }
}

trylazy

老实巴交的map

map在映射时不会改动原来的Collection元素顺序,于是使用未经过中间迭代转换的Collection便会得到意料之外的的排列顺序。

case class RomanNumeral(symbol: String, value: Int)
implicit object RomanOrdering extends Ordering[RomanNumeral] {
  def compare(a: RomanNumeral, b: RomanNumeral) =
    a.value compare b.value
}

val numerals = SortedSet(
  RomanNumeral("M", 1000),
  RomanNumeral("C", 100),
  RomanNumeral("X", 10),
  RomanNumeral("I", 1),
  RomanNumeral("D", 500),
  RomanNumeral("L", 50),
  RomanNumeral("V", 5)
  )

println("Roman numeral symbols for 1 5 10 50 100 500 1000:")
for (num <- numerals; sym = num.symbol) { print(s"${sym} ") }
numerals map { _.symbol } foreach { sym => print(s"${sym} ") }
numerals.view map { _.symbol } foreach { sym => print(s"${sym} ") }
posted @ 2023-02-19 21:47  没头脑的老毕  阅读(39)  评论(0编辑  收藏  举报