一、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