Kotlin編程實(shí)踐1-3章

一、Kotlin基礎(chǔ)

1、在線kotlin沙箱: https://play.kotlinlang.org/

2、在Android中使用Kotlin(Groovy在進(jìn)行插值時(shí)會(huì)使用雙引號(hào),不需要插值時(shí)也可以使用單引號(hào))

頂層build.gradle

buildscript{
    ext.kotlin_version = '1.3.50'
    repositories{
        google()
        jcenter()
    }
    dependencies{
        classpath 'com.amdroid.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

應(yīng)用程序目錄中build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android{
    //android information
}
dependencies{
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version
    //  其他無(wú)關(guān)依賴
}

3、可空類型

class Person(
        val first:String,
        val middle: String?,
        val last : String)

var p = Person(first = "North",middle = null,last = "West")
val middleNameLength = p.middle?.length
//返回的結(jié)果類型是Int?
val middleNameLength = p.middle?.length ?: 0

4、安全轉(zhuǎn)換操作符 as?

避免在強(qiáng)制轉(zhuǎn)換時(shí)拋出ClassCastException

val p1 = p as? Persion
//p1的類型為Persion?

轉(zhuǎn)換成功結(jié)果是一個(gè)Person,失敗值降為null

5、顯式類型轉(zhuǎn)換

kotlin 不會(huì)自動(dòng)將原生類型轉(zhuǎn)換到一個(gè)位數(shù)更大的原生類型,例如Int轉(zhuǎn)型到Long。
使用toInt、toLong等顯示轉(zhuǎn)換函數(shù)來(lái)顯示轉(zhuǎn)換位數(shù)較小的類型。

val intVar : Int =3
//val longVar: Long = intVar  不編譯該行
val longVar: Long = intVar.toLong()

kotlin利用重載操作符來(lái)透明執(zhí)行了類型轉(zhuǎn)換

val longSum = 3L+ intVar

加號(hào)操作符自動(dòng)將intVar的值轉(zhuǎn)換成long并相加

6、使用to 函數(shù)創(chuàng)建Pair實(shí)例

mapOf函數(shù)的簽名如下:

fun <k,v> mapOf(vararg pairs: Pair<K,V>): Map<K,V>

Pair簽名如下

data class Pair<out A,out B> : Serializable

to函數(shù)定義如下

public infix fun <A,B> A.to(that:B):Pair<A,B> = Pair(this,that)

實(shí)際應(yīng)用

val map = mapOf("a" to 1,"b" to 2,"c" to 3)
//創(chuàng)建Pair兩種方式
val p1 = Pair("a",1)
val p2 = "a" to 1

二、Kotlin中的面向?qū)ο缶幊?/h1>

初始化對(duì)象,自定義getter和setter,延遲初始化,惰性初始化,創(chuàng)建單例,理解Nothing類

1、const與val的不同

const修飾的是編譯時(shí)常量;val 是運(yùn)行時(shí)常量

在Java程序里,常量用關(guān)鍵字static final修飾,常量又分為:
編譯期常量
運(yùn)行時(shí)常量

static final  int  A = 1024;//編譯期常量,類型必須是基本類型或String!

static final  int len = "Rhine".length();//運(yùn)行時(shí)常量!運(yùn)行時(shí)才能確定它的值。

編譯期常量不依賴類,不會(huì)引起類的初始化;而運(yùn)行時(shí)常量依賴類,會(huì)引起類的初始化。

class Task (val name:String,_priority:Int = DEFAULT_PRIORITY){
        companion object{
            const val MIN_PRIORITY =1          //編譯時(shí)常量
            const val MAX_PRIORITY = 5        //編譯時(shí)常量
            const val DEFAULT_PRIORITY = 3  //編譯時(shí)常量
        }
        
        var priority = validPriority(_priority)    //自定義set屬性
            set(value){
                field = validPriority(value)
            }
            
        private fun validPriority(p:Int) =     //私有的驗(yàn)證函數(shù)
            p.coerceIn(MIN_PRIORITY,MAX_PRIORITY)
    }

2、自定義getter和setter

自定義set屬性

        var priority = 3   
            set(value){
                field = value.coerceIn(value)
            }

自定義get屬性

var isLowPriority
    get() = priority < 3//isLowPriority是布爾類型的

3、定義數(shù)據(jù)類

使用data關(guān)鍵字,指明特定類的目的是保存數(shù)據(jù)。與java中實(shí)體類相似。
將data添加到類的定義中會(huì)使編譯器生成一系類函數(shù),包括equals、hashCode、toString、copy、component函數(shù)

data class Product(
    val name : String,
    val price :Double,
    val onSale:Boolean = false
)
fun main() {
    println("Hello, world!!!")
 
    val p1 = Product("ball",10.0)
    val p2 = Product(price = 10.0,onSale = false,name = "ball")
    val pros = setOf(p1,p2)

    println("Hello, world!!!"+p1.hashCode()+"|||"+p2.hashCode()) //Hello, world!!!1897955903|||1897955903
    println("Hello, world!!!"+pros.size)//1
}

copy方法

fun  Pcopy(){
    val p1 = Product("ball",10.0)
    val p2 = p1.copy(price = 12.0)  //僅僅更改price的值
}

注意:copy函數(shù)僅僅執(zhí)行淺拷貝,而不執(zhí)行深拷貝

4、幕后屬性技術(shù)

class Customer(val name :String){
    private var _messages:List<String>? = null
    
    val messages:List<String>
        get(){
            if(_messages == null){
                _messages = loadMessages() 
            }
            return _messages!!
        }
    
    private fun loadMessages():MutableList<String> =
        mutableListOf(
            "Initial contact",
            "Convinced hem to use Kotlin",
            "Sold training class. Sweet."
        ).also{println("Loaded messages")}
}

避免了messages屬性初始化,添加了_messages,類型相同但可空

使用Lazy委托函數(shù) 實(shí)現(xiàn)懶加載(lazy 委托在后面小結(jié)中討論)

class Customer(val name :String){
    val messages:List<String> by lazy {loadMessages()}

    private fun loadMessages():MutableList<String> =
        mutableListOf(
            "Initial contact",
            "Convinced hem to use Kotlin",
            "Sold training class. Sweet."
        ).also{println("Loaded messages")}
}

5、使用lateinit進(jìn)行延遲初始化

在依賴注入的情況下很有用
通常在可能情況下考慮其他替代方法,如lazy

lateinit修飾符智能修飾類中聲明的var屬性,并且該屬性不能擁有自定義的getter和setter。從1.2開(kāi)始,可以修飾頂層屬性甚至局部變量,被修飾的類型必須是非空的且不能是原始類型。

class LateInitDemo{
    lateinit var name : String
}

在name屬性還沒(méi)有初始化的時(shí)候訪問(wèn)他會(huì)拋出uninitializedPropertyAccessExcrption.

class LateInitDemo{
    lateinit var name : String
    fun initName(){
        println("before init: ${::name.isInitialized}")//before init: false
        name = "world"
        println("after init: ${::name.isInitialized}")//after init: true
    }
}

isInitialized檢查一個(gè)屬性是否初始化了

lateinit 與lazy:
lateinit 修飾符用于修飾var屬性,但是它具有上面的使用限制,而lazy委托則使用lambda表達(dá)式,該lambda表達(dá)式會(huì)在首次訪問(wèn)這個(gè)屬性時(shí)賦值
如果初始化非常昂貴或者屬性也許永遠(yuǎn)不會(huì)被使用時(shí),請(qǐng)使用lazy。同樣,lazy只能用于val屬性,而lateinit只能用于var屬性。最后,lateinit屬性可以在任何該屬性可見(jiàn)的地方初始化

6、使用安全轉(zhuǎn)換函數(shù)、恒等操作符、以及Elvis操作符覆蓋equals函數(shù)

===、as?、?:

java中==操作符用于檢查是否指向同一個(gè)對(duì)象。
kotlin中==操作符會(huì)自動(dòng)調(diào)用equals函數(shù)

7、創(chuàng)建單例

kotlin中使用object關(guān)鍵字替代class來(lái)實(shí)現(xiàn)單例模式,被稱為object聲明

object MySingleton{
    val myProperty = 3
    fun myFunction() = "Hello"
}

如果反編譯,會(huì)得到下面代碼:

public final class MySingleton{
    private static final int myProperty = 3;
    public static funal MySingleton INSTANCE;

    private MySingleton(){}
    public final int getMyProperty(){
        return myProperty ;
    }
    public final void myFunction(){
        return "Hello";
    }

    static {
        MySingleton var0 = new MySingleton ();
        INSTANCE = var0;
        myProperty  = 3;
    }
}

引用:
https://oreil.ly/P8QCv 的“Kotlin Singletons with Argument”討論了基于雙重校驗(yàn)鎖以及@volatile 來(lái)實(shí)現(xiàn)單例實(shí)例化線程安全的復(fù)雜性
https://blog.csdn.net/mq2553299/article/details/87128674(國(guó)內(nèi))

簡(jiǎn)單地聲明object以創(chuàng)建一個(gè)單例:

object SomeSingleton

與類不同,object 不允許有任何構(gòu)造函數(shù),如果有需要,可以通過(guò)使用 init 代碼塊進(jìn)行初始化的行為:

object SomeSingleton{
    init{
        println("init complete")
    }
}

這樣object將被實(shí)例化,并且在初次執(zhí)行時(shí),其init代碼塊將以線程安全的方式懶惰地執(zhí)行。 為了這樣的效果,Kotlin對(duì)象實(shí)際上依賴于Java的 靜態(tài)代碼塊 。上述Kotlin的 object 將被編譯為以下等效的Java代碼:

public final class SomeSingleton{
    public static final SomeSingleton INSTANCE;

    private SomeSingleton(){
        INSTANCE = (SomeSingleton)this;
        System.out.println("init complete");
    }
  
    static {
        new SomeSingleton();
    }
}

這是在JVM上實(shí)現(xiàn)單例的首選方式,因?yàn)樗梢栽诰€程安全的情況下懶惰地進(jìn)行初始化,同時(shí)不必依賴復(fù)雜的雙重檢查加鎖(double-checked locking)等加鎖算法。 通過(guò)在Kotlin中簡(jiǎn)單地使用object進(jìn)行聲明,您可以獲得安全有效的單例實(shí)現(xiàn)。

