本系列第一篇文章我們學(xué)習(xí)了kotlin的基本概念撞鹉,本篇文章我們將繼續(xù)學(xué)習(xí) 類鸠信、接口讥此、lambda以及可空性绸狐。
接口
接口聲明
interface FirstInterface {
fun function()
}
接口實(shí)現(xiàn)
class FirstClass : FirstInterface {
override fun function() {
}
}
kotlin相比于java沒有 extends 和 implements關(guān)鍵字,而是直接使用 : 符號(hào)
默認(rèn)方法
interface FirstInterface {
fun function()
fun defaultfunction() = { print("hello world") }
}
open關(guān)鍵字
kotlin默認(rèn)所有類都是final鳞滨,也就是不能被重寫洞焙、繼承
如果想讓類和方法可以被重寫、繼承需要加 open 關(guān)鍵字
open class OpenClass : FirstInterface {
final override fun function() {
//由override修飾的方法默認(rèn)是open拯啦,如不想被重寫需要顯式加final
}
open fun openFun() {
//該方法可以被重寫
}
}
abstract class AbsClass {
abstract fun fun0() //強(qiáng)制子類重寫
open fun fun1() {} //允許子類重寫
fun fun2() {} //禁止子類重寫
}
可見修飾符
kotlin較java而言少了 默認(rèn)(即什么都不加) 修飾符澡匪,多了 internal 修飾符
internal修飾符 修飾的類和類成員模塊類可見,比如對(duì)于一個(gè)android項(xiàng)目通常有多個(gè)模塊褒链,如果a模塊的kotlin類A使用internal修飾唁情,b模塊kotlin類B就引用不到類A。但是這特性對(duì)java無效甫匹,在B模塊的java類照樣能引用類A
類
內(nèi)部類和嵌套類
class Outter {
//這是靜態(tài)類 創(chuàng)建對(duì)象:Outter.Static()
class Static
//這是內(nèi)部類 創(chuàng)建對(duì)象:Outter().Inner()
inner class Inner {
fun getOutterHash() = this@Outter.hashCode()
}
}
kotlin由于沒有 static 關(guān)鍵字甸鸟,類中聲明的類默認(rèn)就是靜態(tài)類,聲明內(nèi)部類時(shí)需要使用 inner 關(guān)鍵字
同時(shí) 內(nèi)部類引用外部類對(duì)象的方式也和java不同兵迅,需要使用 this@外部類
密封類
使用 sealed 關(guān)鍵字修飾的一個(gè)類是密封類
sealed class Student
密封類默認(rèn)是open抢韭,密封類不能被實(shí)例化
sealed類通常用在when語句中,如
sealed class Student
class Girl : Student()
class Boy : Student()
fun `when`(student: Student) {
return when (student) {
}
}
此時(shí)點(diǎn)擊
Add remaining branches
按鈕就會(huì)自動(dòng)把所有Student的子類情況列舉出來
sealed class Student
class Girl : Student()
class Boy : Student()
fun `when`(student: Student) {
return when (student) {
is Girl -> TODO()
is Boy -> TODO()
}
}
這里我們定義了一個(gè)when方法恍箭,因?yàn)楹拖到y(tǒng)的when關(guān)鍵字重名刻恭,所以需要使用兩個(gè)`符號(hào)轉(zhuǎn)義
構(gòu)造函數(shù)
class People constructor(name: String, val age: Int) {
var name: String = ""
init {
this.name = name
}
}
class People(var name: String, val age: Int)
要點(diǎn):
- 以上兩段代碼編譯結(jié)果一模一樣,類名旁的括號(hào)聲明了默認(rèn)構(gòu)造函數(shù)(也叫主構(gòu)造函數(shù))
- 如果在構(gòu)造函數(shù)中聲明參數(shù)時(shí)有使用val和var則會(huì)自動(dòng)把該參數(shù)設(shè)置為成員變量(第二段代碼兩個(gè)參數(shù)都被編譯器默認(rèn)設(shè)置為同的名成員變量)扯夭,如果沒有則只當(dāng)成一個(gè)臨時(shí)變量:主構(gòu)造函數(shù)執(zhí)行完后該變量就訪問不到
- init 關(guān)鍵字可以引導(dǎo)一個(gè)語句塊鳍贾,該語句塊伴隨主構(gòu)造函數(shù)執(zhí)行,所以第一段代碼的init語句塊中可以訪問到name這個(gè)臨時(shí)變量
- 一個(gè)類可以有多個(gè)init語句塊交洗,順序是從上到下依次執(zhí)行
- 如果主構(gòu)造函數(shù)沒有注解和可見性修飾符則可以去掉constructor 關(guān)鍵字
- 所有聲明的從構(gòu)造函數(shù)最終都必須要執(zhí)行主構(gòu)造函數(shù)贾漏,不管是直接還是間接
open class Button() {
constructor(x: Float, y: Float) : this() {} //直接調(diào)用無參的主構(gòu)造函數(shù)
constructor(x: Float) : this(x, 0F) {} //通過另一兩個(gè)參數(shù)的從構(gòu)造函數(shù)間接實(shí)現(xiàn)主構(gòu)造函數(shù)
}
- 子類定義構(gòu)造函數(shù)時(shí)一定要實(shí)現(xiàn)父類的構(gòu)造函數(shù)
open class Button
class MyButton1 : Button {
constructor() : super() //定義構(gòu)造函數(shù)
}
class MyButton2() : Button() //定義構(gòu)造函數(shù)
Data class
DataClass其實(shí)只要在定義的class前加一個(gè)data關(guān)鍵字就好
唯一要求就是主構(gòu)造函數(shù)至少有一個(gè)字段
data class MyData(var name:String)
使用DataClass聲明類時(shí)編譯器會(huì)自動(dòng)為該類重寫hashCode()
、equals()
藕筋、copy()
和toString()
等方法纵散,很方便梳码。
Object class
ObjectClass極大的簡化了單例的聲明
object SingleDb
這里我們定義了一個(gè)SingleDb類,他是一個(gè)單例伍掀,不能實(shí)例化掰茶,所以我們也無法指定構(gòu)造函數(shù)
我們可以看看編譯成java文件是什么樣子
public final class SingleDb {
public static final SingleDb INSTANCE;
static {
SingleDb var0 = new SingleDb();
INSTANCE = var0;
}
}
伴生對(duì)象(Companion)
kotlin是沒有static
關(guān)鍵字的,所以沒有靜態(tài)變量與靜態(tài)方法
但是我們可以使用伴生對(duì)象實(shí)現(xiàn)
class SingleDb {
companion object Db {
var dbName: String = "example"
}
}
fun test() {
//兩種寫法都行蜜笤,但是Java中只能使用第一種濒蒋,原因看編譯后的java文件就明白了
print(SingleDb.Db.dbName)
print(SingleDb.dbName)
}
我們使用companion object + 伴生對(duì)象名字 + 語句塊
的方式聲明了一個(gè)伴生對(duì)象,其中伴生對(duì)象名字可以省略把兔,這樣編譯器會(huì)默認(rèn)賦予一個(gè)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最為人津津樂道的就是不會(huì)產(chǎn)生空指針異常
其實(shí)說白了就是編譯期檢查代碼沪伙,把可能會(huì)出現(xiàn)異常的地方全部編譯失敗,丟給開發(fā)一個(gè)個(gè)去解決
我們可以在任何聲明類型的時(shí)候給該類型后加一個(gè)?
來告訴編譯器這是可空的類型(可以為null)县好,如果沒加?
號(hào)表示這是不可空的围橡,那么如果你想給不可空的對(duì)象賦值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;
}
}
可以看到其實(shí)是通過java的@Nullable
和@NotNull
注解實(shí)現(xiàn)的
有時(shí)候我們明明知道該值此時(shí)不為空翁授,但是由于聲明為可空類型我們得判空后才能操作,此時(shí)可以直接使用!!
操作符調(diào)用晾咪,如 print(people!!name)
收擦,使用!!
就是告訴編譯器不用檢查這里的可控性,當(dāng)然如果運(yùn)行的時(shí)候people對(duì)象為空就會(huì)直接拋出空指針異常谍倦。
所以通常使用了kotlin還是老空指針就是濫用!!
操作符的原因了
Kotlin中的Lambda表達(dá)式
還剩一些篇章塞赂,我們就來了解下kotlin中的lambda表達(dá)式吧
lambad是一個(gè)很簡單的小語法,這里貼出一個(gè)教程鏈接大家可以自行閱讀
lambda教程
這里我們主要講lambda在kotlin中的用處
fun sayHello(name: String, onSayHelloFinished: () -> Unit) {
print("hello $name")
onSayHelloFinished()
}
以上是一個(gè)最最最直觀的例子
Unit
其實(shí)就類似java中的Void
其中String
和() -> Unit
都是類型昼蛀,前者是String類型宴猾,后者是函數(shù)類型!
()->Unit
定義了一個(gè)函數(shù)類型曹洽,該類型的對(duì)象是一個(gè)函數(shù)鳍置,該函數(shù)的參數(shù)在括號(hào)內(nèi)(本例無參數(shù))辽剧,返回值是Unit類型
本例聲明了該函數(shù)類型的函數(shù)對(duì)象onSayHelloFinished送淆,該對(duì)象的使用很簡單:
onSayHelloFinished()
或onSayHelloFinished.invoke()
,兩種方式都可以調(diào)用
有了函數(shù)類型這一語法糖怕轿,至少本人自定義view時(shí)不用再寫各種接口來提供點(diǎn)擊事件了
偽代碼獻(xiàn)上:
class MyView {
var clickCallBack: ((MyView) -> Boolean)? = null
fun setListener(onClick: (MyView) -> Boolean) {
clickCallBack = onClick
}
fun clickTwice() {
if (clickCallBack != null) {
clickCallBack.invoke(this)
}
}
}
查看編譯后java文件
學(xué)習(xí)kotlin最好的方法就是一邊寫偷崩,一邊想編譯后的class文件是怎樣的
AndroidStudio自帶了一個(gè)很好的工具用于查看編譯后的class文件
步驟1:打開一個(gè)kt文件,注意焦點(diǎn)要在文件內(nèi)撞羽,也就是文件內(nèi)要顯示一閃一閃的光標(biāo)
步驟2:如圖阐斜,點(diǎn)擊Show Kotlin Bytecode
步驟3: 點(diǎn)擊
Decompile
,搞定诀紊!結(jié)語
通過本篇文章的學(xué)習(xí)我們已經(jīng)算 掌握kotlin 了谒出,基本上加上一篇文章可以應(yīng)對(duì)一般的開發(fā)需求,之后會(huì)有番外篇講Kotlin的反射、泛型以及委托等笤喳。