Kotlin學習筆記(5)- 類

weixin_33670713發表於2017-05-27

系列文章全部為本人的學習筆記,若有任何不妥之處,隨時歡迎拍磚指正。如果你覺得我的文章對你有用,歡迎關注我,我們一起學習進步!
Kotlin學習筆記(1)- 環境配置
Kotlin學習筆記(2)- 空安全
Kotlin學習筆記(3)- 語法
Kotlin學習筆記(4)- 流程控制
Kotlin學習筆記(5)- 類
Kotlin學習筆記(6)- 屬性
Kotlin學習筆記(7)- 介面
Kotlin學習筆記(8)- 擴充套件
Kotlin學習筆記(8)- 擴充套件(續)
Kotlin學習筆記(9)- 資料類
Kotlin學習筆記(10)- 泛型
Kotlin學習筆記(11)- 內部類和巢狀類
Kotlin學習筆記(12)- 委託
Kotlin學習筆記(13)- 函數語言程式設計
Kotlin學習筆記(14)- lambda

一、類

1.類修飾符

修飾符分為屬性修飾符和訪問許可權修飾符。

// 屬性修飾符
annotation  //註解類
abstract   //抽象類
final     //類不可繼承,預設屬性
enum     //列舉類
open    //類可繼承,類預設是final的

// 訪問許可權修飾符
private   //僅在同一個檔案中可見
protected //同一個檔案中或子類可見
public    //所有呼叫的地方都可見
internal  //同一個模組中可見
2.類定義

kotlin預設唯一構造器,暨類定義同時也是構造器。

class Person(name : String, age : int) {
} 

翻譯成java為:

final public class Person {
    public Person(String name, int age){
    }
}
3.建構函式

仔細看會發現,翻譯成的java類定義前面有final修飾符,因為在kotlin中類預設為final的,也就是不可繼承的。如果要繼承,需要宣告為open,或者abstract

class Person(name : String, age : int)

如果連引數也沒有,也可以這麼寫

class Person
4.類初始化

因為kotlin中的類定義同時也是建構函式,這個時候是不能進行操作的,所以kotlin增加了一個新的關鍵字init用來處理類的初始化問題,init模組中的內容可以直接使用建構函式的引數。

class Person(name : String, age : int){
    init{
        // to do something
    }
}
5.次級建構函式

我們知道,java中可以有多個不同型別、不同個數的同名建構函式,以此滿足我們的各種需求。而上面我們說過,kotlin中的類定義同時也是建構函式,kotlin中也預設只有這一個建構函式,那我們怎麼辦呢?很常見的一個問題是,View的自定義。根據android系統版本不同,View有3到4個建構函式,如果只用kotlin中的預設建構函式,我們確實可以在普通程式碼中正常使用,那麼如何在xml中進行定義呢?舊版的kotlin沒有處理這個問題,我們只能在java中自定義View,然後再進行呼叫。而在新版的kotlin已經解決了這個問題,那就是次級建構函式constructor。

次級建構函式用constructor加上引數,後面用this加上主建構函式的引數。同時次級建構函式中可以直接進行程式碼操作,所以沒有init模組

class ParentClass(name: String) {
    var a = 1
    init {
        log("This is --> primary constructor, a=$a, name=$name")
    }

    constructor(name : String, age : Int) : this(name) {
        log("This is --> secondry constructor, a=${++a}, age=$age")
    }
}

// 翻譯成java

final class ParentClass{
    int a = 1;
    public ParentClass(String name){
        log("This is --> primary constructor, a=$a, name=$name")
    }
    
    public ParentClass(String name, int age){
        this(name);
        log("This is --> secondry constructor, a=${++a}, age=$age")
    }
}

// 也就是說,如果新建一個類ParentClass("name", age),那麼就會進行兩次log輸出
6.引數預設值

kotlin中支援類定義的時候,引數可以設定預設值。

// 含有預設值的引數,可以不傳,也就是說ParentClass("xiaoming", 22, 1)和ParentClass("xiaoming", 22)這兩種呼叫方式都是可以的
constructor(name : String, age : Int, sex : Int = 0) : this(name) {
    log("This is --> secondry constructor, a=${++a}, age=$age, sex=$sex")
}

如果建構函式中的所有引數都有預設值,則kotlin會多建立一個無引數的建構函式。

