這篇博客深入介紹 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 中, 每個類都有主構造器, 有如下的結構
- 主構造器的定義和類的定義交織在一起, 主構造器的參數被編譯成字段;
- 主構造器會執行類定義中的所有語句;
- 如果類名后沒有參數, 即該類具備一個無參主構造器, 這樣的一個構造器僅僅簡單的執行類體的所有語句而已
好的, 我們來看一個簡單的 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 🌺的數量 number 字段定義在類體里面, 是為了說明我們既可以選擇將字段定義在主構造器參數中, 也可以將字段定義在類體中, 字段定義在主構造器參數中的好處就是能夠在 new 一個對象的時候, 能將傳入參數給字段賦值. 並且我們定義了一個方法 showMessage, 並且調用了它;
在測試語句中, 我們使用 new Flower("lilac", "purple")
調用了類的主構造器, 從 Flower 類調用主構造器初始化的輸出結果可以看出 Scala 主構造器的 第 2 個特點, 即主構造器會執行類定義中的所有語句, 從語句 println("constructor start")到類定義的最后一條語句 println("constructor finish").
其實主構造器的第一條語句是主構造器參數的所在字段的賦值語句, 具體看下面的反編譯之后的構造器部分).
然后我們看看為什么背后發生了什么? 這里使用 JAD 工具反編譯結果如下, 為了滿足你的好奇心和內容的整體性, 這里我把反編譯的全部代碼都貼在下面, 然后后面部分一點一點說明, 所以說如果你有疑惑的部分也不要緊, 后面會把所有的問題解釋清楚.
Flower類反編譯代碼
import scala.Predef;
import scala.StringContext;
import scala.collection.Seq;
import scala.collection.mutable.WrappedArray;
import scala.reflect.ScalaSignature;
import scala.runtime.BoxesRunTime;
// 此處刪去了 @ScalaSignature(bytes = XXXX)
public class Flower {
private final String name;
private String color;
private int number;
public String name() {
return this.name;
}
public String color() {
return this.color;
}
public void color_$eq(String x$1) {
this.color = x$1;
}
public int number() {
return this.number;
}
public void number_$eq(int x$1) {
this.number = x$1;
}
public void showMessage() {
Predef..MODULE$.println((Object)new StringContext((Seq)Predef..MODULE$.wrapRefArray((Object[])new String[]{"", " ", " ", ""})).s((Seq)Predef..MODULE$.genericWrapArray((Object)new Object[]{BoxesRunTime.boxToInteger((int)this.number()), this.color(), this.name()})));
}
public Flower(String name, String color) {
this.name = name;
this.color = color;
Predef..MODULE$.println((Object)" constructor start");
this.number = 10;
this.showMessage();
Predef..MODULE$.println((Object)"constructor finish");
}
}
上面反編譯的結果, 如果不糾結細節, 從總體上來看還是很清晰的. 現在我們來分析一下
Flower類反編譯代碼解析
首先, 我們定義類 Flower 時, 並沒有聲明類的可見性, 反編譯的結果 Flower 類的可見性是 public. 這是因為在 Scala 中, 類的默認可見性就是 public, 且 Scala 源文件中可以包含多個類, 這些類可見性都是 public.
然后, 我們來看看主構造器 public Flower(String name, String color)
, 可以看出主構造器會執行類中定義中的所有語句, 也就是說 Scala 主構造器包含了整個類定義, 和類的定義交織在一起; 同時我們看一看到反編譯代碼類的前 3 行是:
private final String name;
private String color;
private int number;
可以看出主構造器參數花名 name 和顏色 color 都自動變成了反編譯類中的成員變量(這里沒有說是字段是因為在反編譯代碼很接近 Java 源代碼, 比如說 name, 只是一個成員變量). 你一定發現了反編譯代碼中還有一些方法, 下面我們就來說說這些方法. 不過在說這些方法之前, 我們來回憶一下字段, 之前說過 Scala 的字段和 Java 的成員變量的類似, 其實差別還是很大的, 因為在 Scala 中定義字段時, 自動生成了 setter/getter 方法; 在 Scala 中, getter 和 setter 分別叫做 XXX
和 XXX_=
, 比如說字段 color 的 getter/setter 方法就是 color_= 和 color
private String color;
public String color() {
return this.color;
}
public void color_$eq(String x$1) {
this.color = x$1;
}
看到這里你可能會疑惑, 因為你發現, 編譯器顯示的創建的是 color
和 color_$eq
方法, 由於 JVM 不允許在方法名中使用 =
, 所以在編譯過程中 將 = 替換為 $eq
. 結合上面的代碼, 我們可以這樣理解 Scala 類的字段
字段 = 成員變量 + getter/setter方法
也就是在定義字段時, 我們心中要明白, 我們除了創建了可見的變量之外, 其實 Scala 還自動為我們創建了 getter/ setter 方法.
到這里, 主構造器的知識就到此為止了, 關於輔助構造器的知識放在后面的小節再說. 因為我想你現在更多的疑惑的在字段身上, 比如說 為什么字段 name 只有 getter 方法, 而 字段 color 同時有 getter 和 setter 方法. 從下一節, 我們來探索下 Scala 的字段相關知識.
結束之前我們來做一個"無聊"的數數游戲, 在這里我們假設反編譯代碼等同於實現相同功能的 Java 代碼. Scala 實際代碼長度為 6 行, 而 Java 代碼大約花費 23 行代碼才能實現, 從而可以看到 Scala 代碼的簡潔性. 在這里我們引用 <<黑客與畫家>>的一個例子, 在實際開發中, 假設寫每行代碼的時間相同, 一個項目使用 Scala 開發完成需要1年, 而使用 Java 開發需要 4 年. 所以, 雖然剛開始接觸 Scala 的類時, 感覺有些疑惑, 不過思想上願意后, 很快就會覺得這樣才自然.
在 Java 中構建單例對象時, 需要將構造器私有化, 在 Scala 中你也可以將主構造器私有化, 可以這樣使用 private 修飾符, 由於 Scala 中有單例對象 object, 所以這個知識也就是提一下, 還不清楚生產環境中有什么用途
class Pet private (val creature: String)
2. 類的字段
上一節結束時說到了, Flower 類反編譯代碼中, 為什么字段 name 只有 getter 方法, 而 字段 color 同時有 getter 和 setter 方法? 在說明這個問題之前, 讓我們簡化一下之前的類 Flower, 由於這一節的關注點是字段, 所以我們只留下字段, 並在類中添加一個字段 language(花語).
class Flower(val name: String, var color: String) {
var number = 10
val language = "Missing you"
}
反編譯代碼
2.1 Flower類的字段反編譯代碼
public class Flower {
private final String name;
private String color;
private int number;
private final String language;
public String name() {
return this.name;
}
public String color() {
return this.color;
}
public void color_$eq(String x$1) {
this.color = x$1;
}
public int number() {
return this.number;
}
public void number_$eq(int x$1) {
this.number = x$1;
}
public String language() {
return this.language;
}
public Flower(String name, String color) {
this.name = name;
this.color = color;
this.number = 10;
this.language = "Missing you";
}
}
我們可以看到, 字段既可以在兩個地方定義:
- 類的主構造器參數中
- 類中定義
下面我們先來說明在字段在類中定義的情況, 關於字段在主構造器參數中定義的情況與之有一點點的小區別, 為了文章的清晰性, 我們還是分開介紹.
2.2 類中定義的字段
類中定義的字段的訪問控制屬性由關鍵字 val, var 和 private 控制. 這里要說明的一點是 如果我們按照之前說過的方式來理解 Scala 的字段的話, 即
字段 = 成員變量 + getter/setter方法
Scala 的字段對應的成員變量的可見性默認就是 private 的, val 和 var 控制的字段是否可變, 即是否有 setter 方法; 這里要分清楚的是, 我們本節一開始說類的字段的屬性可以由 private 關鍵字控制, 實際上 private 關鍵字控制的是字段對應的 getter/setter 方法的可見性, 我們需要明確寫出, 而字段的成員變量的可見性是 private, 是強制默認的.
類中字段定義的具體是規則是:
- 如果字段被聲明為 var, Scala 會為該字段生成 getter 方法和 setter 方法, 方法的可見性是 public
- 如果字段被聲明為 val, Scala 會只為該字段生成 getter 方法, 方法的可見性是 public
- 如果字段被聲明為 private var, Scala 會為該字段生成 getter方法和 setter方法, 方法的可見性是 private
- 如果字段被聲明為 private val, Scala 會只為該字段生成 getter 方法, 方法的可見性是 private
這也好理解, Scala 中 var定義的是可變引用, val 定義的是一個不變引用, 沒法修改, 自然就不可以有 setter方法去修改它. 如果你不需要 getter/setter 方法, 你可以將字段聲明為 private var 或者 private val, 這樣就沒法在類的外部調用了 getter/sette 方法訪問字段和修改字段的值了.
如果你現在還有點疑惑的話, 我們來看看下面的例子. 通過實例學習是不錯的學習方式, 看完例子之后你就豁然開朗了. 那么我們來看看 Flower 類中定義的字段
var number = 10
val language = "Missing you"
對應的反編譯代碼整理如下
// 說明: number 字段由 var 定義,
// 反編譯代碼生成的成員變量可見性為 private,
// 並生成 setter 方法和 getter 方法, 可見性為 public.
private int number;
public int number() {
return this.number;
}
public void number_$eq(int x$1) {
this.number = x$1;
}
// 說明: language 字段由 val 定義,
// 反編譯代碼生成的成員變量可見性為 private, 用 final定義為常量,
// 並生成了 getter 方法, 可見性為 public.
private final String language;
public String language() {
return this.language;
}
如果將上面字段使用 private 關鍵字修飾的話, 即
class Flower(val name: String, var color: String) {
private var number = 10
private val language = "Missing you"
}
反編譯結果如下, 不做解釋
public class Flower {
private int number;
private final String language;
// getter, setter 方法可見性為 private
private int number() {
return this.number;
}
private void number_$eq(int x$1) {
this.number = x$1;
}
private String language() {
return this.language;
}
}
類中定義的字段的基礎知識就到這里了, 主構造器中定義的字段的方式幾乎與類中定義的字段一模一樣, 除了一點區別之外, 下面讓我們看看吧
2.3 主構造器中定義的字段
類中定義字段的方式可以完全照搬到主構造器中字段的定義中, 除此之外, 主構造器的參數可以像一般方法參數一樣, 不帶 val 或者 var, 有意思的時, 這樣的參數如何處理取決於它們在類中如何被使用
- 如果不帶 val 或者 var 的參數至少被一個方法所使用, 它變成由 val 的字段
- 否則, 該參數將不會被保存為一個字段, 它僅僅是一個可以被主構造器的代碼訪問的普通參數,.
關於第 2 點, 即構造器不被任何方法使用時, 那么這個參數的存在就沒有實際意義, 所以編譯器就做了優化, 並不會將這樣一個沒有實際意義的參數定義為類的字段. 我們可以假想一個場景, 類設計者在一開始給在類的主構造器中定義的很多對象屬性, 但是這些屬性不是強制使用的, 后來的開發人員可以根據需要使用.
同樣還是通過一個例子就能說明, 例子中定義了兩個構造器參數, 故意設計為一個被方法使用了和一個從來沒有使用過, 然后我們看看反編譯結果
class Book(name:String, pages: Int) {
def bookPages = pages
}
反編譯代碼
public class Book {
private final int pages;
public int bookPages() {
return this.pages;
}
public Book(String name, int pages) {
this.pages = pages;
}
}
可以看出參數 pages 被使用了, 所以被升格變為由 val 定義的字段, 而參數 name, 它僅僅是一個可以被主構造器的代碼訪問的普通參數.
現在類中字段定義和主構造器中字段定義都說清楚了, 下面我們來看看字段更加細粒度的訪問控制吧, 你也看可以跳過下面這一小節, 因為這個知識很繁瑣, 用到的機會也不多.
2.4 字段細粒度訪問控制
相比較 Java, 在 Scala 中的字段訪問控制實際上可以做到更加的細粒度, 這里引用 <<Scala 編程實戰>> 的方法可見性級別, 書中按照從 "最嚴格" 到 "最開放" 的順序, 將 Scala 的作用域級別分為以下
- 對象私有作用域
- 私有的作用域
- 受保護作用域
- 包內可見
- 指定包內可見
- 公共可見性
這里的可見性級別也適用於字段, 為何這樣說了, 我們之前說過我們可以這樣理解字段
字段 = 成員變量 + getter/setter方法
我們實際上是通過控制字段的 getter/setter 方法的可見性來控制字段的可見性的, 說到這我希望我說明白了. 下面我不再說是方法的可見性了, 而是直接說是字段的可見性.
再補充下, 前面提到過, 在 Scala 中, 類的默認可見性是 public, 且 Scala 源文件中可以包含多個類, 這些類可見性都是 public. 我們把多個類定義在一個文件中和定義在同一個包下多個文件中效果是一樣的, 所以下面我們會為了簡潔將多個類定義在同一個源文件中.
我們代碼文件的包結構如下

