Scala 令人着迷的类设计

时间:2023-03-09 17:11:23
Scala 令人着迷的类设计

这篇博客深入介绍 Scala 的类的相关知识, 看看 Scala 简洁的类定义背后都发生了什么? 从简洁的 Scala 类定义代码到冗长的反编译代码解读之后, 回过头在去编写简洁的 Scala 代码时, 我相信这是一个奇妙的感觉.

尽管 Scala 和 Java 有很多相同的地方, 但是在类的声明, 构造, 访问控制上存在很大的差异, 通过本文你也能看到相比较 Java 很多啰嗦的模板代码, Scala 更加的简洁, 使用 Scala 之后, 我想你再也不想去编写那些冗长的 Java 代码了. 不过由于 Scala 写代码简化了很多东西(背后为我们编写很多模板代码), 如果你刚从 Java 转到 Scala, 会感觉有点不适应, 不过一旦你了解 Scala 类的知识, 你将会有不一样的感觉.

为了让你看清楚 Scala 类的全貌, 本文使用 Java Decompiler 反编译工具向你展现 Scala 代码反编译的结果, 这样 Scala 都做了什么你就一目了然了. 还有一点就是, JavaBean 中 一对 getter /setter 方法通常称为属性, 由于 Scala 并没有遵循 JavaBeans 规范将字段属性定义为 getXXX, setXXX, 现在有各种中文版称呼, 现在还没有一个让我感到很舒服的称中文名称, 所以本文还是沿用 Java 中称呼, 用 setter 表示修改方法, getter 表示取值方法, 如果你从 Java 中转过来, 这样表示你将会感到很舒服.

本文以如下思路依次展现 Scala 类相关知识. 为了能避免理论上的空谈, 我们从代码入手, 这就要求我们先得有一个类, 所以我们先从类的主构造器入手, 看看 Scala 类的大致样子, 然后再介绍类的字段定义和访问控制, 方法可见性, 辅助构造器等相关知识, 下面, 我们先看看类的主构造器吧

1. 主构造器

如果你是从 Java 转到 Scala, 你马上就能发现 Scala 声明主构造函数的过程和 Java 区别很大; Java 中构造器函数的定义一目了然, 由于Scala 的设计者认为每敲一个键都是珍贵的, 所以把 Scala 主构造器的定义和类的定义交织在一起, 导致 Scala 的主构造器没法像 Java 的构造器那样清晰了. 当我们学习新知识时, 开放的心态是很重要的, 因为这样我们才能欣赏不同第一眼令我们困惑的设计蕴含的迷人的东西. 在看到下面的代码时, 如果你觉得困惑, 不妨以一种比较开放的思维来看待这样的设计, 想想这样设计给我们带来的代码上的简洁. Scala 之父 Martin Odersky 建议我们这样来看待主构造器, "在 Scala 中, 类也接受参数, 就像方法一样". 开始介绍技术上的知识:)

主构造器结构

先来说明 Scala 类一个术语的定义, 字段(Filed), 对应于 Java 中成员变量, 不过又有不同之处, Scala 中字段还对应一组 setter/getter 方法, 现在有疑问的话, 可以先当成员变量理解, 看到后面就懂了.

在 Scala 中, 每个类都有主构造器, 有如下的结构

  1. 主构造器的定义和类的定义交织在一起, 主构造器的参数被编译成字段;
  2. 主构造器会执行类定义中的所有语句;
  3. 如果类名后没有参数, 即该类具备一个无参主构造器, 这样的一个构造器仅仅简单的执行类体的所有语句而已

好的, 我们来看一个简单的 Flower 类, Flower 类体由 3 个字段, 1 个方法定义和调用语句, 以及 2 个 println 语句构成

class Flower(val name: String, var color: String) {

  println("constructor start")

  var number = 10
def showMessage = println(s"$number $color $name")
showMessage println("constructor finish")
} object Test extends App {
new Flower("lilac", "purple") // lilac 丁香花
} /*输出
constructor start
10 purple lilac
constructor finish
*/

我们先来看看看上面的 Flower 类. 定义 Flower 类时, 我们直接在类名后加上了参数列表, 即主构造器的参数列表, 这是与 Java 的不同之处, 即上面说的第 1 个特点, 主构造器的定义和类的定义交织在一起, 并且这些由 val 或 var 定义的参数列表会成为 Flower 类的字段(成员变量); 接着, 我故意把 Flower