Kotlin 基礎(chǔ)知識(shí)
一,Using Kotlin for Android Development
? 1.1 使用Kotlin做Android開(kāi)發(fā)
Kotlin 是非常適合用于開(kāi)發(fā)Android應(yīng)用的,它給Android平臺(tái)帶來(lái)了新語(yǔ)言的所有優(yōu)點(diǎn)卻不增加任何新的限制:
兼容性:Kotlin完全兼容JDK6,所以Kotlin可以在老舊的Android設(shè)備上運(yùn)行不出問(wèn)題。AndroidStudio完全支持Kotlin,編譯系統(tǒng)也兼容Kotlin。
性能:因?yàn)榉浅O嗨频淖止?jié)碼結(jié)構(gòu),Kotlin應(yīng)用運(yùn)行速度和Java一樣快。因?yàn)镵otlin支持內(nèi)聯(lián)函數(shù),當(dāng)時(shí)用lambdas時(shí),通常比用Java代碼運(yùn)行更快。
互通性:Kotlin能100%與Java相互協(xié)作,能使用所有的Android庫(kù),包括注解處理庫(kù),所以像databinding,dagger這些庫(kù)都能使用。
占用空間:Kotlin的運(yùn)行時(shí)庫(kù)非常簡(jiǎn)潔,當(dāng)時(shí)用混淆的時(shí)候還會(huì)縮減到更小,只會(huì)增加幾百個(gè)方法,只增加apk不到100k的體積。
編譯時(shí)間:Kotlin支持高效的增量編譯,所以當(dāng)項(xiàng)目已經(jīng)編譯過(guò),再進(jìn)行額外的編譯時(shí),這種增量編譯可以像Java一樣快,甚至更快。
學(xué)習(xí)曲線:對(duì)于Java開(kāi)發(fā)者來(lái)說(shuō),使用Kotlin非常簡(jiǎn)單。Java代碼自動(dòng)化轉(zhuǎn)換成Kotllin代碼的Kotlin插件幫我們邁出了第一步。 Kotlin Koans 通過(guò)一系列的包含Kotlin主要特點(diǎn)的交互性習(xí)題給我們提供了指南。
1.2 用Kotlin 做Android開(kāi)發(fā)樣例研究
Kotlin已經(jīng)被一些大企業(yè)接受,其中部分企業(yè)分享了他們的經(jīng)驗(yàn):
Pinterest已經(jīng)成功地在他們?cè)掠脩袅?.5億的應(yīng)用中引進(jìn)了Kotlin。
Basecamp 的Android app 100%使用Kotlin代碼,他們表示Kotlin給他們帶來(lái)了編程的快樂(lè),以及工作質(zhì)量和效率的提升。
Keepsafe的app Lock app也已經(jīng)轉(zhuǎn)換成100%的Kotlin,這使得代碼行數(shù)減少30%,方法數(shù)減少10%。
1.3 Android 開(kāi)發(fā)工具
Kotlin團(tuán)隊(duì)一套工具用于開(kāi)發(fā),其中有超越了標(biāo)準(zhǔn)語(yǔ)言的特性:
是編譯器的拓展,它使你免去在代碼中調(diào)用findViewById()方法,而是通過(guò)編譯生成。
Anko是一個(gè)庫(kù),它提供了一組Android APIs和DSL的包裝,讓你用Kotlin代碼替換.xml文件。
1.4 下一步
下載安裝 Android Studio 3.0,它提供了Kotlin支持,安裝即可使用。
按照 Getting Started with Android and Kotlin指導(dǎo)創(chuàng)建你的第一個(gè)Kotlin應(yīng)用。
想要深入了解,可查看reference documen和Kotlin Koans。
另一個(gè)很好的資源是一本書(shū)《 Kotlin for Android Developers》,它一步一步地指導(dǎo)你創(chuàng)建一個(gè)Kotlin編寫(xiě)的Android應(yīng)用。
看Google的樣例 sample projects written in Kotlin。
二,變量的聲明和使用
2.1 變量聲明
只讀變量
可理解為Java中常量,使用 val 關(guān)鍵字修飾
val a: Int = 1? //在聲明的時(shí)候直接賦值
val b = 2? // 類(lèi)型是明確的,變量類(lèi)型Int可省去
val c: Int? // 在聲明時(shí)沒(méi)有賦值,變量類(lèi)型Int不可省去
c = 3? ? ? // 只可賦值一次,此后c值不能再變
易變變量
可理解為Java中的普通變量,用 var 關(guān)鍵字修飾,與 val 變量的區(qū)別是變量的值可變:
var a=0
a=1
a=3
成員變量
與Java一樣,通過(guò)對(duì)象.變量名的方式調(diào)用:
fun main(args: Array<String>) {
? ? print(Test().property)
}
class Test{
? ? var property="property"
}
靜態(tài)變量
用companion object{}包裹,與Java一樣通過(guò)類(lèi)名.變量名的方式調(diào)用,關(guān)于companion object后面會(huì)詳細(xì)講解:
fun main(args: Array<String>) {
? ? print(Test.com)
}
class Test{
? ? companion object{
? ? ? ? var com="com"
? ? }
}
頂級(jí)變量
在類(lèi)的外部聲明,可理解為Java中的靜態(tài)成員變量。
通過(guò)包名.變量名的方式來(lái)調(diào)用
樣例:
package a.b.c
var top = "top"
fun main(args: Array<String>) {
? ? print(a.b.c.top)
}
靜態(tài)變量與頂級(jí)變量的區(qū)別:
通過(guò)反編譯可以知道,其實(shí)他們不在一個(gè)類(lèi)中。
當(dāng)文件中有頂級(jí)變量,編譯時(shí)會(huì)新生成一個(gè)[文件名+kt]的類(lèi),頂級(jí)變量就在其中。
2.2 Getters and Setters
我們先定義各種可見(jiàn)性的var類(lèi)型成員變量:
? ? private var privateField = ""
? ? internal var internalField = ""
? ? protected var protectedField = ""
? ? var publicField = ""
然后反編譯看看對(duì)應(yīng)的java代碼:
? @NotNull
? public final String getInternalField$app() {
? ? ? return this.internalField;
? }
? public final void setInternalField$app(@NotNull String var1) {
? ? ? Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
? ? ? this.internalField = var1;
? }
? @NotNull
? protected final String getProtectedField() {
? ? ? return this.protectedField;
? }
? protected final void setProtectedField(@NotNull String var1) {
? ? ? Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
? ? ? this.protectedField = var1;
? }
? @NotNull
? public final String getPublicField() {
? ? ? return this.publicField;
? }
? public final void setPublicField(@NotNull String var1) {
? ? ? Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
? ? ? this.publicField = var1;
? }
可知,protected和public會(huì)生成對(duì)應(yīng)的setter和getter方法,internal也生成了特殊的對(duì)應(yīng)方法。
所以對(duì)于protected和public的成員變量,我們不能自己創(chuàng)建對(duì)應(yīng)的getter和setter方法,如:
var name:String?
fun setName(name:String){}
將會(huì)報(bào)錯(cuò):
Platform declaration clash: The following declarations have the same JVM signature (setName(Ljava/lang/String;)V)
Visibility Modifiers
對(duì)于getter
getter的可見(jiàn)性和變量的可見(jiàn)性相同,無(wú)需重復(fù)添加修飾符。非要多此一舉添加,如果和變量的修飾符不一樣,將會(huì)報(bào)錯(cuò),如:
var field: String = ""
private get? //報(bào)錯(cuò): Getter visibility must be the same as property visibility
對(duì)于setter
setter的可見(jiàn)性必須小于等于變量自身的可見(jiàn)性,否則報(bào)錯(cuò),如:
private var name=""
public set //error:Setter visibility must be the same or less permissive than property visibility
我們也可以自定義getter和setter方法
語(yǔ)法
<var <propertyName>[: <PropertyType>] [= <property_initializer>]
? ? [<getter>]
? ? [<setter>]
當(dāng)給變量賦值時(shí)會(huì)調(diào)用setter方法,調(diào)用變量得到的是getter中的返回值
樣例:
var name: String = "111"
? ? get() {
? ? ? ? return "{$field}"
? ? }
? ? set(value) {
? ? ? ? field = "[$value]"
? ? }
fun main(args: Array<String>) {
? ? println(name)
? ? name = "222"
? ? print(name)
}
結(jié)果:
{111}
{[222]}
三,方法的定義和使用
3.1 方法定義
定義語(yǔ)法:
fun? [方法名] ( [參數(shù)名] : [參數(shù)類(lèi)型] ) : [返回類(lèi)型]{
? ? ...
? ? return [返回值]
}
有返回值
fun multiply(x: Int, y:Int): Int {
? ? return x * y
}
也可以轉(zhuǎn)換為:
fun multiply(x: Int, y: Int): Int = x * y
還可以這樣:
var multiply = { x: Int, y: Int -> x * y }
無(wú)返回值
使用Unit代替返回類(lèi)型:
fun log(msg: String): Unit {
? ? print(msg)
}
Unit也可以省去:
fun log(msg: String) {
? ? print(msg)
}
成員方法
與Java一樣通過(guò)對(duì)象.方法名的方式調(diào)用:
fun main(args: Array<String>) {
? ? Test().method()
}
class Test{
? ? fun method(){
? ? ? ? print("hello")
? ? }
}
靜態(tài)方法
和Java一樣通過(guò)類(lèi)名.方法名的方式調(diào)用:
fun main(args: Array<String>) {
? ? Test.com()
}
class Test{
? ? companion object{
? ? ? ? fun com(){
? ? ? ? ? ? print("com")
? ? ? ? }
? ? }
}
頂級(jí)方法
在類(lèi)外部定義的方法,可理解為靜態(tài)方法,通過(guò)包名.方法名的方式調(diào)用:
package a.b.c
fun top(){
? ? print("top")
}
fun main(args: Array<String>) {
? ? a.b.c.top()
}
頂級(jí)方法與靜態(tài)方法區(qū)別
通過(guò)反編譯可以知道,其實(shí)他們不在一個(gè)類(lèi)中。
如果有頂級(jí)變量會(huì)新生成一個(gè)[文件名+kt]的類(lèi),頂級(jí)方法就在其中。
3.2 方法調(diào)用順序可變
通過(guò)指明參數(shù)名稱(chēng),可按任意順序傳參:
fun main(args: Array<String>) {
? ? log(name = "mao", age = 18)
}
fun log(age: Int, name: String) {
? ? print("age:$age,name:$name")
}
3.3 命名參數(shù)
方法樣例:
fun logInfo(
? ? ? ? name: String,
? ? ? ? age: Int,
? ? ? ? married: Boolean = true,
? ? ? ? language: String = "Chinese"
) {
? ? println("[name:$name,age:$age,married:$married,language:$language");
}
必須傳入未設(shè)置默認(rèn)值的參數(shù)
在上述logInfo方法中,name和age未設(shè)置默認(rèn)值,必須傳參,其它參數(shù)可自由選擇:
? ? logInfo("ma",18)
? ? logInfo("ma",18,false);
? ? logInfo("ma",18,false,"English");
3.4 可變參數(shù)
使用vararg 修飾參數(shù):
fun logInfos(vararg infos: String) {
? ? for (info in infos) {
? ? ? ? print("$info? ")
? ? }
}
方法調(diào)用
logInfos("aaa","bbb","ccc")
四,null安全(“?”,“?:”,“!!”)
在Java開(kāi)發(fā)中,null一直是個(gè)大問(wèn)題,哪怕我們?cè)傩⌒?,也難免有疏忽的時(shí)候,Kotlin針對(duì)這個(gè)問(wèn)題做了一些措施。
Kotlin將變量分為可以為Nullable類(lèi)型 Non-Null類(lèi)型,變量在聲明時(shí)就要確定屬于哪個(gè)陣營(yíng)。
變量默認(rèn)Non-Null類(lèi)型,如果想聲明Nullable的變量,需要用“?”修飾:
聲明Non-Null變量
? ? var a: String = "hello"?
聲明Nullable變量
? ? var b: String? = "world"
聲明變量時(shí)若直接賦值,變量類(lèi)型由所賦值的類(lèi)型決定
如在聲明b時(shí),將a賦值給b,b的類(lèi)型(Nullable或Non-Null)與a相同:
? ? var a: String? = "hello"? //Nullable
? ? var b = a? //與a相同,也是Nullable
4.1 Non-Null變量賦值
Non-Null變量不能賦值為null
var a: String=null //報(bào)錯(cuò):Null can not be a value of a non-null type String
Nullable變量無(wú)法直接賦值給Non-Null變量
? ? var a: String? = "hello"
? ? var b = "world"
? ? b = a //報(bào)錯(cuò):Type mismatch: inferred type is String? but String was expected
想要將Nullable變量賦值給Non-Null變量有以下方法:
先處理后賦值
? ? var a: String? = "hello"
? ? var b = "world"
? ? if (a != null) {
? ? ? ? b == a
? ? }
使用“!!”
? ? var a: String? = "hello"
? ? var b = "world"
? ? b = a!!
使用“!!”方法要注意,當(dāng)a為null時(shí)會(huì)拋出KotlinNullPointerException異常。
4.2 Nullable變量的使用
“?”符號(hào)的使用
Nullable變量進(jìn)行操作時(shí)要帶“?”,當(dāng)變量為null時(shí),不會(huì)出現(xiàn)異常,而是返回結(jié)果null:
? ? var name: String? = null
? ? var len = name?.length
? ? print(len == null)? //輸出:true
“?:”符號(hào)的使用
這個(gè)符號(hào)的作用是當(dāng)它左邊的結(jié)果為null時(shí),進(jìn)行右邊的操作。
左邊結(jié)果不為null:
? ? var a: String? = "hello"
? ? var b = a?.length ?: 100? //很明顯左邊不為null
? ? println(b)? //輸出: 5
左邊結(jié)果為null:
? ? var a: String? = null
? ? var b = a?.length ?: 100? //左邊為null,返回右邊的100
? ? println(b)? //輸出: 100
五,類(lèi)與繼承
5.1 類(lèi)的創(chuàng)建
與Java一樣,Kotlin也是用class關(guān)鍵字聲明類(lèi)。
class User{}
Kotlin中一個(gè)類(lèi)可以有一個(gè)主構(gòu)造方法(primary constructor)和一個(gè)或多個(gè)次構(gòu)造方法( secondary constructors)。
5.2 主構(gòu)造方法
主構(gòu)造方法通過(guò)在類(lèi)名后面添加constructor和參數(shù)實(shí)現(xiàn):
class User private constructor(name: String) {}
如果沒(méi)有注解和可見(jiàn)的修飾符,constructor關(guān)鍵字可以省略:
class User(name: String) {}
初始化順序
類(lèi)內(nèi)部的init模塊和變量的初始化順序按照他們出現(xiàn)的順序進(jìn)行
fun main(args: Array<String>) {
? ? User("mao")
}
class User(name: String) {
? ? val firstProperty = "First property".also(::println)
? ? init {
? ? ? ? println("First initializer")
? ? }
? ? val secondProperty = "Second property".also(::println)
? ? init {
? ? ? ? println("Second initializer")
? ? }
}
輸出:
First property
First initializer
Second property
Second initializer
成員變量和init模塊在初始化時(shí)可直接使用主構(gòu)造方法中的參數(shù)
class User(name: String) {
? ? var mName = name
? ? init {
? ? ? ? var mName = name
? ? }
}
5.3 次構(gòu)造方法
次構(gòu)造方法也使用constructor實(shí)現(xiàn)
class User {
? ? var name: String = ""
? ? constructor(name: String) {
? ? ? ? this.name = name
? ? }
}
當(dāng)類(lèi)聲明了主構(gòu)造方法,所有次構(gòu)造方法必須直接或間接調(diào)用主構(gòu)造方法
class User() {
? ? constructor(name: String) : this() {
? ? ? ? print("conconstructor")
? ? }
? ? constructor(name: String, age: Int) : this(name) {}
}
類(lèi)中的變量初始化和init模塊初始化都是主構(gòu)造方法的一部分,所以都在次構(gòu)造方法之前執(zhí)行
fun main(args: Array<String>) {
? ? User("mao")
}
class User() {
? ? constructor(name: String) : this() {
? ? ? ? print("conconstructor")
? ? }
? ? var name = "property".also(::println)
? ? init{
? ? ? ? println("init")
? ? }
}
輸出:
property
init
conconstructor
當(dāng)一個(gè)類(lèi)沒(méi)有任何構(gòu)造方法時(shí),默認(rèn)生成一個(gè)public類(lèi)型的無(wú)參主構(gòu)造方法,如果不希望這個(gè)默認(rèn)構(gòu)造方法存在,可以主動(dòng)聲明一個(gè)主構(gòu)造方法
class User private constructor() {}
5.4 繼承
Kotlin中的類(lèi)默認(rèn)是final類(lèi)型的,想要被繼承,得用“open”關(guān)鍵字修飾。
open class Shape {}
class Rectangle : Shape {}
子類(lèi)的所有構(gòu)造構(gòu)造方法必須直接或間接調(diào)用一個(gè)父類(lèi)的構(gòu)造方法
open class Shape {
? ? constructor(name: String) {
? ? ? ? print(name)
? ? }
}
class Rectangle : Shape {
? ? constructor(name: String) : super(name) {}
? ? constructor(name: String, age: Int) : this(name) {}
}
方法重寫(xiě)
繼承過(guò)程中,只有open修飾的方法才能被重寫(xiě),重寫(xiě)時(shí)要用override修飾。
open特性也能被繼承,想要斷了open特性,只需用final修飾即可。
open class Shape {
? ? open fun method() {}
}
open class Rectangle : Shape() {
? ? override fun method() {}
}
class Square : Rectangle() {
? ? final override fun method() {}
}
成員變量重寫(xiě)
與方法重寫(xiě)相同,只有open修飾的變量才能被重寫(xiě),open同樣可以繼承,也可以用final中斷。
重寫(xiě)過(guò)程中,變量可由val類(lèi)型變?yōu)関ar類(lèi)型,反之則不行。
open class Shape {
? ? open val name: String = "Shape"
}
open class Rectangle : Shape() {
? ? override var name: String = "Rectangle"
}
class Square : Rectangle() {
? ? final override var name = "Square"
}
調(diào)用父類(lèi)方法和成員變量
可通過(guò)“super”關(guān)鍵字調(diào)用父類(lèi)的方法和成員變量
open class Shape {
? ? open val name: String = "Shape"
? ? open fun draw() {}
}
open class Rectangle : Shape() {
? ? override var name: String = super.name
? ? override fun draw() {
? ? ? ? super.draw()
? ? }
}
內(nèi)部類(lèi)調(diào)用外部類(lèi)父類(lèi)的方法
使用“super@Outer”方式:
open class Sup {
? ? open fun method() { println("Sup.method") }
}
class Sub:Sup(){
? ? inner class Inner{
? ? ? ? fun test(){
? ? ? ? ? ? super@Sub.method()
? ? ? ? }
? ? }
}
當(dāng)繼承的類(lèi)和接口當(dāng)中出現(xiàn)相同的方法(方法名和參數(shù)都相同),通過(guò)類(lèi)似泛型的方法明確調(diào)用哪個(gè)方法
interface Action {
? ? fun eat() {
? ? ? ? println("Action")
? ? }
}
open class Animal {
? ? open fun eat() {
? ? ? ? println("Animal")
? ? }
}
class Human() : Animal(), Action {
? ? override fun eat() {
? ? ? ? super<Action>.eat()
? ? ? ? super<Animal>.eat()
? ? }
}
六,內(nèi)聯(lián)方法
使用高階方法會(huì)造成一些強(qiáng)制性的開(kāi)銷(xiāo):內(nèi)存分配和調(diào)用都是開(kāi)銷(xiāo)。不過(guò),在許多情況下有些開(kāi)銷(xiāo)是可以避免的。
例子:
現(xiàn)在把吃飯分為三個(gè)步驟:1.拿起筷子 2.吃 一口3.放下筷子
1.情況一:每吃一口都要放下筷子,即拿、吃、放、拿、吃、放...
操作順序:
1、2、3;? 1、2、3; 1、2、3...
這種情況不是飯菜不合胃口、就是吃撐了。
2.情況二:每吃若干口才放下筷子。
操作順序:
1、2、2...2、3; 1、2、2...2、3;... 1、2、2...2、3;
明顯這才是正確操作,能省不少力氣。
在JVM中也有類(lèi)似的情況,JVM中每個(gè)線程都有一個(gè)虛擬機(jī)棧,每個(gè)方法的從調(diào)用到完成,對(duì)應(yīng)著入棧、出棧的過(guò)程,如果將一方法分為多個(gè)方法時(shí),就會(huì)有更多的入棧、出棧的開(kāi)銷(xiāo),使用內(nèi)聯(lián)方法能有效減少這部分開(kāi)銷(xiāo)。
例一
現(xiàn)在我們要打印一個(gè)人,分三部分打?。侯^、身體和腳,其中身體構(gòu)造復(fù)雜,又要分三部分
1-1
fun main(args: Array<String>) {
? ? //printHead
? ? println("head")
? ? //printbody
? ? println("body1")
? ? println("body2")
? ? println("body3")
? ? //printFoot
? ? println("foot")
}
現(xiàn)在我們想把打印身體這部放到一個(gè)單獨(dú)的方法中,使代碼更清晰:
1-2
fun main(args: Array<String>) {
? ? //printHead
? ? println("head")
? ? //printbody
? ? printBody()
? ? //printFoot
? ? println("foot")
}
fun printBody() {
? ? println("body1")
? ? println("body2")
? ? println("body3")
}
不過(guò)這樣一來(lái)多了部分開(kāi)銷(xiāo),這時(shí)內(nèi)聯(lián)方法可以幫我們避免這部分開(kāi)銷(xiāo)。
6.1 inline
定義內(nèi)聯(lián)方法需使用“inline”關(guān)鍵字修飾
1-3
fun main(args: Array<String>) {
? ? //printHead
? ? println("head")
? ? //printbody
? ? printBody()
? ? //printFoot
? ? println("foot")
}
inline fun printBody() {
? ? println("body1")
? ? println("body2")
? ? println("body3")
}
編譯過(guò)程會(huì)幫我們把1-3轉(zhuǎn)換為1-1的樣子,提高了性能的同事又能保證結(jié)構(gòu)清晰。
例二
如何才能將代碼
lock(l) { foo() }
實(shí)現(xiàn)這樣的效果:
l.lock()
try {
? ? foo()
}
finally {
? ? l.unlock()
}
可以通過(guò)內(nèi)聯(lián)方法實(shí)現(xiàn):
inline fun <T> check(lock: Lock, body: () -> T): T {
? ? lock.lock()
? ? try {
? ? ? ? return body()
? ? } finally {
? ? ? ? lock.unlock()
? ? }
}
調(diào)用:
? ? var lock = ReentrantLock()
? ? check(lock) {print("hello")}
完整樣例:
開(kāi)啟兩個(gè)線程,通過(guò)加同一把鎖實(shí)現(xiàn)同步:
fun main(args: Array<String>) {
? ? var lock = ReentrantLock()
? ? Thread() {
? ? ? ? check(lock) {
? ? ? ? ? ? for (i in 1..5) {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1)
? ? ? ? ? ? ? ? println("111")
? ? ? ? ? ? }
? ? ? ? }
? ? }.start()
? ? Thread() {
? ? ? ? check(lock) {
? ? ? ? ? ? for (i in 1..5) {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1)
? ? ? ? ? ? ? ? println("222")
? ? ? ? ? ? }
? ? ? ? }
? ? }.start()
}
inline fun <T> check(lock: Lock, body: () -> T): T {
? ? lock.lock()
? ? try {
? ? ? ? return body()
? ? } finally {
? ? ? ? lock.unlock()
? ? }
}
輸出:
111
111
111
111
111
222
222
222
222
222
6.2 noinline
inline 方法中的方法參數(shù)默認(rèn)是inline類(lèi)型的,如果想過(guò)濾掉inline特性,可用noinline修飾。
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
? ? // ...
}
6.3 lambda與return
1.普通方法的lambda模塊中禁止使用return
fun foo() {
? ? ordinaryFunction {
? ? ? ? return // ERROR: can not make `foo` return here
? ? }
}
2.inline方法的lambda模塊可用使用return
fun foo() {
? ? inlineFunction {
? ? ? ? return // OK: the lambda is inlined
? ? }
}
fun hasZeros(ints: List<Int>): Boolean {
? ? ints.forEach {
? ? ? ? if (it == 0) return true // returns from hasZeros
? ? }
? ? return false
}
這么設(shè)計(jì)很好理解,普通方法的lambda模塊其實(shí)是另一個(gè)方法的方法體,你在本方法中調(diào)用另一方法中的return算是怎么回事,稍不注意容易理解錯(cuò)誤。
而inline方法可認(rèn)為是本方法的一部分,外面那層大括號(hào)包裝可當(dāng)它不存在,使用return就顯得和自然了。
3.那么想要在普通方法lambda return怎么辦?只需在return后加“@方法名”即可:
fun f() {
? ? test {
? ? ? ? return@test
? ? }
}
fun test(action: () -> Unit) {}
4.inline 類(lèi)型的方法參數(shù)不能直接用于賦值,要想用于賦值得用crossinline 修飾
inline fun f(crossinline body: () -> Unit) {
? ? val f = object: Runnable {
? ? ? ? override fun run() = body()
? ? }
}
6.4 reified
泛型只在編譯時(shí)起作用,在運(yùn)行時(shí)已經(jīng)被擦除,所以泛型標(biāo)記是沒(méi)法當(dāng)做對(duì)象使用的。
不過(guò)在Kotlin中,reified可修飾inline方法中的泛型,對(duì)其進(jìn)行反射操作。
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
? ? println(membersOf<User>())
}
class User() {
? ? var name = "mao"
}
七,enum,data,sealed,object
7.1 enum類(lèi)
7.1.1與java不同,Kotlin中enum當(dāng)做class的修飾符使用
1.常見(jiàn)用法:
enum class Direction {
? ? NORTH, SOUTH, WEST, EAST
}
2.有成員屬性時(shí):
enum class Color(val rgb: Int) {
? ? RED(0xFF0000),
? ? GREEN(0x00FF00),
? ? BLUE(0x0000FF)
}
打印成員的值
print(Color.RED.rgb)
3.匿名方法
假如有一個(gè)機(jī)器,他有三種狀態(tài)start,run,stop,并且不斷循環(huán)我們可以這樣實(shí)現(xiàn):
enum class State {
? ? Start {
? ? ? ? override fun nextState() = Run
? ? },
? ? Run {
? ? ? ? override fun nextState() = Stop
? ? },
? ? Stop {
? ? ? ? override fun nextState() = Start
? ? };
? ? abstract fun nextState(): State
}
簡(jiǎn)單調(diào)用:
fun main(args: Array<String>) {
? ? var state = State.Stop
? ? println(state)
? ? for (i in 1..3) {
? ? ? ? state = state.nextState()
? ? ? ? println(state)
? ? }
}
輸出:
Stop
Start
Run
Stop
7.2 data類(lèi)
在Java中,我們?cè)谛畔㈩?lèi)創(chuàng)建信息類(lèi)時(shí)總是伴隨大量getter/setter方法,雖然可以用工具自動(dòng)生成,但也影響美觀。Kotlin中使用data類(lèi)型幫助我們解決了這個(gè)問(wèn)題。
data class User(
? ? ? ? var name: String,
? ? ? ? var age: Int
)
在編譯時(shí),根據(jù)主構(gòu)造器中的參數(shù)會(huì)自動(dòng)生成getter/setter,hashcode(),toString(),equals(),copy()等方法。
我們無(wú)法直接調(diào)用getter/setter 方法,但我們對(duì)它的操作上本質(zhì)上都是通過(guò)調(diào)用getter/setter方法實(shí)現(xiàn)的。
data class User(
? ? ? ? var name: String,
? ? ? ? var age: Int
)
設(shè)置和修改變量的值
? ? var user = User("mao", 18)
? ? user.name = "zhang"
? ? user.age = 3
copy()
當(dāng)我們想將對(duì)象復(fù)制一份時(shí),可用copy()方法:
? ? var user = User("mao", 18)
? ? var user2 = user.copy()
如果想改變某個(gè)變量:
? ? var user=User("mao",18)
? ? var user2=user.copy(age=100)
7.3 sealed類(lèi)
sealed類(lèi)可看做時(shí)enum類(lèi)的一種拓展,相比于enum的常量以單一實(shí)例存在,sealed類(lèi)的子類(lèi)可以有多種確定的類(lèi)型。
sealed類(lèi)自身是抽象類(lèi),它的子類(lèi)不能是抽象類(lèi),子類(lèi)和它必須在同一個(gè)文件中。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
與when搭配使用非常方便
fun eval(expr: Expr): Double = when(expr) {
? ? is Const -> expr.number
? ? is Sum -> eval(expr.e1) + eval(expr.e2)
? ? NotANumber -> Double.NaN
}
栗子:現(xiàn)在將人根據(jù)顏值和智商分為4類(lèi):
1.有顏值有智商
2.有顏值沒(méi)智商
3.沒(méi)智商有顏值
4.沒(méi)智商沒(méi)顏值
sealed class Feature
data class IQ(var score: Int) : Feature()
data class FaceScore(var Sore: Int) : Feature()
data class Sum(var f1: Feature, var f2: Feature) : Feature()
object None : Feature()
然后設(shè)計(jì)評(píng)分機(jī)制:顏值(faceScore)和智商(IQ)基礎(chǔ)分0-10分
sum(總分)= faceScore * 8 + IQ * 2
計(jì)算方法實(shí)現(xiàn):
fun cal(f: Feature): Int = when (f) {
? ? is IQ -> {
? ? ? ? f.score * 2
? ? }
? ? is FaceScore -> f.Sore * 8
? ? is Sum -> cal(f.f1) + cal(f.f2)
? ? None -> 0
}
計(jì)算:
fun main(args: Array<String>) {
? ? var f1 = IQ(10)
? ? var f2 = FaceScore(10)
? ? var f3 = Sum(f1, f2)
? ? var f4=None
? ? cal(f1).also(::println)
? ? cal(f2).also(::println)
? ? cal(f3).also(::println)
? ? cal(f4).also(::println)
}
7.4 object
object到底有什么作用呢?先創(chuàng)建一個(gè)最簡(jiǎn)單的object類(lèi)
用來(lái)實(shí)現(xiàn)單例模式
object O
反編譯獲得Java代碼:
public final class O {
? ? public static final O INSTANCE;
? ? static {
? ? ? ? O localo = new O();
? ? ? ? INSTANCE = localo;
? ? }
}
這是一種單例模式的實(shí)現(xiàn)方法,如此看來(lái)object 可以用來(lái)實(shí)現(xiàn)單例模式。
簡(jiǎn)單用法:
fun main(args: Array<String>) {
? ? O.test()
? ? O.name = "hello"
}
object O {
? ? var name = "mao"
? ? fun test() {
? ? ? ? print("test")
? ? }
}
object還可以用來(lái)實(shí)現(xiàn)匿名內(nèi)部類(lèi)
fun main(args: Array<String>) {
? ? var btn = Btn()
? ? btn.onClickLsn = object : Btn.OnClickLsn {
? ? ? ? override fun click() {
? ? ? ? ? ? print("click")
? ? ? ? }
? ? }
? ? btn.callClick()
}
class Btn() {
? ? var onClickLsn: OnClickLsn? = null
? ? fun callClick() {
? ? ? ? onClickLsn?.click()
? ? }
? ? interface OnClickLsn {
? ? ? ? fun click()
? ? }
}
object類(lèi)可以繼承一個(gè)類(lèi)和多個(gè)接口。
當(dāng)父類(lèi)有構(gòu)造方法時(shí),應(yīng)傳入對(duì)應(yīng)的參數(shù)。
interface A
open class B(age: Int) {
? ? var mAge = age
}
var c: A = object : B(18), A {}
object可以做private方法和成員變量的返回值,不能做public方法和成員變量的返回值。
作為private方法和變量的返回值時(shí),返回類(lèi)型是匿名對(duì)象類(lèi)型,可以訪問(wèn)內(nèi)部成員。
而最為public方法和變量返回值時(shí),返回類(lèi)型為Any,不能訪問(wèn)內(nèi)部成員。
class C {
? ? // private方法,返回類(lèi)型是匿名對(duì)象類(lèi)型
? ? private fun foo() = object {
? ? ? ? val x: String = "x"
? ? }
? ? // public方法,返回類(lèi)型是 Any
? ? fun publicFoo() = object {
? ? ? ? val x: String = "x"
? ? }
? ? fun bar() {
? ? ? ? val x1 = foo().x? ? ? ? // 沒(méi)問(wèn)題
? ? ? ? val x2 = publicFoo().x? //報(bào)錯(cuò),無(wú)法引用x
? ? }
}
object和companion
在類(lèi)內(nèi)部,object和companion可實(shí)現(xiàn)靜態(tài)成員和靜態(tài)方法的效果。
class Outer {
? ? companion object Inner {
? ? ? ? var property = "property"
? ? ? ? fun method() {
? ? ? ? ? ? print("method")
? ? ? ? }
? ? }
}
調(diào)用:
Outer.Inner.property.also(::println)
Outer.Inner.method()
通過(guò)反編譯可知此處的 Inner并不是類(lèi),而是一個(gè)靜態(tài)常量實(shí)例。
正常使用過(guò)程中Inner可以省去:
Outer.property.also(::println)
Outer.method()
這兩種方法本質(zhì)上是一樣的。
類(lèi)中的companion object修飾的Inner也可去掉:
class Outer {
? ? companion object {
? ? ? ? var property = "property"
? ? ? ? fun method() {
? ? ? ? ? ? print("method")
? ? ? ? }
? ? }
}
當(dāng)沒(méi)有Inner的情況下,Inner默認(rèn)為Companion