constructor(name : String = "", age : Int = 12, sex : Int = 0) : this(name) {
    log("This is --> secondry constructor, a=${++a}, age=$age, sex=$sex")
}

如果有多個建構函式,且某個或某幾個建構函式中有預設值,則kotlin會選擇最符合條件的一個作為建構函式。在下面的示例中,第三個建構函式的第三個引數是有預設值的,可以不傳,這就和第二個建構函式有了相同的假象。kotlin的規則是,選擇最符合條件的一個,也就是如果你這樣建立:ParentClass("xiaoming", 22),kotlin會先去找只有這兩引數型別的建構函式,也就是第二種。

class ParentClass(name: String) {
    var a = 1
    init {
        log("This is --> primary constructor, a=$a, name=$name")
    }

    constructor(name : String, age : Int) : this(name) {
        log("This is --> secondry constructor, a=${++a}, age=$age")
    }

    constructor(name : String, age : Int, sex : Int = 0) : this(name) {
        log("This is --> secondry constructor, a=${++a}, age=$age, sex=$sex")
    }
}

二、繼承

1.繼承的基本使用

在kotlin中,類預設是final型別的,也就是不可繼承的。如果需要繼承,需要顯式的將類宣告為open。我覺得這其實也表現出kotlin不推薦使用繼承的一種態度,畢竟在物件導向設計中,是推薦多用組合、少用繼承的。kotlin使用冒號:代替java中的extend來表示繼承關係,如果父類的建構函式有引數,則在繼承時也要註明。

open class ParentClass(name: String) {
    var a = 1
    init {
        log("This is --> primary constructor, a=$a, name=$name")
    }
}
class Child(name: String, age : Int) : ParentClass(name){
}
2.多建構函式的繼承

次級建構函式初始化使用super關鍵字,類似於java中的super

class Child2 : ParentClass {
    constructor(name : String) : super(name){
        log("Child2 age : $name")
    }
    // 可以對引數進行擴充套件
    constructor(name: String, age: Int) : super(name){
    }
}
3.方法過載

同樣的,秉承著多用組合、少用繼承的原則,類中的方法預設也是final的,父類如果想要某個方法可以被子類過載,那麼這個方法也要顯式的被宣告為open;而子類如果要過載父類的方法,則該方法必須使用override關鍵字。

open class ParentClass(name: String) {
    open fun publicMethod(){
        log("I am public")
    }
}
class Child(name: String, age : Int) : ParentClass(name){
    override fun publicMethod() {
        super.publicMethod()
    }
}

成員標記為override的本身是開放的,也就是說,它可以在子類中重寫。如果你想禁止重寫的,使用final關鍵字。

class Child(name: String, age : Int) : ParentClass(name){
    final override fun publicMethod() {
        super.publicMethod()
    }
}
4.同名方法

我們可能還會遇到一種問題:類實現了多個介面,其中某些介面中恰好有相同的方法。kotlin中使用尖括號對父類進行標記,以示區分。

open class ParentClass(name: String) {
    open fun publicMethod(){
        log("I am public from ParentClass")
    }
}
interface ParentInterface(name: String) {
    fun publicMethod(){
        log("I am public from ParentInterface")
    }
}
class Child(name: String, age : Int) : ParentClass(name),ParentInterface{
    override fun publicMethod() {
        super<ParentClass>.publicMethod()
        super<ParentInterface>.publicMethod()
    }
}

三、抽象

和java一樣,抽象也使用abstract關鍵字。抽象類中可以沒有抽象方法,但抽象方法一定在抽象類中。那麼既然是抽象的,那麼肯定是需要繼承的,所以abstract自動包含open屬性,這也是不言而喻的。

abstract class ParentClass(name: String) {
    abstract fun abstractMethod()
}
class Child(name: String, age : Int) : ParentClass(name){
    override fun abstractMethod() {
        //To implemented it
    }
}

其實抽象用到的也是繼承,但是為什麼沒有放到第二部分裡呢?這裡有一點個人的理解。我覺得抽象和單純的繼承在使用目的上是有本質區別的。抽象是為了封裝變化,將不同的實現交給不同的子類去完成。而單純的繼承和方法過載反而破壞了封裝的美感,既然需要通過過載父類方法來實現目的,那很可能你在設計父類的時候就已經做出了不恰當的決定。

相關文章