建構式

建構式跟Java建構子一樣,主要是用來建立物件。

建構式分為主要建構式(Primary constructor)與次要建構式(Secondary constructor)。

預設主要建構式

constructor()為主要建構式,與「class 類別名」同一行,放在「類別名」後面,預設建構式沒有參數。

語法

class 類別名 constructor() {
}
1
2
3
class Dog constructor() {
	
}

Kotlin目的在簡化程式碼,若建構式沒有參數,也沒有權限修飾子,可以省略constructor(),Kotlin會自動產生預設建構式。

1
2
3
class Dog {
	
}

若有參數,可以省略constructor的文字,只留下圓括號(參數),代表它是主要建構式。

class 類別名 (參數) {
}

主要建構式

之前在屬性的文章中提到,val唯讀屬性,不會有set()方法。

可由主要建構式設定val屬性的值,在類別中也不用再次定義val屬性。

語法,參數可以為var或val,沒有限定只能val。

class 類別名 (val 屬性: 類型, var 屬性: 類型, ...) {
	// 省略類別屬性,移到主要建構式
}

原本程式碼

1
2
3
4
class Cat {
    // 類別屬性
    val name = ""
}

改為主要建構式,注意!可以不用給初始值,但一定要有類型。

不給類型會有這個編譯錯誤,A type annotation is required on a value parameter。

這一部分跟Java final 建構子初始值的內容一樣。

1
2
3
4
// 屬性要有類型
class Cat (val name: String) {

}

由「主要建構式」建立物件。

1
2
3
4
fun main() {
    val cat = Cat("小咪")
    println(cat.name)
}
小咪

主要建構式自動產生set()、get()

搭配val與var,會自動產生set()、get(),注意!val不會自動產生set()方法。

因為val是唯讀,不能修改。

Java原始碼,因為val name唯讀,所以只有getName(),沒有setName()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class Cat {
   @NotNull
   private final String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public Cat(@NotNull String name) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
   }
}

主要建構式 + 所有屬性預設值

每一個屬性都有預設值,建立物件時,只要代入屬性名 = 值,就可以設定屬性的值。

1
2
3
4
5
6
7
8
9
10
11
12
class Cat(
    val name: String = "",
    var age: Int = 0,
    var color: String = ""
) {
}

fun main() {
    // 主要建構式 屬性名 = 值
    val cat = Cat(name = "小咪")
    println(cat.name)
}
小咪

次要建構式

次要建構式使用方式

次要建構式參數數量,不能跟主要建構式參數數量一樣。

次要建構式無法使用val與var,不會自動產生set()、get()。

因為無法使用val與var,所以會由以下三種方式搭配次要建構式。

  1. 主要建構式 + 初始化所有屬性 + 次要建構式。
  2. 類別屬性 + 次要建構式
    class 類別名 {
      // 類別屬性
      var 屬性名: 屬性類型 = 初始值
      val 屬性名: 屬性類型 = 初始值
    }
    
  3. 主要建構式屬性 + 類別屬性 + 次要建構式

Kotlin 的設計原則是:「能用簡單寫法就不要複雜化」!

類別屬性 + 次要建構式

次要建構式無法修改val的屬性。

若要透過次要建構式修改屬性,請設為var。

注意!次要建構式沒有val與var。

次要建構式由constructor(屬性:類型),注意!屬性一定要有類型。

語法:

class 類別名 {
    // 類別屬性
    var 屬性1: 類型 = 初始值
    var 屬性2: 類型 = 初始值

    // 次要建構式
    constructor(屬性1: 類型, 屬性2: 類型) {
    	this.屬性1 = 屬性1
    	this.屬性2 = 屬性2
    } 
}

