笔记来源:Scala谜题

多级继承

Scala 支持面向对象的编程概念,继承是它的一个很重要的特征。继承通常对父类和特质中定义的缺省值的重载很有用。当增加多级继承时事情变得更加有趣,例如下面这段程序。

trait A {val foo: Intval bar = 10println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends B {override val bar = 99println("In C: foo: " + foo + ", bar: " + bar)
}
new C()

它的打印结果我一开始也没想到:

In A:foo:0,bar:0
In B:foo:25,bar:0
In C:foo:25,bar:99

首先我们要知道 val 的初始化和重载规则:

  1. 超类会在子类之前初始化;
  2. 按照声明的顺序对成员初始化;
  3. 当一个 val 被重载时,只能初始化一次;
  4. 与抽象 val 类似,重载的 val 在超类构造期间会有一个缺省的初始化。

因此,根据以上规则,在最开始的那段程序中,虽然表面上看在特质 A 中给 bar 分配了一个初始值,实际上却不是这样,因为类 C 中重载了 bar。这意味着特质 A 构造时,给bar 分配了一个缺省初始值
0,而不是原有的值 10。

Scala 从Java 中继承了初始化顺序规则。Java 确保首先初始化超类,这样就可以从子类构造器中安全使用超类字段,确保正确地初始化字段。特质会被编译成接口和具体的(非抽象的)类,所以也可应用同样的规则。

Scala 给记录赋的缺省初始值为:

  • Byte、Short 和Int 类型val 的初始值是0
  • Int、Long、Float 和Double 类型val 的初始值分别是0L、0.0f 和0.0d
  • Char 类型val 的初始值是'0'
  • Boolean 类型val 的初始值是false
  • Unit 的初始值是()
  • 所有其他类型的初始值是 null

那如果我们想要展示的是这样的结果:


In A:foo:0,bar:99
In B:foo:25,bar:99
In C:foo:25,bar:99

我们应该怎么做呢:

用定义

一种方法是将 bar 声明为 def,而不是 val。之所以这个方法能解决问题,是因为 def 这个方法体不属于主构造器,因此不参与类初始化。此外,因为类 C 中重载了 bar,多态会特别选择使用这个重载的定义。因此,3 个 println 语句中的 bar 都会调用类 C 中的重载定义。

trait A {val foo: Intdef bar: Int = 10println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends B {override def bar: Int = 99println("In C: foo: " + foo + ", bar: " + bar)
}

这种方法的一个缺点是每次调用都要评估。Scala 也遵从统一访问原则,所以在超类中定义一个参数方法不会阻止在子类中将它重载为一个 val,这会导致令人迷惑的行为再次出现,从而破坏原有的架构规划。

lazy val

另外一种避免这种意外的方法是将 bar 声明为 lazy vallazy val 在初次访问时初始化。而常规的 val,又叫静态变量,是在定义时初始化的。lazy val 使用编译器生成的方式初始化,这里将调用特质 Cbar 的重载版本。注意,lazy val 的特点是将高成本的初始化过程尽可能推迟到最后时刻(有时可能永远也不进行初始化)。

trait A {val foo: Intlazy val bar = 10println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends B {override lazy val bar = 99println("In C: foo: " + foo + ", bar: " + bar)
}

不过,要注意的是,lazy val 也有一些缺点:

  1. 由于在底层发生同步,这会引起轻微的性能成本;
  2. 不能声明抽象 lazy val
  3. 使用 lazy val 容易产生循环引用,从而导致首次访问时发生栈溢出错误,甚至可能发生死锁;
  4. 如果在对象间做了声明而 lazy val 间的循环依赖却不存时,就可能会发生死锁,这种情况也许非常微妙,不易觉察。

预初始化字段

使用预初始化字段(也就是大家所知道的早期初始化器)也可以达到相同的效果:

trait A {val foo: Intval bar = 10println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends {override val bar = 99
} with B {println("In C: foo: " + foo + ", bar: " + bar)
}

这段程序与原来的程序的唯一差别,就是 bar 在类 C 的早期字段定义从句中初始化。早期字段定义从句紧跟着 extends 关键字后的大括号,它是子类的一部分,在超类构造器之前运行。这样就可以确保 bar 在特质 A 被构造之前即被初始化。

总结

用什么方法解决潜在的初始化顺序问题,是因不同的用例而有所不同的:

  • 如果每次访问评估表达式的成本不是太高,也许会用定义的方法。
  • 或者只要能避免循环依赖,就可以用 lazy val 的方法,这对用户的类来说也许是最简单的解决方案。
  • 或者,如果用户很清楚他们应该使用早期字段定义,那么简单地使用原来的抽象 val 也是一个不错的选择。

【Scala谜题】继承相关推荐

