Kotlin也沒那麼難(二)

本系列第一篇文章我們學習了kotlin的基本概念,本篇文章我們將繼續學習 類、接口、lambda以及可空性。

接口

接口聲明

interface FirstInterface {
    fun function()
}

接口實現

class FirstClass : FirstInterface {
    override fun function() {
    }
}

kotlin相比於java沒有 extendsimplements關鍵字,而是直接使用 : 符號


默認方法

interface FirstInterface {
    fun function()
    fun defaultfunction() = { print("hello world") }
}

open關鍵字

kotlin默認所有類都是final,也就是不能被重寫、繼承
如果想讓類和方法可以被重寫、繼承需要加 open 關鍵字

open class OpenClass : FirstInterface {
    final override fun function() {
        //由override修飾的方法默認是open,如不想被重寫需要顯式加final
    }

    open fun openFun() {
        //該方法可以被重寫
    }
}
abstract class AbsClass {
    abstract fun fun0() //強制子類重寫
    open fun fun1() {} //允許子類重寫
    fun fun2() {} //禁止子類重寫
}

可見修飾符

kotlin較java而言少了 默認(即什麼都不加) 修飾符,多了 internal 修飾符

internal修飾符 修飾的類和類成員模塊類可見,比如對於一個android項目通常有多個模塊,如果a模塊的kotlin類A使用internal修飾,b模塊kotlin類B就引用不到類A。但是這特性對java無效,在B模塊的java類照樣能引用類A


內部類和嵌套類

class Outter {
    //這是靜態類 創建對象:Outter.Static()
    class Static
    //這是內部類 創建對象:Outter().Inner()
    inner class Inner {
        fun getOutterHash() = [email protected]()
    }
}

kotlin由於沒有 static 關鍵字,類中聲明的類默認就是靜態類,聲明內部類時需要使用 inner 關鍵字
同時 內部類引用外部類對象的方式也和java不同,需要使用 this@外部類


密封類

使用 sealed 關鍵字修飾的一個類是密封類
sealed class Student
密封類默認是open,密封類不能被實例化
sealed類通常用在when語句中,如

sealed class Student
class Girl : Student()
class Boy : Student()
fun `when`(student: Student) {
    return when (student) {
    }
}


此時點擊Add remaining branches按鈕就會自動把所有Student的子類情況列舉出來

sealed class Student
class Girl : Student()
class Boy : Student()
fun `when`(student: Student) {
    return when (student) {
        is Girl -> TODO()
        is Boy -> TODO()
    }
}