但是,如果初始化的代碼需要一些額外的參數(shù)呢?你不能將任何參數(shù)傳遞給它,因?yàn)镵otlin的object關(guān)鍵字不允許存在任何構(gòu)造函數(shù)。
在Kotlin中,您必須通過(guò)不同的方式去管理單例的另一種情況是,單例的具體實(shí)現(xiàn)是由外部工具或庫(kù)(比如Retrofit,Room等等)生成的,它們的實(shí)例是通過(guò)使用Builder模式或Factory模式來(lái)獲取的——在這種情況下,您通常將單例通過(guò)interface或abstract class進(jìn)行聲明,而不是object。

我們可以通過(guò)封裝邏輯來(lái)懶惰地在SingletonHolder類中創(chuàng)建和初始化帶有參數(shù)的單例。

為了使該邏輯的線程安全,我們需要實(shí)現(xiàn)一個(gè)同步算法,它是最有效的算法,同時(shí)也是最難做到的——它就是 雙重檢查鎖定算法(double-checked locking algorithm)。

請(qǐng)注意,為了使算法正常工作,這里需要將@Volatile注解對(duì)instance成員進(jìn)行標(biāo)記。

這可能不是最緊湊或優(yōu)雅的Kotlin代碼,但它是為雙重檢查鎖定算法生成最行之有效的代碼。請(qǐng)信任Kotlin的作者:實(shí)際上,這些代碼正是從Kotlin標(biāo)準(zhǔn)庫(kù)中的 lazy() 函數(shù)的實(shí)現(xiàn)中直接借用的,默認(rèn)情況下它是同步的。它已被修改為允許將參數(shù)傳遞給creator函數(shù)。

