Kotlin也沒那麼難(二)
本系列第一篇文章我們學習了kotlin的基本概念,本篇文章我們將繼續學習 類、介面、lambda以及可空性。
介面
介面宣告
interface FirstInterface { fun function() }
介面實現
class FirstClass : FirstInterface { override fun function() { } }
kotlin相比於java沒有 extends 和 implements 關鍵字,而是直接使用 : 符號
預設方法
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)
要點:
- 以上兩段程式碼編譯結果一模一樣,類名旁的括號聲明瞭預設建構函式(也叫主建構函式)
- 如果在建構函式中宣告引數時有使用val和var則會自動把該引數設定為成員變數(第二段程式碼兩個引數都被編譯器預設設定為同的名成員變數),如果沒有則只當成一個臨時變數:主建構函式執行完後該變數就訪問不到
- init 關鍵字可以引導一個語句塊,該語句塊伴隨主建構函式執行,所以第一段程式碼的init語句塊中可以訪問到name這個臨時變數
- 一個類可以有多個init語句塊,順序是從上到下依次執行
- 如果主建構函式沒有註解和可見性修飾符則可以去掉 constructor 關鍵字
- 所有宣告的從建構函式最終都必須要執行主建構函式,不管是直接還是間接
open class Button() { constructor(x: Float, y: Float) : this() {} //直接呼叫無參的主建構函式 constructor(x: Float) : this(x, 0F) {} //通過另一兩個引數的從建構函式間接實現主建構函式 }
- 子類定義建構函式時一定要實現父類的建構函式
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 + 伴生物件名字 + 語句塊
的方式聲明瞭一個伴生物件,其中伴生物件名字可以省略,這樣編譯器會預設賦予一個 COMPAIO/">NION
的名字
編譯後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是一個很簡單的小語法,這裡貼出一個教程連結大家可以自行閱讀
ofollow,noindex">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的反射、泛型以及委託等。