這裏我們定義了一個when方法,因爲和系統的when關鍵字重名,所以需要使用兩個`符號轉義


構造函數

class People constructor(name: String, val age: Int) {
    var name: String = ""

    init {
        this.name = name
    }
}
class People(var name: String, val age: Int)

要點:

  1. 以上兩段代碼編譯結果一模一樣,類名旁的括號聲明瞭默認構造函數(也叫主構造函數)
  2. 如果在構造函數中聲明參數時有使用val和var則會自動把該參數設置爲成員變量(第二段代碼兩個參數都被編譯器默認設置爲同的名成員變量),如果沒有則只當成一個臨時變量:主構造函數執行完後該變量就訪問不到
  3. init 關鍵字可以引導一個語句塊,該語句塊伴隨主構造函數執行,所以第一段代碼的init語句塊中可以訪問到name這個臨時變量
  4. 一個類可以有多個init語句塊,順序是從上到下依次執行
  5. 如果主構造函數沒有註解和可見性修飾符則可以去掉constructor 關鍵字
  6. 所有聲明的從構造函數最終都必須要執行主構造函數,不管是直接還是間接
open class Button() {
    constructor(x: Float, y: Float) : this() {} //直接調用無參的主構造函數
    constructor(x: Float) : this(x, 0F) {} //通過另一兩個參數的從構造函數間接實現主構造函數
}
  1. 子類定義構造函數時一定要實現父類的構造函數
open class Button
class MyButton1 : Button {
    constructor() : super() //定義構造函數
}
class MyButton2() : Button() //定義構造函數

Data class

DataClass其實只要在定義的class前加一個data關鍵字就好
唯一要求就是主構造函數至少有一個字段

data class MyData(var name:String)

使用DataClass聲明類時編譯器會自動爲該類重寫hashCode()equals()copy()toString()等方法,很方便。


Object class

ObjectClass極大的簡化了單例的聲明

object SingleDb

這裏我們定義了一個SingleDb類,他是一個單例,不能實例化,所以我們也無法指定構造函數
我們可以看看編譯成java文件是什麼樣子

public final class SingleDb {
   public static final SingleDb INSTANCE;

   static {
      SingleDb var0 = new SingleDb();
      INSTANCE = var0;
   }
}

伴生對象(Companion)

kotlin是沒有static關鍵字的,所以沒有靜態變量與靜態方法
但是我們可以使用伴生對象實現

class SingleDb {
    companion object Db {
        var dbName: String = "example"
    }
}

fun test() {
    //兩種寫法都行,但是Java中只能使用第一種,原因看編譯後的java文件就明白了
    print(SingleDb.Db.dbName)
    print(SingleDb.dbName)
}

我們使用companion object + 伴生對象名字 + 語句塊的方式聲明瞭一個伴生對象,其中伴生對象名字可以省略,這樣編譯器會默認賦予一個COMPANION的名字
編譯後java文件

public final class SingleDb {
   private static String dbName = "example";
   public static final SingleDb.Db Db = new SingleDb.Db();

   public static final class Db {
      public final String getDbName() {
         return SingleDb.dbName;
      }

      public final void setDbName(String var1) {
         SingleDb.dbName = var1;
      }

      private Db() {
      }
   }
}



可空類型

kotlin最爲人津津樂道的就是不會產生空指針異常
其實說白了就是編譯期檢查代碼,把可能會出現異常的地方全部編譯失敗,丟給開發一個個去解決
我們可以在任何聲明類型的時候給該類型後加一個?來告訴編譯器這是可空的類型(可以爲null),如果沒加?號表示這是不可空的,那麼如果你想給不可空的對象賦值null是一定編譯不過去的!!!

class People {
    val name: String? = null
    val region: String = "china"
}

比如上面的people類我們就定義name可以爲空,region不能爲空
同樣的,我們可以看看編譯後的java文件

public final class People {
   @Nullable
   private final String name;
   @NotNull
   private final String region = "china";

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

   @NotNull
   public final String getRegion() {
      return this.region;
   }
}

可以看到其實是通過java的@Nullable@NotNull註解實現的

有時候我們明明知道該值此時不爲空,但是由於聲明爲可空類型我們得判空後才能操作,此時可以直接使用!!操作符調用,如 print(people!!name),使用!!就是告訴編譯器不用檢查這裏的可控性,當然如果運行的時候people對象爲空就會直接拋出空指針異常。
所以通常使用了kotlin還是老空指針就是濫用!!操作符的原因了




Kotlin中的Lambda表達式

還剩一些篇章,我們就來了解下kotlin中的lambda表達式吧
lambad是一個很簡單的小語法,這裏貼出一個教程鏈接大家可以自行閱讀
lambda教程
這裏我們主要講lambda在kotlin中的用處

fun sayHello(name: String, onSayHelloFinished: () -> Unit) {
    print("hello $name")
    onSayHelloFinished()
}

以上是一個最最最直觀的例子
Unit其實就類似java中的Void
其中String() -> Unit都是類型,前者是String類型,後者是函數類型!
()->Unit定義了一個函數類型,該類型的對象是一個函數,該函數的參數在括號內(本例無參數),返回值是Unit類型
本例聲明瞭該函數類型的函數對象onSayHelloFinished,該對象的使用很簡單:
onSayHelloFinished()onSayHelloFinished.invoke() ,兩種方式都可以調用
有了函數類型這一語法糖,至少本人自定義view時不用再寫各種接口來提供點擊事件了

僞代碼獻上:

class MyView {
    var clickCallBack: ((MyView) -> Boolean)? = null
    fun setListener(onClick: (MyView) -> Boolean) {
        clickCallBack = onClick
    }

    fun clickTwice() {
        if (clickCallBack != null) {
            clickCallBack.invoke(this)
        }
    }
}



查看編譯後java文件

學習kotlin最好的方法就是一邊寫,一邊想編譯後的class文件是怎樣的
AndroidStudio自帶了一個很好的工具用於查看編譯後的class文件
步驟1:打開一個kt文件,注意焦點要在文件內,也就是文件內要顯示一閃一閃的光標
步驟2:如圖,點擊Show Kotlin Bytecode


步驟3: 點擊Decompile,搞定!




結語

通過本篇文章的學習我們已經算 掌握kotlin 了,基本上加上一篇文章可以應對一般的開發需求,之後會有番外篇講Kotlin的反射、泛型以及委託等。

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