範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Cat1 {
    // 類別屬性
    var name: String = ""
    var age = 0
    var color = ""

    // 次要建構式
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

測試如下:

1
2
3
4
5
fun main() {
	val cat1 = Cat1("小咪", 10)
	println(cat1.name)
	println(cat1.age)
}
小咪
10

主要建構式屬性 + 類別屬性 + 次要建構式

主要建構式中的屬性可為val。

次要建構式無法修改val的屬性。

次要建構式後面就要加上 : this(屬性),先呼叫主要建構式。

this(屬性)後面不用有類型。

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 主要建構式屬性 
class Cat2(val name: String) {
    // 類別屬性
    var age = 0
    var color = ""

    // 次要建構式
    constructor(name: String, age: Int) : this(name) {
        // name是val,不能透過次要建構式修改
        // 注意!此處沒有this.name = name
        this.age = age
    }
}

測試:

1
2
3
val cat2 = Cat2("喵喵", 2)
println(cat2.name)
println(cat2.age)
喵喵
2

主要建構式 + 初始化所有屬性 + 次要建構式

1
2
3
4
5
6
7
8
9
10
11
// 主要建構式 + 初始化所有屬性
class Cat3(
    val name: String = "",
    var age: Int = 0,
    var color: String = ""
) {
    // 次要建構式
    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

測試

1
2
3
val cat3 = Cat3("嘟嘟", 10)
println(cat3.name)
println(cat3.age)
嘟嘟
10

init初始化函式

這部分的知識跟Java匿名程式碼區塊很像,但執行順序不同,Java是先執行程式碼區塊,再執行建構子。

Kotlin是先執行主要建構式 -> 類別屬性 -> init初始化函式 -> 次要建構式。

以下這行不能編譯成功,Property must be initialized or be abstract

屬性必須要初始化。

1
2
3
class Dog {
    val name: String
}

使用init初始化函式,類別中的屬性可以不用給初始值,由init函式初始化屬性。

這一部分跟Java final 匿名區塊初始化的內容一樣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Dog1 {
    val name: String

    init {
    	// 由init函式初始化屬性
        name = "小白"
        loadDB()
    }
    fun loadDB() {
        println("DB loading")
        println("name = $name")
    }
}
fun main() {
    val dog1 = Dog1()
}
DB loading
name = 小白

主要建構式不用有val或var

主要建構式可以不用有val或var,沒有val與var的參數就不是類別屬性,會使用_底線在參數前面,代表只使用一次。

1
2
3
4
5
6
7
8
9
class Cat4 (_name: String, _age: Int) {
    var name:String = _name
    var age:Int = _age
}
fun main() {
    val cat4 = Cat4("喵喵", 2)
    println(cat4.name)
    println(cat4.age)
}
喵喵
2

可以與init搭配

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cat4 (_name: String, _age: Int) {
    var name:String = ""
    var age:Int = 0
    init {
        name = _name
        age = _age
    }
}
fun main() {
    val cat4 = Cat4("喵喵", 2)
    println(cat4.name)
    println(cat4.age)
|
喵喵
2

程式碼執行順序

  1. 主要建構式
  2. 類別屬性
  3. init初始化函式
  4. 次要建構式

使用Decompile證明執行順序

Kotlin程式碼

1
2
3
4
5
6
7
8
9
10
11
12
class Cat4 (val name: String, _age: Int) { // 1. name
    private var age:Int = _age  //2.
    private val hobby:String
    private var sleeptimes: Int = 0
    init {
        println("init block ...")  // 3.
        hobby = "swimming"         // 4.
    }
    constructor(name: String, _age: Int, sleeptimes: Int):this(name, _age) {
        this.sleeptimes = sleeptimes  // 5.
    }
}

Java程式碼,以下已把建構子中的順序與Kotlin對映。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class Cat4 {
   private int age;
   private final String hobby;
   private int sleeptimes;
   @NotNull
   private final String name;

   @NotNull
   public final String getName() {
      return this.name;
   }
   public Cat4(@NotNull String name, int _age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;  // 1.
      this.age = _age;   // 2.
      String var3 = "init block ...";  // 3.
      System.out.println(var3);        // 3.
      this.hobby = "swimming";         // 4.
   }

   public Cat4(@NotNull String name, int _age, int sleeptimes) {
      Intrinsics.checkNotNullParameter(name, "name");
      this(name, _age);  // 先呼叫2個參數的父類建構子
      this.sleeptimes = sleeptimes;  //5.
   }

屬性不能寫在後面

你不能像C++把屬性寫到最後面,會無法讀取到屬性,Kotlin執行順序是由上而下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Dog1 {
    init {
        loadDB()
    }

    fun loadDB() {
        println("DB loading")
        // 無法讀取到屬性
        println("name = $name")
    }

    // 屬性寫到最後面
    val name: String = "小白"
}
DB loading
name = null

results matching ""

    No results matching ""