Skip to content

Latest commit

 

History

History
301 lines (223 loc) · 5.9 KB

专题2——抽象成员.md

File metadata and controls

301 lines (223 loc) · 5.9 KB

专题 2 —— 抽象成员

一、抽象成员

在 scala 中,抽象成员是指在抽象类或trait中没有完整定义的元素,其中包含成员函数,成员变量与抽象类型:

trait Abstract {
    type T
    def transform(x:T) :T
    val initial :T
    var current :T
}

简单地重写

class Concrete extends Abstract {
    type T = String
    def transform(x:String) = x + x
    val initial = "hi"
    var current = initial
}

二、抽象类型

抽象类型是定义一个占位符类型,有如下定义

class Food
abstract class Animal {
    def eat(food: Food)
}

实现牛吃草

class Grass extends Food
class Cow extends Animal {
    override def eat(food: Grass) {}
}

这里会报错,因为 eat 方法参数类型不匹配。方法不能被重载。

使用抽象类型来解决这个问题

class Food
abstract class Animal {
    type SuitableFood <: Food //上界限制
    def eat(food:SuitableFood)
}

注入实际类型

class Grass extends Food
class Cow extends Animal {
    type SuitableFood = Grass
    override def eat(food:Grass) {}
}

三、抽象成员预初始化

initialization-order

abstract class A {
  val x1: String
  val x2: String = "mom"

  println("A: " + x1 + ", " + x2)
}
class B extends A {
  val x1: String = "hello"

  println("B: " + x1 + ", " + x2)
}
class C extends B {
  override val x2: String = "dad"

  println("C: " + x1 + ", " + x2)
}
new C

打印的结果

A: null, null
B: hello, null
C: hello, dad

看似 A 有一个默认的初始值,因为类 C 中重载了 x2。这意味着特质 A 构造时,给 B 分配了一个缺省初始值 null,而不是原有的值 "mom"。

特质中 val 的初始化和重写规则

① 超类会在子类之前初始化,超类先初始化,子类再初始化;

② 当一个 val 被重写时,只能初始化一次。例如,x2 在 B处初始化了,并且在 C处也初始化了,只有C处的生效。

如果 x2 同时在 B、C 两处初始化,打印的结果是

```scala
A: null, null
B: hello, null
C: hello, dad
```

如果 x2 仅在B处初始化则,打印的结果是

```scala
A: null, null
B: hello, dad
C: hello, dad
```

如果 x2不发生重写,初始值就是默认初始值。打印的结果是

```scala
A: null, mom
B: hello, mom
C: hello, mom
```

③ 与抽象 val 类似,重载的 val 在超类构造期间会有一个缺省的初始化。

解决方案

(1)预先初始化成员的值

trait Person {
  val name:String
  val age:Int
  require(age != 0)
}

new {
  val name = "lisi"
  val age = 10
} with Person

class Kid extends {
  val name = "lisi"
  val age = 20
} with Person {
  val temper: Int = 0
}

this 不指向当前所构造的对象,而是指向定义当前方法的所在对象

object ITest {
  val m = 3

  def main(args: Array[String]): Unit = {
    new {
      val age = 10
      val name = "lisi" + this.m
    } with Person
  }
}

(2)延迟初始化

trait LazyRationalTrait{
    val numerArg :Int
    val denomArg :Int

    lazy val numer = numerArg/g
    lazy val denom = denomArg/g

    private lazy val g = {
        require(denomArg !=0)
        gcd(numerArg,denomArg)
    }
    private def gcd(a:Int,b:Int):Int =
        if(b==0) a else gcd(b, a % b)

    override def toString = numer + "/" + denom
}

四、关于重写

参看 三、抽象成员预初始化 中的第一个例子

注意

  • val x1 没有完整的定义,属于抽象成员。则重写时不需要添加 override 修饰符,否则必须加 override
  • 父类中的 var 不可被 override

钻石结构

所谓的钻石结构就是一个菱形的结构,一个基类,两个子类,最后一个类又继承这两个子类。那么如果这两个子类都包含一个基类的方法,那么最后的这个类也有这个方法,选择继承那个子类呢?

/*
diamond problem
*/
object Testclass {
    trait Animal {
        def talk: String
    }
    trait Cat extends Animal {
        def talk: String = "I am Cat"
    }
    trait Monkey extends Animal {
        def talk: String = "I am monkey"
    }
    trait Dog extends Animal {
        override def talk: String = "I am Dog"
    }

    class MonkeyCat extends Monkey with Cat {
        override def talk: String = "I am monkeyCat"
    }

    def main(args: Array[String]): Unit = {
        val kittyDog = new Cat with Dog
        println(kittyDog.talk)
        val monkeyCat = new MonkeyCat
        println(monkeyCat.talk)
    }
}
/*
I am Dog
I am monkeyCat
*/

结构类型

Scala 支持 结构类型(structural types) — 类型需求由接口 结构 表示,而不是由具体的类型表示。

scala> def foo(x: { def get: Int }) = 123 + x.get
foo: (x: AnyRef{def get: Int})Int

scala> foo(new { def get = 10 })                 
res0: Int = 133

这可能在很多场景都是相当不错的,但这个实现中使用了反射,所以要注意性能!

特质中的抽象类型成员

你可以使用 hash 操作符来引用一个抽象类型的变量:

scala> trait Foo[M[_]] { type t[A] = M[A] }
defined trait Foo

scala> val x: Foo[List]#t[Int] = List(1)
x: List[Int] = List(1)

抽象类型与参数化类型

如果类型参数用于构造器时,应当使用参数化类型,因为抽象类型不在构造器参数列表的作用域内。

以下场景可用于抽象类型:

abstract class BulkReader {
  type In
  val source: In

  def read: String
}

class StringBulkReader(val source: String) extends BulkReader {
  type In = String

  def read: String = source
}

class FileBulkReader(val source:File) extends BulkReader {
  type In = File
  
  def read: String = {
    ""
  }
}