從Kotlin的類開始提及

歡迎來到kotlin的世界,Kotlin 是一個用於現代多平臺應用的靜態編程語言,它能夠編譯成Java字節碼,在JVM平臺上運行,而且能夠徹底兼容Java。它有不少優勢,如:如空指針檢查、高階函數、函數擴展等等。2017Google IO大會上,指定Kotlin做爲Android開發的官方語言。所以,若是你是一個Android開發者,該學習使用kotlin來進行開發了。html

如何開始學習Kotlin呢?在面向對象編程中,咱們說萬物皆對象,任何事物均可以進行抽象和封裝成一個對象來表達它所具備的屬性和特徵。Kotlin做爲一種現代面向對象編程語言,所以咱們就從類和對象開始來認識它,本篇本章就來說講Kotlin中的全部類。 java

image

1 . Kotlin中的類

1.一、Java中的類

在認識Kotlin的類以前,咱們來先看看咱們熟悉的Java類,一段Java代碼以下:git

class Person {
    private String name;
    private int age;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    
    /** * 方法 */ 
    public void walk(){
        System.out.print("person walk...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

複製代碼

Java類特徵以下:編程

  • class關鍵字聲明一個類,形式如:class 類名 {}
  • 類能夠有一個或者多個構造函數,若是沒有顯示的構造函數,會默認有一個無參構造函數
  • 類中能夠聲明屬性和方法,私有屬性用相應的getXXX()setXXX()方法

一塊兒來看一下,和上面功能功能同樣的kotlin類:api

class Person(var name:String,var age:Int){
    /** * 函數 */
    fun walk():Unit{
        println("Person walk...")
    }
}

複製代碼

1.2 . Kotlin中的類

Kotlin的類的聲明形式爲:bash

class 類名 [可見性修飾符] [註解] [constructor] (Params){
     ...
  }  
複製代碼

其中,{}中的 的內容成爲類體,類名與類體之間的內容稱爲類頭,在kotlin中,類頭和類體是能夠省略的,[]中的類容也是可選的。好比一個完整的類聲明如:網絡

class Person private @Inject constructor(name: String) { …… }
複製代碼

若是沒有可見性修飾符和註解,類頭能夠省去,那麼能夠簡寫成以下:編程語言

class Person(name: String) { …… }
複製代碼

若是**構造函數(kotlin的構造函數將在下文講解)**沒有參數,能夠寫成以下:ide

class Person { …… }
複製代碼

若是類體裏面也沒有類容的話,類體也能夠省略,以下:函數

class Person
複製代碼

2 . 構造函數

上面提到了構造函數,熟悉Java的同窗都知道,Java也有構造函數,Java中的構造函數有以下特色:

  • 一個Java類能夠有多個構造函數,構造函數之間是重載的
  • 能夠不給Java類顯示聲明構造函數,可是會默認生成一個無參的構造函數
  • 若是顯示的生成了構造函數,則在new對象的時候,不能使用默認的無參構造函數,除非顯示的生成一個無參構造函數。

如:多個重載的構造函數:

public class Person {
    private String name;
    private int age;

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

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

// 生成對象時
Person person1 = new Person("Paul",30);
Person person2 = new Person("jake");
Person person3 = new Person();//編譯錯誤,沒有無參構造函數
複製代碼

也能夠生明構造函數,可是它會有一個默認的無參構造函數:

public class Person {
    private String name;
    private int age;
}

// 生成對象時
Person person = new Person();//使用默認無參構造函數
複製代碼

回到Kotlin ,在Kotlin中,一個類能夠有一個主構造函數以及一個或多個次構造函數。主構造函數是類頭的一部分:它跟在類名(與可選的類型參數)後。與Java稍有不一樣,Kotlin有主構造函數和次構造函數之分。

2.1 主構造函數

一個帶有主構造函數的類聲明以下:

class Cat constructor(name: String){
    ...
}
複製代碼

若是主構造函數沒有任何註解或者可見性修飾符,能夠省略這個constructor關鍵字。

class Cat(name: String){
    ...
}
複製代碼

注意,Kotlin主構造函數是不能包含任何代碼的,可是有時候咱們又須要在構造函數中作一些初始化的操做,這咋辦呢?Kotlin 引入了初始化代碼塊,用 關鍵字init聲明,須要在主構造函數中初始化的操做能夠放到初始化代碼塊中,如:

class Cat(name: String){
    //初始化代碼塊
    init {
        // 在這裏面作一些須要在主構造函數中作的初始化操做
        println("第一個初始化代碼塊,name:$name")
    }
}
// 使用以下:

fun main(args: Array<String>) {
  var  cat: Cat = Cat("喵喵")
}
複製代碼

執行結果以下:

/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java
 
第一個初始化代碼塊,name:喵喵
Process finished with exit code 0
複製代碼

能夠看到,生成一個Cat對象的時候,執行了初始化代碼塊。有2點值得注意的是:

  • 1,初始化代碼塊和類體均可以訪問主構造函數的參數,如上面的例子,能夠訪問name參數
  • 2,類體中能夠有多個初始化代碼塊,它們的執行順序與在類中聲明的順序同樣

多個初始化代碼塊例子:

class Cat(name: String){
    //類體中也能夠訪問構造函數的參數
    val catName:String = "catName:$name"
    //初始化代碼塊
    init {
        // 在這裏面作一些須要在主構造函數中作的初始化操做
        println("第一個初始化代碼塊,name:$name")
    }

    init {
        println("第二個初始化代碼塊,name:$name")
    }

    init {
        println("第三個初始化代碼塊,name:$name")
    }
}

// 使用以下:
fun main(args: Array<String>) {
  var  cat: Cat = Cat("喵喵")
  println(cat.catName)//打印屬性
}
複製代碼

執行結果以下:

/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java

第一個初始化代碼塊,name:喵喵
第二個初始化代碼塊,name:喵喵
第三個初始化代碼塊,name:喵喵
catName:喵喵

Process finished with exit code 0
複製代碼

上面的代碼中,咱們在類體中聲明瞭一個屬性catName,在Kotlin中聲明屬性其實有更簡單的方法,那就是在主構造函數中聲明類屬性

方式一:

class Person(var name:String ,var gender: Int)
複製代碼

方式二:

class Person{
    var name:String = ""
    var gender:Int = 0
    //這是次構造函數
    constructor(name: String, gender: Int) {
        this.name = name
        this.gender = gender
    }
}
複製代碼

上面2種方式聲明屬性是等價的,聲明瞭2個類屬性namegender,能夠看出,經過主構造函數聲明屬性簡潔了不少。

在Kotlin中,函數的參數是能夠設置默認值的,若是調用的時候不傳對應參數,就使用默認值,主構造函數聲明類屬性也同樣,也能夠設置默認值。 代碼以下:

class Person(var name: String= "" ,var gender: Int= 0)
複製代碼
2.2 次構造函數

Kotlin 中,類也能夠有次構造函數,次構造函數在類體中用關鍵字constructor聲明,代碼以下:

class Person {
    var name: String = ""
    var gender: Int = 0
    //次構造函數
    constructor(name: String, gender: Int){
        this.name = name
        this.gender = gender
    }
}
複製代碼

若是類有一個主構造函數,每一個次構造函數須要委託給主構造函數, 能夠直接委託或者經過別的次構造函數間接委託。委託到同一個類的另外一個構造函數用 this 關鍵字便可:

class Person(name: String){
    var name: String = name // 主構造函數參數賦值
    var gender: Int = 0

    constructor(name: String, gender: Int) : this(name) {
        this.name = name
        this.gender = gender
    }
}
複製代碼

請注意,初始化塊中的代碼實際上會成爲主構造函數的一部分。委託給主構造函數會做爲次構造函數的第一條語句,所以全部初始化塊中的代碼都會在次構造函數體以前執行。即便該類沒有主構造函數,這種委託仍會隱式發生,而且仍會執行初始化塊:

class Person(name: String){
    var name: String = name
    var gender: Int = 0
    
    init {
        println("這是初始化代碼塊...")
    }

    constructor(name: String, gender: Int) : this(name) {
        println("這是次構造函數...")
        this.name = name
        this.gender = gender
    }
}

// 運行程序
fun main(args: Array<String>) {
    var person = Person("Paul",30)
}
複製代碼

打印結果以下:

/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java

這是初始化代碼塊,name:Paul
這是次構造函數,name:Paul,gender:30

Process finished with exit code 0
複製代碼

從結果看,先執行初始化塊,再執行次構造函數。

在Java中,若是沒有聲明任何構造函數,會有一個默認的無參構造函數,這有利於經過這個無參構造函數建立類的對象,可是有些狀況,好比單例模式,不但願外部構造對象,咱們只需私有化一個無參構造函數就行,Java代碼以下:

class Singleton{
        // 私有化構造函數,外部不能直接建立對象
        private Singleton(){}
        
        public static Singleton getInstance(){
            return new Singleton();
        }
    }
複製代碼

在Kotlin中也相似,若是一個非抽象類沒有聲明任何(主或次)構造函數,它會有一個生成的不帶參數的主構造函數。構造函數的可見性是 public。若是你不但願你的類有一個公有構造函數,你須要聲明一個帶有非默承認見性的空的主構造函數:

class DontCreateMe private constructor () { ... }
複製代碼

kotlin構造函數小結:

1, 能夠有一個主構造函數和多個次構造函數 2,能夠只有主構造函數或者只有次構造函數 3,主、次構造函數同時存在的時候,次構造函數必須直接或者間接地委託到主構造函數 4,沒有聲明主構造函數或者次構造函數時,會有一個默認的無參數主構造函數,方便建立對象,這與Java同樣 5,若是不但願類有公有構造函數,那麼請私有化一個無參數主構造函數

3 . 抽象類

和Java同樣,在kotlin中,抽象類用關鍵字abstract修飾,抽象類的成員能夠在本類中提供實現,也能夠不實現而交給子類去實現,不實現的成員必須用關鍵字abstract聲明:

abstract class AbsBase{
    abstract fun method()
}
複製代碼

在kotlin中,被繼承的類須要用關鍵字open聲明,代表該類能夠被繼承,可是抽象類或者抽象函數是不用 open 標註的,由於這不言而喻。可是若是子類要實現抽象類的非抽象函數,須要在抽象類中將其聲明爲open

abstract class AbsBase{
    abstract fun method()
    // 若是子類要實現需聲明爲抽象 
    open fun method1(){
        println("非抽象方法若是要類子類實現,須要聲明爲open")
    }
}

class Child : AbsBase() {

    override fun method() {

    }

    override fun method1() {
        super.method1()
        println("子類實現")
    }

}
複製代碼

另外,抽象成員能夠覆蓋一個非抽象成員:

abstract class AbsBase{ 
   open fun method1(){
        println("非抽象方法若是要類子類實現,須要聲明爲open")
    }
}

abstract class AbsChild :AbsBase(){
    // 將父類的非抽象方法覆蓋爲一個抽象方法
    abstract override fun method1()
}
複製代碼

抽象類小結: 跟Java 的抽象類幾乎同樣,熟悉Java的同窗很容易理解。

4 . 數據類

在Java中,咱們會常常建立一些保存數據的類,xxxModule或者xxxEntry ,主要用在網絡請求中,保存api接口返回的數據,如一個 Java 的User類:

public class User {
    private String name;
    private int gender;
    private String avatar;
    private int age;
    

    public User(String name, int gender, String avatar, int age) {
        this.name = name;
        this.gender = gender;
        this.avatar = avatar;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
複製代碼

一個複雜點的實體類,動不動就幾十幾百行代碼。臃腫並且麻煩。

在Kotlin 中,引入了一種特殊的來來解決這個問題,叫作數據類,用關鍵字data標記,kotlin數據類代碼以下:

data class User(var  name:String,var age:Int,var gender: Int,var avatar: String)
複製代碼

Java 中幾十行的數據類,在kotlin中,一行就搞定。爲何kotlin 能這麼簡單,那是由於編譯器爲咱們作了許多事,編譯器會自動的從主構造函數中根據全部聲明的屬性提取如下函數:

  • equals()/hashCode() ;
  • toString() 格式是 "User(name=John, age=42)";
  • componentN() 函數 按聲明順序對應於全部屬性;
  • copy() 函數

也就是說編譯器自動爲咱們的數據類生成了以上個函數,前面幾個熟悉Java的同窗知道,Java 中也有,再此不表。componentNcopy 函數使數據類有了2個特性:

  • 1 . 解構
  • 二、數據類複製
4.1 . 解構

解構是什麼意思呢?就是把一個對象拆解成對應的多個變量,好比上面的User類 ,咱們能夠把它的4 個屬性拆解出來:

fun main(args: Array<String>) {
    // 建立一個User對象
    var user = User("Paul",30,1,"https:qiniu.com/w200/h200.png")
    // 解構
    val(name,age,gender,avatar) = user
    // 打印
    println("name:$name,age:$age,gender:$gender,avatar:$avatar")
}
複製代碼

打印結果:

name:Paul,age:30,gender:1,avatar:https:qiniu.com/w200/h200.png

Process finished with exit code 0
複製代碼

爲何數據類能夠解構呢?是由於編譯器自動幫數據類,生成了componentN函數, 其中N表明有N個 component函數,這取決於數據類主構造函數聲明屬性的個數,如上面的例子,有4個屬性,那麼就有4個component函數: component1() component2() component3() component4()

4個函數對應4個component函數,按在主構造函數聲明屬性的順序對應。 上面的解構會被編譯成:

val name = user.component1()
val age = user.component2()
val gender = user.component3()
val avatar = user.component4()
複製代碼

Q: 普通的類能夠解構嗎? A: 能夠,爲其聲明component函數

4.2 複製copy

在不少狀況下,咱們須要複製一個對象改變它的一些屬性,但其他部分保持不變。 copy() 函數就是爲此而生成。對於上文的 User 類,其實現會相似下面這樣:

fun copy(name: String = this.name, age: Int = this.age,gender: Int = this.gender,avatar: Int = this.avatar) = User(name, age,gender,avatar)
複製代碼

好比咱們複製了一個User類:

var user = User("Paul",30,1,"https:qiniu.com/w200/h200.png")

    // 想再建立一個對象只改變年齡
    var user2 = user.copy(age = 31)
    // 改變名字和年齡,其餘不變
    var user3 = user.copy(name = "Dw",age = 33)
    
    println(user.toString())
    println(user2.toString())
    println(user3.toString())
複製代碼

運行結果以下:

1535613834079

注意:數據類也能夠在類體中聲明屬性,可是在類體中聲明的屬性不會出如今那些自動生成的函數中,如:

data class User(val name: String) {
    var age: Int = 0
}
複製代碼

由於主構造函數只有name,所以在 toString()、 equals()、 hashCode() 以及 copy() 的實現中只會用到 name 屬性,只有一個component1函數,對應name。若是你建立2個對象,名字相同,年齡不一樣,可是會被視爲相等。user1 == user2

數據類小結 數據類需知足如下要求: 一、主構造函數須要至少有一個參數; 二、主構造函數的全部參數須要標記爲 val 或 var; 三、數據類不能是抽象、開放、密封或者內部的;

5 . 枚舉類

kotlin 中的枚舉類與Java 中的枚舉類差很少,簡單的說一下:

一、枚舉用關鍵字enum聲明,與Java不一樣,緊跟後面是class (Java聲明枚舉沒有class關鍵字)枚舉類的聲明形式以下:

enum class 類名{
    常量1,
    常量2,
    ...
  }
複製代碼

如:

enum class Direction{
    WEST,
    EAST,
    NORTH,
    SOUTH;
}
複製代碼

二、枚舉類默認有2個屬性ordinalname:

  • ordinal 屬性:枚舉常量的順序,從0開始
  • name屬性: 枚舉常量的名字 以上面的枚舉類Direction爲例:
Direction.WEST.ordinal // 0
 Direction.WEST.name // WEST
複製代碼

三、枚舉類默認又2個方法:values()valueOf()

  • values : 獲取全部枚舉常量
  • valueOf() : 獲取對應枚舉常量
// 遍歷
    Direction.values().forEach {
        println("value:${it.ordinal}")
    }
    // 獲取"EAST"對應枚舉常量,若是枚舉類中沒這個常量會拋異常
    val direction = Direction.valueOf("EAST")
複製代碼

四、枚舉常量能夠有構造函數和自有屬性、方法,自定義方法需放在;後,每個枚舉常量都是一個實例,調用構造函數初始化。

enum class Season(var enumName: String,var range: String){
    Spring("春季","1-3"),
    Summer("夏季","4-6"),
    Fall("秋季","7-9"),
    Winter("冬季","10-12");
    
    fun printSeason(){
        print("name:$enumName,range:$range")
    }
}
複製代碼

6. 密閉類

密閉類定義以下:密閉類用來表示受限的類繼承結構:當一個值爲有限集中的類型、而不能有任何其餘類型時。在某種意義上,他們是枚舉類的擴展:枚舉類型的值集合也是受限的,但每一個枚舉常量只存在一個實例,而密閉類的一個子類能夠有可包含狀態的多個實例。

這麼長一串定義,看得一臉懵逼,不要緊,稍候解釋。先來看一下如何聲明一個密閉類

密閉類用 sealed 修飾符 ,密閉類的字類必須與密閉類在同一文件中(子類也能夠嵌套在密閉類的內部)

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
複製代碼

子類在內部:

sealed class Expr{
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
}
複製代碼

有幾點須要注意:

  • 一、一個密閉類是自身抽象的,它不能直接實例化並能夠有抽象(abstract)成員

  • 二、密閉類不容許有非-private 構造函數(其構造函數默認爲 private)

  • 三、擴展密閉類子類的類(間接繼承者)能夠放在任何位置,而無需在同一個文件中。

密閉類算是枚舉類的擴展,用法和枚舉類類似,常常配合when表達式使用,使用例子以下:

fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
    Expr.NotANumber -> Double.NaN
    // 再也不須要 `else` 子句,由於咱們已經覆蓋了全部的狀況
}

fun main(args: Array<String>) {
    val const = eval(Expr.Const(12.0))
    val sum = eval(Expr.Sum(Expr.Const(10.0),Expr.Const(12.0)))

    println("const:$const")
    println("sum:$sum")
}   

// 執行結果:
const:12.0
sum:22.0 
複製代碼

做爲初學者,密閉類是Kotlin 中比較難以理解的一個類,其實看完上面的例子仍是很難理解它到底能幹嗎,感受它作的事兒,枚舉類也能作到,前面說它算是對枚舉類的擴展,那麼他就應該能作到枚舉類作不到的事。網上看到一篇博客用View 顯示和隱藏來舉例,頓時茅舍頓開。以下:

場景: 假如在 Android 中咱們有一個 view,咱們如今想經過 when 語句設置針對 view 進行兩種操做:顯示和隱藏,那麼就能夠這樣作:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
} 
//定義了一個操做View的方法
fun viewOperator(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE 
    UiOp.Hide -> view.visibility = View.GONE
}
複製代碼

以上功能其實徹底能夠用枚舉實現,可是若是咱們如今想加兩個操做:水平平移和縱向平移,而且還要攜帶一些數據,好比平移了多少距離,平移過程的動畫類型等數據,用枚舉顯然就不太好辦了,這時密封類的優點就能夠發揮了,如今密閉類中添加2個操做:

sealed class UiOp {
    object Show: UiOp()
    object Hide: UiOp()
    class TranslateX(val px: Float): UiOp() // 水平移動
    class TranslateY(val px: Float): UiOp()//垂直移動
}
複製代碼

接着在when表達式添加兩個移動的case

fun execute(view: View, op: UiOp) = when (op) {
    UiOp.Show -> view.visibility = View.VISIBLE
    UiOp.Hide -> view.visibility = View.GONE
    is UiOp.TranslateX -> view.translationX = op.px // 這個 when 語句分支不只告訴 view 要水平移動,還告訴 view 須要移動多少距離,這是枚舉等 Java 傳統思想不容易實現的
    is UiOp.TranslateY -> view.translationY = op.px
}
複製代碼

以上代碼中,TranslateX 是一個類,它能夠攜帶多於一個的信息,好比除了告訴 view 須要水平平移以外,還能夠告訴 view 平移多少像素,甚至還能夠告訴 view 平移的動畫類型等信息,這大概就是密封類出現的意義吧。

看到這個場景演示後,是否是就以爲撥開雲霧見月明瞭呢?好理解多了吧!

7. 嵌套類

一個類能夠嵌套在另外一個類裏面

class Outer{
    val attr = 0
    // 嵌套類
    class Nested{
        fun inMethod(){
            // 不能訪問外部類的屬性
            println("內部類")
        }
    }
}

fun main(args: Array<String>) {
    // 調用嵌套類方法
    Outer.Nested().inMethod()
}
複製代碼

嵌套類不能訪問外部類的屬性,它其實就至關於Java 中的靜態內部類,咱們把它翻譯成Java 代碼,Nested其實就是一個靜態內部類,以下:

public final class Outer {
   private final int attr;
   public static final class Nested {
      public final void inMethod() {
         String var1 = "內部類";
         System.out.println(var1);
      }
   }
}
複製代碼
7.2 內部類

Kotlin中,內部類用inner關鍵字,聲明,跟Java 同樣,內部類持有一個外部類的對象引用,能夠訪問外部類的屬性和方法。

class Outer{
    private val attr = 10
    inner class Inner{
        fun method(){
            println("內部類能夠訪問外部類屬性:$attr")
        }
    }
}

fun main(args: Array<String>) {
    // 調用內部類方法,看出區別了嗎
    Outer().Inner().method()
}
複製代碼

注意調用方法,嵌套類經過類直接調用(Java靜態方法方式),內部類經過對象調用。

7.3 匿名內部類

來看一下Java的匿名內部類:

mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // onClick
            }
        });
複製代碼

在kotlin中,匿名內部類用對象表達式建立,給View設置點擊事件的kotlin代碼以下:

mButton?.setOnClickListener(object: View.OnClickListener{

            override fun onClick(v: View?) {
                //onClick
            }
        })
複製代碼

8 . 總結

八月份初的時候就在寫着篇文章,前先後後差很少1個月左右,這篇文章終於寫完了,本篇文章看完算是對Kotlin 中的類能有一個完整了解,因爲涉及的內容比較多,篇幅太長,關於類的繼承、對象和對象表達式、屬性和方法 這些另開篇幅吧。Kotlin 的的一些中文官方文檔翻譯得比較生硬,有的很差理解,本文有些知識點我嘗試經過Java 代碼對比的方式講解,但願能好理解一點。若是有什麼錯誤的地方,歡迎指出。

參考:

Kotlin 語言官方參考文檔

Kotlin 數據類與密封類

更多Android乾貨文章,關注公衆號 【Android技術雜貨鋪】

相關文章
相關標籤/搜索