我們在包 nowgood.accesscontrol 中定義了寵物類 Pet, 在類中有一些用 val 定義了"見名知意"的字段.
我們通過訪問這些字段來說明字段的可見性, 在上述目錄結構中我們在定義了如下類:
- Pet類用於說明 對象私有作用域 和 類私有作用域
- Dog類和 Panda類用於說明 受保護作用域
- Accesscontrol 類用於說明 包內可見;
- Nowgood 類用於說明 指定包內可見;
- 上面所有類中都可以說明 公有可見性
下面貼代碼, 代碼中標明 編譯器報錯
的注釋, 表明在當前作用域不能訪問的字段.
class Pet
package nowgood.accesscontrol
class Pet {
// 對象私有作用域
private[this] val privateThis = "private[this] Pet"
// 私有的作用域
private val privatee = "private Pet"
// 受保護作用域
protected val protectedd = "protected Pet"
// 包內可見
private[accesscontrol] val privateAccesscontrol = "private[accesscontrol] Pet"
// 指定包內可見
private[nowgood] val privateNowgood = "private[nowgood] Pet"
// 公共可見性
val publicc = "Pet"
protected def show = println(privateThis)
def showInfo(otherPet: Pet) {
// println(pet.privateThis) 編譯器報錯, 與 方法 show 和下面的 otherPet.privatee 對比說明, 不能訪問本類的其他實例, 具有對象私有可見性
println(otherPet.privatee)
println(otherPet.protectedd)
println(otherPet.privateAccesscontrol)
println(otherPet.privateNowgood)
println(otherPet.publicc)
}
}
class Dog extends Pet {
def showInfo1 = println(protectedd)
def showInfo2(pet: Pet) {
/* 編譯器報錯
println(pet.privateThis)
println(pet.privatee)
// 和 showInfo1 比較可以看出 protected 字段對繼承類型可見而對繼承類型的其他實例不可見
println(pet.protectedd)
*/
println(pet.privateAccesscontrol)
println(pet.privateNowgood)
println(pet.publicc)
}
}
class Accesscontrol{
def showInfo(pet: Pet) {
/* 編譯器報錯
println(pet.privateThis)
println(pet.privatee)
println(pet.protectedd)
*/
println(pet.privateAccesscontrol)
println(pet.privateNowgood)
println(pet.publicc)
}
}
class Panda {
// def showName(pet: Pet ) = pet.name3
// 編譯時錯誤, 由於 Panda 不是 Pet 的子類所以沒法在 Panda 中訪問
}
class Nowgood
package nowgood
import accesscontrol.Pet
class Nowgood {
def showInfo(pet: Pet) {
/* 編譯器報錯
println(pet.privateThis)
println(pet.privatee)
println(pet.protectedd)
println(pet.privateAccesscontrol)
*/
println(pet.privateNowgood)
println(pet.publicc)
}
}
如果你願意用心去看上面的代碼, 我想你應該很輕松就能理解了, 不過有一些細節還是在這里說明下. 這里要特別說明的是受保護保護
可見性字段, protected 聲明的字段對子類可見, 但是對子類的其他對象和包中的其他類都不可見, 這和 Java 的區別, Java 中 protected 成員變量不僅對子類可見, 而且對子類的其他類型和包中其他類都具有可見性.
看如下例子
class Person
package test;
public class Person {
protected String name;
Person(String name) {
this.name = name;
}
}
Teacher 編譯通過說明 Java 中 protected 成員變量的包可見性
class Teacher {
public void show(Person person) {
System.out.println(person.name);
}
}
class Student
package test;
// 說明 Java 父類 protected 成員變量對子類的其他對象的可見性
public class Student extends Person {
public Student (String name) {
super(name);
}
public static void main(String[] args) {
new Student("wangbin").show(new Person("wangbin"));
}
public void show(Person person) {
System.out.println(this.name == person.name);
}
}
/*輸出
true
*/
上面的說明的訪問控制機制, 其實就是方法的訪問控制機制, 並且 Scala 所有的方法參數都是不變引用, 所以說現在 Scala 方法的訪問控制和參數類型你也都掌握了, 已經能夠進行 Scala 的簡單的面向對象編程了.
其實 Scala 有更加細致的方法, 類, 字段的訪問控制機制, 想要詳細了解的請參考 <<Scala 程序設計 >> 第二版第 13 章有詳細的講解.
2.5 重定義默認的 getter/setter 方法
如果你想要自定義某個字段 getter/setter 方法的邏輯, 首先你想到可能是從覆寫 Scala 自動生成的 getter 或 setter 方法, 我們來看看這樣是不是能走的通
class Fruit(val name: String, var num: Int) {
def num()= num // 錯誤1
def num_=(n: Int) { // 報錯2
if (n >= 0) num = n // 報錯3
}
}
這里會 3 個錯誤,其中一個錯誤為
method num_=#39263 is defined twice; the conflicting method num_=#39254 was defined at line 7:7 of '/Users/mac/Documents/ScalaEclipseProject/Decompiler/src/nowgood/Fruit.scala'
總而言之就是這條路沒法走通,常見做法就是在將參數名 xxx 前添加下換線 _xxx
, 並將可見性聲明為 private, 然后手動創建 getter/setter 方法 xxx
和 xxx_=
. 補充一點, Scala 中當我們定義無參方法時, 方法名后面的小括號()
是可選的; 如果方法定義時, 采用不帶括號的風格, 方法調用時, 只能使用使用不帶括號的風格; 如果方法定義時, 采用帶括號的風格, 方法調用時, 既可以在方法名后寫上圓括號, 也可以不寫.
private 關鍵字的作用是不會把這個字段暴露給其他類, 並且下面為了同時測試 getter, setter 方法所以將字段定義為 private var.
package nowgood
class Fruit(val name: String, private var _num: Int) {
// 不可寫成 def num() = _num, 理由見下分析
def num = _num
def num_=(n: Int) {
if (n >= 0) _num = n
}
}
object TestFruit {
def main(args: Array[String]): Unit = {
var apple = new Fruit("apple", 10)
apple.num = 6
println(apple.num)
}
}
結合例子做出說明, 在重寫 Scala 風格 getter 方法時, Scala 規定使用不帶括號的風格(使用帶括號風格的話, 我們沒法使用 Scala 提供給 setter 方法的語法糖, 只能通過 apple.num_=(6)
方式調用 setter 方法, 這就是普通的方法調用, 我們把 setter 方法寫成 xxx_=
形式也失去了意義); 當我們調用 setter 方法時, 如上 apple.num = 6
就像我們直接給這個字段賦值一樣, 實際上背后調用的是 apple.num_=(6)
, 這是 Scala 給 getter/setter 方法提供的語法糖, Scala 形式的 setter 方法與 getter 方法一起出現時這種寫法才成立
這里提醒下, 千萬不要以為編譯器看到了 xxx_=
形式的方法命名就可以通過這種方式來進行方法調用. 你可以把上面的 def num = _num
定義拿掉, 編譯器報錯 value num is not a member of nowgood.Fruit
; 所以說 xxx_=
形式的方法命名只有在 getter 方法放在一起搭配時才有用. 我大膽在這里猜測一下 setter 方法 apple.num = 6
這個語法糖的工作過程, 在我們 Fruit 類中, 等號的左邊 apple.num 的返回值為是一個 Int 類型, Int 類型是不變類型, 將 6 賦值時 Scala 編譯器會收到這個異常, 然后進行異常處理. 異常處理過程為, 編譯器發現 num 方法簽名符合 Scala getter 方法風格(即無參不帶小括號), 然后編譯器會去找方法簽名為num_=
, 並且參數個數為 1 方法, 如果找到了就將 apple.num = 6
轉化為 apple.num_=(6)
的方法調用, 如果找不到報錯 value num_= is not a member of nowgood.Fruit
.
3. 輔助構造器
為了滿足業務需求, 我們大多數情況下, 我們都會定義多個構造器, Scala 中構造器分為主構造器和輔助構造器, 主構造器的風格第一節已經說過了, 現在來說一說輔助構造器.
Scala 的輔助構造器相比較主構造器來說, 結構及其簡單, 主要包括下面幾點
- 輔助構造器必須用 this 命名構建;
- 每個輔助構造器
必須
以主構造器或者一個先前定義的其他輔助構造器的調用開始; - 每個輔助構造器必須有不同的方法簽名
- 每個輔助構造器通過 this 調用另一個不同的構造函數;
即: 輔助構造器第一條語句為調用主構造器或者一個先前定義的其他輔助構造器的語句, 如果調用的是主構造器的話, 形式是 this(主構造器參數列表)
. 並且方法定義為過程形式, 即 如果方法體包含在花括號當中但沒有前面的等於號= ;
下面來看個例子
package nowgood
class Feline (val name: String, val size: String, val num: Int) {
// 輔助構造器1
def this(name: String) {
this(name, "Big", 2) // 第一條語句
}
// 輔助構造器2
def this() {
this("tiger")
}
// 輔助構造器3, 與 輔助構造器1 方法簽名沖突
// def this(size: String) {
// this(" leopard", size, 10)
//
// }
override def toString: String = s"$num $size $name"
}
object TestFeline extends App {
val cat = new Feline("cat", "small", 5)
val lion = new Feline("lion")
val tiger = new Feline()
println(cat)
println(lion)
println(tiger)
}
/*輸出
5 small cat
2 Big lion
2 Big tiger
*/
上面代碼中, 輔助構造器 3 與 輔助構造器 1 方法簽名沖突, 所以被注釋掉了, 如果你確實想要設定美洲豹的體型, 你可以使用 case class, 定義一個 case class Size(size: String), 這里只是為了引出 Scala 中的重量級類 case class, case class 使用極其簡單, 在 Scala 中使用極其廣泛, 不過由於篇幅原因就不在這里說明了, 網上有大量關於 case class 的介紹.
關於 Scala 類的介紹就到這里了, 如果你還意猶未盡的話, 還可以看看我之前寫的關於 繼承 和 特質(trait) 的文章, 特質這篇下了很大的功夫去寫, 希望能有所幫助. 最近學習 Scala 有所感悟, 分享給和我一樣為 Scala 着迷的人, 文中難免出現錯誤, 歡迎指正. 如果你有不同的看法和理解, 歡迎分享交流.
參考書籍
- 快學 Scala
- Scala 編程實戰
- scala in depth