一、Kotlin基礎
1、在線kotlin沙箱: https://play.kotlinlang.org/
2、在Android中使用Kotlin(Groovy在進行插值時會使用雙引號,不需要插值時也可以使用單引號)
頂層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"
}
}
應用程序目錄中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
// 其他無關依賴
}
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
//返回的結果類型是Int?
val middleNameLength = p.middle?.length ?: 0
4、安全轉換操作符 as?
避免在強制轉換時拋出ClassCastException
val p1 = p as? Persion
//p1的類型爲Persion?
轉換成功結果是一個Person,失敗值降爲null
5、顯式類型轉換
kotlin 不會自動將原生類型轉換到一個位數更大的原生類型,例如Int轉型到Long。
使用toInt、toLong等顯示轉換函數來顯示轉換位數較小的類型。
val intVar : Int =3
//val longVar: Long = intVar 不編譯該行
val longVar: Long = intVar.toLong()
kotlin利用重載操作符來透明執行了類型轉換
val longSum = 3L+ intVar
加號操作符自動將intVar的值轉換成long並相加
6、使用to 函數創建Pair實例
mapOf函數的簽名如下:
fun <k,v> mapOf(vararg pairs: Pair<K,V>): Map<K,V>
Pair簽名如下
data class Pair<out A,out B> : Serializable
to函數定義如下
public infix fun <A,B> A.to(that:B):Pair<A,B> = Pair(this,that)
實際應用
val map = mapOf("a" to 1,"b" to 2,"c" to 3)
//創建Pair兩種方式
val p1 = Pair("a",1)
val p2 = "a" to 1
二、Kotlin中的面向對象編程
初始化對象,自定義getter和setter,延遲初始化,惰性初始化,創建單例,理解Nothing類
1、const與val的不同
const修飾的是編譯時常量;val 是運行時常量
在Java程序裏,常量用關鍵字static final修飾,常量又分爲:
編譯期常量
運行時常量
static final int A = 1024;//編譯期常量,類型必須是基本類型或String!
static final int len = "Rhine".length();//運行時常量!運行時才能確定它的值。
編譯期常量不依賴類,不會引起類的初始化;而運行時常量依賴類,會引起類的初始化。
class Task (val name:String,_priority:Int = DEFAULT_PRIORITY){
companion object{
const val MIN_PRIORITY =1 //編譯時常量
const val MAX_PRIORITY = 5 //編譯時常量
const val DEFAULT_PRIORITY = 3 //編譯時常量
}
var priority = validPriority(_priority) //自定義set屬性
set(value){
field = validPriority(value)
}
private fun validPriority(p:Int) = //私有的驗證函數
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、定義數據類
使用data關鍵字,指明特定類的目的是保存數據。與java中實體類相似。
將data添加到類的定義中會使編譯器生成一系類函數,包括equals、hashCode、toString、copy、component函數
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函數僅僅執行淺拷貝,而不執行深拷貝
4、幕後屬性技術
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委託函數 實現懶加載(lazy 委託在後面小結中討論)
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進行延遲初始化
在依賴注入的情況下很有用
通常在可能情況下考慮其他替代方法,如lazy
lateinit修飾符智能修飾類中聲明的var屬性,並且該屬性不能擁有自定義的getter和setter。從1.2開始,可以修飾頂層屬性甚至局部變量,被修飾的類型必須是非空的且不能是原始類型。
class LateInitDemo{
lateinit var name : String
}
在name屬性還沒有初始化的時候訪問他會拋出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檢查一個屬性是否初始化了
lateinit 與lazy:
lateinit 修飾符用於修飾var屬性,但是它具有上面的使用限制,而lazy委託則使用lambda表達式,該lambda表達式會在首次訪問這個屬性時賦值
如果初始化非常昂貴或者屬性也許永遠不會被使用時,請使用lazy。同樣,lazy只能用於val屬性,而lateinit只能用於var屬性。最後,lateinit屬性可以在任何該屬性可見的地方初始化
6、使用安全轉換函數、恆等操作符、以及Elvis操作符覆蓋equals函數
===、as?、?:
java中==操作符用於檢查是否指向同一個對象。
kotlin中==操作符會自動調用equals函數
7、創建單例
kotlin中使用object關鍵字替代class來實現單例模式,被稱爲object聲明
object MySingleton{
val myProperty = 3
fun myFunction() = "Hello"
}
如果反編譯,會得到下面代碼:
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”討論了基於雙重校驗鎖以及@volatile 來實現單例實例化線程安全的複雜性
https://blog.csdn.net/mq2553299/article/details/87128674(國內)
簡單地聲明object以創建一個單例:
object SomeSingleton
與類不同,object 不允許有任何構造函數,如果有需要,可以通過使用 init 代碼塊進行初始化的行爲:
object SomeSingleton{
init{
println("init complete")
}
}
這樣object將被實例化,並且在初次執行時,其init代碼塊將以線程安全的方式懶惰地執行。 爲了這樣的效果,Kotlin對象實際上依賴於Java的 靜態代碼塊 。上述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上實現單例的首選方式,因爲它可以在線程安全的情況下懶惰地進行初始化,同時不必依賴複雜的雙重檢查加鎖(double-checked locking)等加鎖算法。 通過在Kotlin中簡單地使用object進行聲明,您可以獲得安全有效的單例實現。
但是,如果初始化的代碼需要一些額外的參數呢?你不能將任何參數傳遞給它,因爲Kotlin的object關鍵字不允許存在任何構造函數。
在Kotlin中,您必須通過不同的方式去管理單例的另一種情況是,單例的具體實現是由外部工具或庫(比如Retrofit,Room等等)生成的,它們的實例是通過使用Builder模式或Factory模式來獲取的——在這種情況下,您通常將單例通過interface或abstract class進行聲明,而不是object。
我們可以通過封裝邏輯來懶惰地在SingletonHolder類中創建和初始化帶有參數的單例。
爲了使該邏輯的線程安全,我們需要實現一個同步算法,它是最有效的算法,同時也是最難做到的——它就是 雙重檢查鎖定算法(double-checked locking algorithm)。
請注意,爲了使算法正常工作,這裏需要將@Volatile
註解對instance
成員進行標記。
這可能不是最緊湊或優雅的Kotlin
代碼,但它是爲雙重檢查鎖定算法生成最行之有效的代碼。請信任Kotlin
的作者:實際上,這些代碼正是從Kotlin
標準庫中的 lazy() 函數的實現中直接借用的,默認情況下它是同步的。它已被修改爲允許將參數傳遞給creator
函數。
有鑑於其相對的複雜性,它不是您想要多次編寫(或者閱讀)的那種代碼,實際上其目標是,讓您每次必須使用參數實現單例時,都能夠重用該SingletonHolder
類進行實現。
聲明getInstance()
函數的邏輯位置在singleton類的伴隨對象內部,這允許通過簡單地使用單例類名作爲限定符來調用它,就好像Java
中的靜態方法一樣。Kotlin
的伴隨對象提供的一個強大功能是它也能夠像任何其他對象一樣從基類繼承,從而實現與僅靜態繼承相當的功能。
在這種情況下,我們希望使用SingletonHolder
作爲單例類的伴隨對象的基類,以便在單例類上重用並自動公開其getInstance()
函數。
對於SingletonHolder
類構造方法中的creator
參數,它是一個函數類型,您可以聲明爲一個內聯(inline)
的lambda,但更常用的情況是 作爲一個函數引用的依賴交給構造器,最終其代碼如下所示:
class Manager private constructor(context:Context){
init{
//init using context argument
}
companion object SingletonHolder<Manager,Context>(::Manager)
}
現在可以使用以下語法調用單例,並且它的初始化將是lazy並且線程安全的:
Manager.getInstance(context).doStuff()
8、Nothing類
在永不返回的函數中使用Nothing類
下面兩種情況下Nothing類會自然的出現:
1、函數主體完全由拋出異常組成;
fun doNothing():Nothing = throw Exception("Nothing at all")
當三方庫生成單例實現並且Builder需要參數時,您也可以使用這種方式,以下是使用Room 數據庫的示例:
@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而不顯示聲明類型時。
val x = null
kotlin中Nothing類是所有其他類型的子類
TODO函數返回Nothing,其實現就是拋出NotImplementedError