面向對象:類和構造函數

Kotlin 作爲類 Java 語言,在面向對象上具有與 Java 相似的特性,但是針對不同的情況進行了不同的優化,今天我們簡單介紹一下 Kotlin 中的類和構造函數。

1. 定義類和創建類的實例

Kotlin 中定義類與 Java 相同,使用 class 關鍵字:

class 類名[主構造函數][{
  //類成員
}]

Kotlin 中的類名與 Java 相同,採用駱峯式命名法,首字母大寫。

不同的是,Kotlin 中的類如果是空類,沒有任何語句,則可以省略大括號(閒的)。

要創建一個類的實例,只需要調用類的構造函數,不使用 new 關鍵字

val s = StringBuilder("Hello World")val list: List<String> = ArrayList()


2. 主構造函數

Kotlin 類的構造函數與 Java 有較大區別。首先,Kotlin 把構造函數分爲 主構造函數 和 次構造函數,主構造函數寫在類頭中,有且只有一個;次構造函數寫在類語句中,可以有多個,也可以沒有。

首先,我們看一個簡單的 Java 類:

public class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }}

這個類定義一個 String 類型的成員變量 name,然後定義了帶有一個 String 類型參數的構造函數,這個構造函數把參數列表中的 name 賦給了成員變量 name。

用 Kotlin,我們可以這樣寫:

class Person constructor(name: String) {
    val name: String
    init {
        this.name = name
    }}

首先,我們使用 class 定義了一個類 Person,然後在類頭用 constructor 關鍵字定義帶有一個 String 類型參數的主構造函數。在類體裏,我們定義了一個不可變的 String 類型成員變量 name,然後使用 init 關鍵字定義主構造函數的行爲,把主構造函數的 name 參數賦給成員變量 name。

能看到,Kotlin 類的主構造函數,參數列表定義在類聲明的部分,函數體卻在 init 代碼塊裏。(莫名其妙)

這樣寫不夠簡潔呀!實際上,充分利用 Kotlin 提供的特性,我們可以把這個類縮短到一行:

class Person(val name: String)

看吧,所有冗餘信息都已除去,只保留了最關鍵的部分。想理解這句話,我們需要知道主構造函數定義中的兩個細節:

  1. 如果主構造函數沒有任何修飾符,則可以去掉 constructor 關鍵字。這樣,我們的類定義就很像一個函數了:

    class Person(name: String) {/*……*/}

    如果想使用在主構造函數前使用修飾符,那麼這個 constructor 就不能省了:

    class Person private constructor() {/*……*/}
  2. 如果主構造函數中定義的參數使用 val 或者 var 修飾,則會創建與這個參數同名的成員變量,並使用傳入的參數值初始化這個成員變量。簡單來說,就是把“定義成員變量”和“使用構造函數傳入的參數初始化成員變量”簡化爲一個 val 或者 var 關鍵字。

    上面的例子中,我們在主構造方法裏聲明 val name: String,Kotlin 就會自動爲我們添加一個名爲 name 的不可變的 String 類型成員變量,然後用主構造函數傳入的值初始化這個成員變量,可以直接調用。

3. 次構造函數

Java 中,一個類往往有多個不同的構造函數,它們一般有下面兩種關係:

  1. 參數列表由少到多,參數列表少的構造函數使用 默認值 調用參數列表多的構造函數。對於這個常見的類型,Kotlin 中使用 函數默認參數 的方法簡化代碼;

  2. 不同的構造方法使用不同的參數列表,相互之間存在調用關係。Kotlin 中使用 次構造函數委託 的解決方法。

首先看第一種情況,這裏我們給用 Java 寫的 Person 類添加一些東西:

public class Person {
  long id;
  String name = "";
  int age = 0;

  public Person(long id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
  }
  public Person(long id, String name) {
    this.id = id;
    this.name = name;
  }
  public Person(long id, int age) {
    this.id = id;
    this.age = age;
  }
  public Person(long id) {
    this.id = id;
  }}

我們添加了兩個屬性,一個 long 類型的 id,一個 int 類型的 age。三個構造函數都需要傳入 id 變量,name 和 age 變量的要求則是 如果沒有外部傳入的參數,就使用默認值

使用 Kotlin,我們可以大大簡化這一長串代碼:

class Person(val id: Long, val name: String = "", val age: Int = 0)

好吧,Kotlin 又是隻用一行就解決了問題……

我們重點看一下參數列表:

  • 參數列表中定義了三個參數,都使用 val 關鍵字修飾,說明要使用它們創建成員變量並初始化;

  • name 和 age 參數後面都使用 = 默認值 的方法聲明瞭它們的默認值,在調用主構造函數的時候,如果不傳入參數,就使用指定的默認值。

對於構造函數的第二種類型:使用沒有關係的參數列表,但存在調用關係,Kotlin 中的處理方式是使用次構造函數委託主構造函數。我們接着改一下 Person 類:

public class Person {
  long id;
  String name = "";

  public Person(long id) {
    this.id = id;
  }
  public Person(String name) {
    this(name.hashCode());
    this.name = name;
  }}

使用 Kotlin,我們可以這樣寫:

class Person(val id: Long) {
  var name: String = ""

  constructor(name: String) : this(name.hashCode().toLong()) {
    this.name = name
  }}

首先,我們在主構造函數裏聲明瞭並初始化了成員變量 id,然後在類體內使用 constructor 關鍵字定義一個帶有一個 String 類型參數的 次構造方法,這個次構造方法先調用主構造函數,然後將參數賦給成員變量。這裏有幾個需要注意的地方:

  1. 如果類已經有了一個主構造函數,那麼所有的次構造函數都要直接或間接地委託給主構造函數。也可以先委託給其他的次構造函數,再由它們委託給主構造函數。所有的次構造函數都會先調用主構造函數,再執行自己特有的代碼。寫法:

    constructor([參數列表]): this([參數列表]) {/*……*/}
  2. 次構造函數不能在參數列表中聲明並初始化成員變量,這也是上面“name: String”前面爲什麼沒有 val 的原因。而且因爲次構造函數會改動 name 的值,所以 name 必須聲明爲 var。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章