有鑒于其相對(duì)的復(fù)雜性,它不是您想要多次編寫(xiě)(或者閱讀)的那種代碼,實(shí)際上其目標(biāo)是,讓您每次必須使用參數(shù)實(shí)現(xiàn)單例時(shí),都能夠重用該SingletonHolder類進(jìn)行實(shí)現(xiàn)。

聲明getInstance()函數(shù)的邏輯位置在singleton類的伴隨對(duì)象內(nèi)部,這允許通過(guò)簡(jiǎn)單地使用單例類名作為限定符來(lái)調(diào)用它,就好像Java中的靜態(tài)方法一樣。Kotlin的伴隨對(duì)象提供的一個(gè)強(qiáng)大功能是它也能夠像任何其他對(duì)象一樣從基類繼承,從而實(shí)現(xiàn)與僅靜態(tài)繼承相當(dāng)?shù)墓δ堋?/p>

在這種情況下,我們希望使用SingletonHolder作為單例類的伴隨對(duì)象的基類,以便在單例類上重用并自動(dòng)公開(kāi)其getInstance()函數(shù)。

對(duì)于SingletonHolder類構(gòu)造方法中的creator參數(shù),它是一個(gè)函數(shù)類型,您可以聲明為一個(gè)內(nèi)聯(lián)(inline)的lambda,但更常用的情況是 作為一個(gè)函數(shù)引用的依賴交給構(gòu)造器,最終其代碼如下所示:

class Manager private constructor(context:Context){
    init{
        //init using context argument
    }
    companion object SingletonHolder<Manager,Context>(::Manager)
}

現(xiàn)在可以使用以下語(yǔ)法調(diào)用單例,并且它的初始化將是lazy并且線程安全的:

Manager.getInstance(context).doStuff()

8、Nothing類

在永不返回的函數(shù)中使用Nothing類

下面兩種情況下Nothing類會(huì)自然的出現(xiàn):
1、函數(shù)主體完全由拋出異常組成;

fun doNothing():Nothing = throw Exception("Nothing at all")

當(dāng)三方庫(kù)生成單例實(shí)現(xiàn)并且Builder需要參數(shù)時(shí),您也可以使用這種方式,以下是使用Room 數(shù)據(jù)庫(kù)的示例:

@Database(entitier = arrayOf(User::class),version = 1)
abstract class UsersDatabase :RoomDatabase(){
    absract fun userDao():UserDao

    companion object :SingletonHolder<UsersDatabase,Context>({
        Room.databaseBuilder(it.applicationContext,
            UsersDatabase::class.java,"Sample.db")
            .build()
    })
}

2、將變量賦值為null而不顯示聲明類型時(shí)。

val x = null

kotlin中Nothing類是所有其他類型的子類
TODO函數(shù)返回Nothing,其實(shí)現(xiàn)就是拋出NotImplementedError

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容