本系列第一篇文章我們學習了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 + 伴生對象名字 + 語句塊
的方式聲明瞭一個伴生對象,其中伴生對象名字可以省略,這樣編譯器會默認賦予一個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的反射、泛型以及委託等。