  1. Scala的继承和多态

    基本语法 class 子类名 extends 父类名 { 类体} 其中: (1)子类继承父类的属性和方法 (2)scala 是单继承 案例实操 (1)子类继承父类的属性和方法 (2)继承的调用顺序:父 ...

  2. Scala 多继承问题

    多继承问题: object LoadIssueDemo extends App {import java.io.PrintWritertrait Logger {def log(msg: String ...

  3. 大数据必学语言Scala(三十):scala面向对象 继承(extends)

    文章目录 继承(extends) 简单继承 override和super isInstanceOf和asInstanceOf getClass和classOf 访问修饰符

  4. Scala学习教程笔记二之函数式编程、Object对象、伴生对象、继承、Trait、

    1:Scala之函数式编程学习笔记: 1:Scala函数式编程学习:1.1:Scala定义一个简单的类,包含field以及方法,创建类的对象,并且调用其方法:class User {private v ...

  5. Scala入门到精通——第九节 继承与组合

    主要内容 类的继承 构造函数执行顺序 方法重写 匿名类 多态与动态绑定 组合与继承的使用 1 类的继承 下类的代码演示了Scala类的继承 //Person类 class Person(name:St ...

  6. Scala 继承和特质

    一.继承 1.1 Scala中的继承结构 Scala 中继承关系如下图: Any 是整个继承关系的根节点: AnyRef 包含 Scala Classes 和 Java Classes,等价于 Jav ...

  7. spark之scala快速入门

    scala和java都是在jvm之上的语言,相对来讲,scala热度比较低,其实并不是一个特别好的语言选择. 原因倒不是因为scala本身的缺点,而是使用人群不够多,论坛和社区不够活跃.这就跟社交软件 ...

  8. Scala学习(八)练习

    Scala中继承&练习 1. 扩展如下的BankAccount类,新类CheckingAccount对每次存款和取款都收取1美元的手续费 class BankAccount ( initial ...

  9. Scala基础教程(七):类和对象、特征

    扩展一个类: 可以扩展scala类以类似的方式,如在Java中的一样,但有两个限制:方法重载需要override关键字,只有主构造可以传递参数给基构造.现在扩展上面的类,并增加一个类的方法: clas ...

最新文章

  1. 微信小游戏开发教程-游戏实现1
  2. 数学奥赛用不用计算机,报考自招必看!五大学科竞赛利弊详解,到底哪科最适合你?...
  3. SAP Fiori Launchpad的后台配置路径
  4. 微软人工智能愿景:根植于研发 寄望于“对话”
  5. 阿里云助力浙江大学信息化建设,以实时数据驱动校园智能管理
  6. 又一个半成品库 weblog rpc client
  7. python get函数 i_Python高阶技巧,你 GET了吗?
  8. ES6--Reflect
  9. G 音乐鉴赏(非二分解法)
  10. 若依设置匿名访问路径
  11. 数据库与身份认证(学习笔记)
  12. 千锋深圳Java培训分享:MySQL详细知识点
  13. 零基础想要做好人物角色模型,先了解人体的构造!快来康康
  14. python求单词长度_python 统计单词平均长度,统计a出现的次数
  15. 有一个已经排好序的数组,要求输入一个数后,按原来排序的规律将它插入数组中
  16. 王者荣耀与英雄联盟:如何解决玩家骂人的问题?
  17. linux 查看dns进程,探查Linux系统DNS服务器运行状况
  18. 递归解决常见爬楼梯走一步或是两步问题,走多步也是相同的道理!
  19. Node-RED中使用JSON数据建立web网站
  20. 推荐系统的评价指标笔记(NDCG、MAP、AUC、HR、MRR)

热门文章

  1. C语言程序设计 C语言中的时间函数
  2. 局域网内时间同步的一种简单办法
  3. blob转成json js_javascript – 文件API – Blob到JSON
  4. 大学文科计算机考试大纲,(文科)大学计算机信息技术课程考核大纲(文科)介绍.doc...
  5. 故障码123401_电力系统规划设计对电力工程设计的应用
  6. mysql+表复制+效率_MySQL数据库复制表的几种方式讲解
  7. jdbc map获取keys_跟我学shardingjdbc之分布式主键及其自定义
  8. systemd管理mysql多实例_使用 systemd 配置多个 MySQL 8.0 实例
  9. docker可视化管理界面_分析一款Docker容器可视化管理工具Porttainer
  10. 前端HTML5CSS动画变形动画之过渡