在 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) {}
}
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 = {
""
}
}