柚子快報激活碼778899分享:kotlin使用教程
柚子快報激活碼778899分享:kotlin使用教程
寫在前面:現(xiàn)在工作越來越不好找,搞不好突然就會離開開發(fā)。為了在離開時候,如果想要寫kotlin還能想起來怎么寫,所以寫了一篇教程給自己。
變量表達式方法類擴展屬性及函數(shù)Function正則表達式kotlin常用的apikotlin協(xié)程和flowktx
先來一個kotlin中文站,可以用來看看kotlin新版本出了什么新特性。還可以看看b站的benny老師,他在國內(nèi)的kotlin推廣做了很多事。
main方法 在創(chuàng)建一個kotlin文件之后,不需要聲明類名就可以編寫main方法,只需
fun main() {
}
在可以在main里面編寫代碼,編寫完成后再點擊運行即可。
變量
val a = 1
var b = ""
// 下面這句代碼會編譯錯誤
// var b = 1
val a: Float = 1f
kotlin會自動推斷變量的類型,一個變量在聲明后,編譯器就已經(jīng)確定了該變量的類型,后續(xù)是無法更改變量的類型的??梢栽谧兞棵己竺媸褂?:變量類型"來指定變量類型,就像上面的Float一樣。 使用val聲明的變量不可以被重新賦值,使用var聲明的變量可以被重新賦值。 java的byte、short、int、long、char、boolean、float、double在kotlin里面,是以大寫字母開頭體現(xiàn)的,并且在kotlin里面,沒有Integer、Character類型。 在kotlin里面,一個String如果想要轉(zhuǎn)換成Int,可以使用toInt方法,toInt本質(zhì)也是調(diào)用Integer.parseInt方法。順便一提,在kotlin里面,也可以調(diào)用parseInt方法,不過需要這樣調(diào)用,java.lang.Integer.parseInt。 提到toInt,就順便提一個kotlin在聲明Int時特有的問題。如果Int的最高位是1時,Int不會將這個值視為負數(shù),而是當做Long。此時,就需要使用toInt轉(zhuǎn)換成Int。
val white:Int = 0xffffffff.toInt()
long類型和java一的點不同,在java里面,long類型可以使用大小寫L作為結(jié)尾,但小寫l總會和數(shù)字1看錯,所以kotlin不允許用小寫l,只能用大寫L。 除了基本數(shù)據(jù)有一點不同之外,大部分類型都和java一樣,因為kotlin可以直接使用java的api,但有一些還是有點不同。
Object:對應(yīng)kotlin的Any。void/Void:對應(yīng)kotlin的Unit。List:List也有一點不同。java的List有add方法,而kotlin的List是沒有的add和remove這樣的方法的。如果想要使用add或remove方法,只能聲明一個MutableList。我認為kotlin這樣設(shè)計的原因,是為了讓開發(fā)者可以提供一個只讀的List。當開發(fā)者不希望返回的List被操作時,就返回List,而如果不在乎是否被操作,就返回MutableList。
0x、0b:kotlin同樣支持在聲明數(shù)字時,用0x表示16進制,用0b表示二進制,還能用_隔開數(shù)字而不影響數(shù)值。
數(shù)組:提到List,就順便將數(shù)組說一下。在kotlin里面,如果想要使用數(shù)組,可以使用IntArray或Array。八大基本數(shù)據(jù)類型都有自己的Array,其他類型需要使用Array。Array在new時,還是比較麻煩的,以IntArray為例:
IntArray(1){ it ->
2
}
這里的1就是Array的長度,2就是要返回的值,這里的意思就是返回一個長度為1,第0個元素為2的數(shù)組。 it就是index,這里的it->可以去掉,{}默認的名稱就是it,可以修改為其他名稱,比如index,這個下面會具體說明,現(xiàn)在只要知道能這樣用即可,其他類型的數(shù)組使用的方式也是一樣。如果要問,IntArray和Array有什么區(qū)別,我記得看過相關(guān)博客,說是IntArray的性能更好,所以如果使用基本類型的數(shù)組,就使用相應(yīng)的Array,其他情況再用Array。 從上面的使用方式也可以看到,數(shù)組使用的方式特別麻煩,如果length很長,那就是需要判斷所有index,這顯然是不合理的,所以kotlin也提供了arrayOf這樣的api。有intArrayOf和arrayOf等方式。
intArrayOf(1, 2, 3)
arrayOf(1, 2, 3)
從上面可以看到初始化一個對象沒有使用new關(guān)鍵字,在kotlin里面,沒有new關(guān)鍵字,所以想要創(chuàng)建對象時不需要使用new。再補充一下,如果一行只寫一句代碼,不需要寫";“,只有一行寫多句代碼和在聲明枚舉類時,才需要用”;"。
val a = 1;val b = 1
enum class Word{
A,
B;
fun test(){
}
}
如果沒有在B后面補一個";",就沒辦法編寫其他代碼。
類型轉(zhuǎn)換,在java里面,想要判斷變量是否為某種類型可以使用instanceof關(guān)鍵字判斷,而在kotlin,需要使用“is”進行判斷。而kotlin和java有點不同,在java里面,即使結(jié)果為true,還是需要手動轉(zhuǎn)型一次,但在kotlin里面就不需要手動轉(zhuǎn)型。
val a: Any = ""
if(a is String){
a.substring(0)
}
只要為true,就可以直接使用String的api。 kotlin還有強轉(zhuǎn)的關(guān)鍵字:as。這個as和java的強轉(zhuǎn)還有點不一樣。
val a: Any = ""
val b: Int? = a as? Int
as可以在后面帶一個"?",這樣做的意思是,如果是該類型,就轉(zhuǎn)型,如果不是就返回空。 借助這個特性,可以用來判斷強轉(zhuǎn)是否成功,比如:
val a: Any = ""
(a as? Int)?.plus(2)
如果轉(zhuǎn)型成功,就+2,否則就什么都不做。
數(shù)字的位操作:在java中,有左移、右移和無符號右移,在kotlin中,不能使用"<<“、”>>“、”>>>"來操作位移,只能使用“shl”來實現(xiàn)左移、"shr"來實現(xiàn)右移,“ushr"實現(xiàn)無符號右移。還有"and"作為”&"的代替,“or"作為”|"的代替。
判斷字符串是否相等:在java里面,判斷字符串是否相等是比較麻煩的,不但要調(diào)用equals方法,還要考慮控安全。到了kotlin,就不用想那么多了,直接使用"=="就可以了。 在idea里面,可以雙擊shift搜索show?kotlin?bytecode并點擊decompile,就可以發(fā)現(xiàn),之所以使用==就可以避免很多問題,是因為kotlin做了多空的判斷。
public static boolean areEqual(Object first, Object second) {
return first == null ? second == null : first.equals(second);
}
但這樣做其實就是占用了"==“操作符,如果想要判斷兩個對象的內(nèi)存地址是否相等,需要使用三等號”==="。
字符串拼接:在java里面,字符串拼接是很麻煩的,kotlin可以使用$在"“里面拼接字符串。還能使用”{}“寫大量代碼,并在最后一行返回字符串。如果想要”$“符號,需要使用”\"轉(zhuǎn)義。
val a = 1
"a$a"
val a = 2
"a${
if(a == 1){
"b"
}else "c"
}"
除此之外,kotlin還能使用三引號。用三引號的字符串,可以保留字符串原本字符串的樣式。
println("""
123
456
""".trimIndent())
// 結(jié)果
123
456
如果把trimIndent去掉,123和456前后的空行和空格就還會保留。
空安全:kotlin還有一個空安全這個特性,有了空安全,寫代碼方便了不少??瞻踩袔追N用法
// 基礎(chǔ)用法,調(diào)用字段/方法前使用問號,只有不為空才會繼續(xù)執(zhí)行
val str: String? = "abc"
str?.length
// 如果可以保證不為空,就可以使用!!
str!!.length
// 如果在聲明時沒有寫聲明可能為空,在使用時就不用寫問號
// 下面這兩種方式在使用時都不需要使用問號
val str: String = "abc"
val str = "abc"
str.length
如果在聲明時使用了"?“,那就必須賦值,并且在用該字段時,也需要一直用”?“,就算上一句代碼用了”?",下一句還代碼還是需要使用,這是kotlin考慮到多線程的情況,反例
class MyActivity: Activity(){
private var text_tv: TextView? = null
override onCreate(){
text_tv = findViewById(R.id.text_tv)
text_tv?.text = "aaa"
text_tv?.text = "bbb"
}
}
可以看到,每次調(diào)用都需要用"?",這樣真的很麻煩,雖然也可以用also、let等方法解決,但這里先用latainit解決。 lateinit關(guān)鍵字,如果一個變量在構(gòu)建方法時為空,但會在類的某個方法初始化,就可以用lateinit關(guān)鍵字,這個在android開發(fā)中很常見。
class MyActivity: Activity(){
private lateinit var text_tv: TextView
override onCreate(){
text_tv = findViewById(R.id.text_tv)
text_tv.text = "aaa"
}
}
可以看到,用了lateinit之后,就不需要使用問號。但如果想要判斷一個字段是否初始化了,需要使用isLateinit
this::text_tv.isLateinit
kotlin還有一個語法:“?:”。這個語法很好用,可以看一下例子:
fun test(str: String?){
str ?:return
}
這里str??:return的意思就是,如果str為空就return,否則就繼續(xù)執(zhí)行。這個語法還有另一個用途:
fun test(str: String?): String{
str ?:return ""
}
如果有返回值,還能這樣用,除此之外還有:
fun test(str: String?): String{
val newStr = str ?:return ""
}
還能用來賦值,如果不為空,就將值賦給newStr,否則就返回。 有了這個特性,就不再需要編寫if(xxxx)return這樣的代碼,只需一行代碼就可以搞定。
如果java的屬性或方法在返回值上面加了@NotNull的注解,當kotlin去調(diào)用該屬性/方法時,就無需使用"?"。如果不加就可能為空,可調(diào)用可不調(diào)用。如果加了@Nullable注解,就必須使用。 相對的,如果kotlin返回一個不為空的對象,java訪問時,就會有@NotNull的注解,返回為空的對象就會有@Nullable的注解。
權(quán)限修飾符:kotlin的權(quán)限修飾符有private、protected、internal和pulbic。默認的權(quán)限修飾符是public,internal表示模塊可見。比如項目中引用了moduleA,這個module里面某些類或方法使用了internal修飾符,在當前module就用不了。kotlin里面沒有java中包可見的修飾符。
靜態(tài)變量:kotlin種沒有static修飾符,想要使用靜態(tài)變量只能使用伴生對象。具體用法:
class Test {
companion object {
// const表示編譯期常量,也就是說,該變量的值必須在編譯時就可以確定的
// 使用了const修飾符的變量,在java中可以正常調(diào)用。比如這里就是:Test.VALUE_1
const val VALUE_1 = 1
// 如果沒有使用const修飾符,就變成了這樣:Test.Companion.getVALUE_2()
val VALUE_2 = 2;
// 此時,可以使用JvmStatic注解,但調(diào)用起來還是不好看,Test.getVALUE_3()
@JvmStatic
val VALUE_3 = 3
// 如果使用JvmField注解,就和VALUE_1一樣了,Test.VALUE_4。
@JvmField
val VALUE_4 = 4
}
}
如果不是編譯期常量,又必須被java調(diào)用,才有必要加上注解。如果不是編譯期常量,而只被kotlin調(diào)用,那去掉const修飾符就行,就像VALUE_2。在kotlin中調(diào)用VALUE_2也只是:Test.VALUE_2。 可能有人會覺得kotlin特意搞這么一個出來反而更麻煩,和java的static沒有區(qū)別,但實際開發(fā)下來給我的感受就是,使用了伴生對象之后,就可以將靜態(tài)變量全部放在伴生對象里面。在java里面,只要使用了static就可以變成靜態(tài)變量,這就很容易在開發(fā)中,將靜態(tài)變量和非靜態(tài)變量寫在一起,這加大了維護的難度。而在kotlin里面,想要找靜態(tài)變量只需到本省對象里面找就可以了,不用整個類文件找一遍。
泛型:kotlin的泛型和java泛型基本一樣,只是換了新的寫法和多了reified關(guān)鍵字?;居梅ǎ?/p>
class Test
// 在使用時,可以指定泛型的類型
Test
// 如果該類或者方法有某個參數(shù)使用到該泛型,并且傳值了,可以省略泛型
Test(1)
泛型的邊界:kotlin可以使用
class Test
Test("")
// 如果給泛型邊界加上"?",在傳參時還能傳null,并且不需要在T旁邊寫"?"
class Test
Test(null)
如果有多個邊界,可以使用where關(guān)鍵字。多個邊界只能有一個是類,其他的只能是接口,并且類必須放在最前面
class Test
在kotlin中,如果聲明了變量類型,在調(diào)用返回值為泛型的方法時,可以不用寫泛型,這在安卓開發(fā)中很常見
// 聲明了text_tv的類型時
val text_tv: TextView = findViewById(R.id.text_tv)
// 如果沒有聲明text_tv的類型,就必須指定泛型
val text_tv = findViewById
真實的泛型,java中的泛型是偽泛型,而kotlin提供了reified關(guān)鍵字還實現(xiàn)真實的泛型。不過所謂真實的泛型,本質(zhì)是通過代碼內(nèi)聯(lián)還實現(xiàn),并沒有脫離java偽泛型。
// 一般情況下,啟動Activity需要使用MainActivity::class.java來指定class對象
startActivity(Intent(this, MainActivity::class.java))
// 聲明一個真實泛型的startActivity
inline fun
startActivity(Intent(context, T::class.java))
}
// 使用
startActivity
可以看到,使用了reified關(guān)鍵字之后,就可以直接使用T的class。在聲明reified泛型時,必須在方法前面寫上inline關(guān)鍵字,這個關(guān)鍵字的作用下面會講。
協(xié)變和逆變
協(xié)變:kotlin的
無邊界通配符,kotlin使用<*>來表示無邊界通配符。
class Test
val test: Test<*> = Test(1)
同步代碼塊: 在kotlin里面,如果想要使用同步代碼塊,沒辦法像java一樣在聲明方法時寫上synchronized關(guān)鍵字,只能在需要的地方使用synchronized方法,傳入一個鎖對象并編寫對應(yīng)的代碼。
synchronized(Test::class){
}
匿名函數(shù):只要使用kotlin,就會無時無刻在使用匿名函數(shù),匿名函數(shù)是kotlin非常重要的特性,先看怎么聲明。
// 聲明
val function: (Int) -> Unit = {
}
// 使用
function(1)
function.invoke(1)
(Int)?->)?Unit。這里的Int就是第1個參數(shù)的類型,Unit就是返回類型,匿名函數(shù)也沒有任何參數(shù)也可以有多個參數(shù),沒記錯的話,kotlin最多支持聲明22個參數(shù)的匿名函數(shù)。在調(diào)用時,可以直接fucntion(…),也可以調(diào)用invoke方法。 在function里面,想要使用Int這個參數(shù)有2種方式:
val function: (Int) -> Unit = {
// it就是參數(shù)默認的名稱
it + 1
}
// 也可以使用 xxx -> 這種方式來命名
val function: (Int) -> Unit = { i ->
i + 1
}
如果有1個以上的參數(shù),就必須顯示聲明參數(shù)名稱:
// 如果沒有顯示聲明參數(shù)的名稱,編譯會報錯
val function: (Int, String) ->Unit = {
}
// 有多個參數(shù)名稱時,用","隔開
val function: (Int, String) ->Unit = { i, s ->
}
還能給參數(shù)提供一個名稱:
// 在類型前面寫名稱,這樣在寫到 Unit = {之后,編譯器就會提醒參數(shù)的默認名稱
val function: (i: Int, s: String) ->Unit = { i, s ->
}
匿名函數(shù)也可以為空
// 在外部套一個括號,并加一個“?”,就可以聲明一個空的匿名函數(shù)
var function: ((Int) -> Unit)? = null
// 再在其他地方初始化這個參數(shù)
function = {
}
上面基本就講完匿名函數(shù)的聲明方式,但有什么用?看一看forEach的代碼就知道有什么用了
public inline fun
val list = listOf(1, 2, 3)
list.forEach { }
可以看到,forEach方法有一個action參數(shù),這個參數(shù)的結(jié)構(gòu)和上面提到的聲明方式是一樣的。 在kotlin中之所以可以使用list.forEach這種方式來遍歷一個list,就是因為kotlin提供了這么一個方法。而且kotlin的標準庫里面在,這樣的代碼還有很多,下面講到kotlin標準庫時,就講有哪些常用的方法。 并且日常開發(fā)中,我們也可以根據(jù)自己的需要,定義大量的匿名函數(shù)來解決我們的需求。
typealias:可以給一個匿名函數(shù)命名,比如:
typealias OnParentClickListener = (parentPosition: Int) -> Unit
typealias OnChildClickListener = (parentPosition: Int, childPosition: Int) -> Unit
使用
private var onParentClickListener: OnParentClickListener? = null
private var onChildClickListener: OnChildClickListener? = null
holder.itemView.setOnClickListener {
onParentClickListener?.invoke(parentPosition)
}
holder.itemView.setOnClickListener {
onChildClickListener?.invoke(parentPosition, childPosition)
}
元組:元組的概念我就不寫了,不懂的查一下百度。我沒有使用過其他編程語言,所以不清楚其他編程語言的元組要怎么用,所以不知道kotlin使用元組的方式是否和其他編程語言一樣,我就是說一下怎么在kotlin里面使用元組。 先看一簡單用法
val (v1, v2) = 1 to "a"
這里的to會將1和a變成了一個Pair對象,該對象是一個data?class,data?class會為當前類生成component1、component2…componentN的方法,來實現(xiàn)元組的功能。 這里的v1的類型就是Int,v2就是String。 這樣有什么用?元組的寫法使用有用不同的人有不同的看法,但Pair對象絕對有用,有時我們只是需要一個對象來存儲某個幾個值,沒到非要聲明一個對象不可,此時Pair可能就夠用了。 kotlin還提供了Triple還聲明元組,但Triple沒有to這樣的方法,需要手動創(chuàng)建。
val (v1, v2, v3) = Triple(1,"a",true)
然后有一點需要注意,這里的v1、v2和v3雖然用了括號,但還是屬于當前大括號的作用域,所以這行代碼的上下都不能有v1這樣的變量,否則就有變量重名的問題。 再看看兩種自定義元組的方式:
fun main() {
val test = Test()
val (v1, v2, v3, v4, v5, v6, v7) = test
}
class Test {
operator fun component1() = 1
operator fun component2() = "a"
operator fun component3() = true
operator fun component4() = 1
operator fun component5() = 1
operator fun component6() = 1
operator fun component7() = 1
}
只需在前面寫operator并且方法名稱是componentN就可以。
fun main() {
val test = Test("","",0L,0L)
val (v1, v2, v3, v4, ) = test
data class Test(val name: String, val company: String, val startDate: Long, val stopDate:Long)
data?class也可以,data?class下面會重點介紹,這里只是提到元組,才寫出來。
屬性委托:屬性委托使用by關(guān)鍵字,通過屬性委托,就能將任務(wù)交給委托的對象來執(zhí)行。kotlin提供了ReadOnlyProperty和ReadWriteProperty用聲明屬性委托,好像還有其他方式,但我不會用,不過只要知道用by就是屬性委托就行了。來一個lazy看看屬性委托有什么用:
class MyActivity: Activity(){
val tex_tv by lazy{
findViewById
}
override fun onCreate(savedInstanceState: Bundle?) {
text_tv.text = "aaa"
text_tv.text = "bbb"
}
}
上面的by?lazy意思是延遲加載,在聲明時,不會調(diào)用lazy里面的findViewById,只有在第一次使用時才會調(diào)用,第二次使用則不會。onCreate里面的代碼,在第一次t調(diào)用text_tv時,發(fā)現(xiàn)這個變量為空,所以就調(diào)用lazy里面的代碼初始化text_tv,第二次調(diào)用text時,text_tv已經(jīng)初始化完成,所以就不會調(diào)用了。具體可以看看lazy里面的代碼,里面的代碼邏輯并不復雜。 上面還提到ReadOnlyProperty和ReadWriteProperty這種方式,我們可以自定義屬性委托,將一些重復任務(wù)交給委托類來執(zhí)行,拿我之前在工作的例子:
// 繼承ReadWriteProperty,這個類有兩個泛型,第一個是this,也就是要使用的外部類,第二個是屬性類型,這里CharSequence也就是String
class ContentDescriptionValueDelegate: ReadWriteProperty
// 獲取屬性的值,第一個參數(shù)是thisRef,類型就是第一個泛型,返回值的類型是第二個泛型
// 可以看到,這里的代碼等于同view.getContentDescription().toString,只要記住返回值是這樣就行,該方法的作用下面會講
override fun getValue(thisRef: View, property: KProperty<*>): CharSequence? = thisRef.contentDescription?.toString()
// 這里就是設(shè)置值,調(diào)用委托的變量設(shè)置值,最終會執(zhí)行這里的代碼
// 這里的代碼就是view.setContentDescription(value),并且還調(diào)用了getParent執(zhí)行了其他代碼
override fun setValue(thisRef: View, property: KProperty<*>, value: CharSequence?) {
thisRef.apply {
contentDescription = value
parent?.requestSendAccessibilityEvent(this, AccessibilityEvent.obtain(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION).also {
it.contentDescription = value
})
}
}
}
class AccessibilityTestLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0):
LinearLayout(context, attrs, defStyleAttr) {
// 這里的CharSequence?就是上面的第二個泛型,其實:CharSequence?可以省略。
// 可以看到,使用了屬性委托之后,該變量的返回值是CharSequence,而不是ContentDescriptionValueDelegate
// 而在new ContentDescriptionValueDelegate時,也不需要將this傳進去,只要外部類是第一個泛型就行
var contentDescriptionValue :CharSequence? by ContentDescriptionValueDelegate()
}
// MainActivity
val accessibilityTestLayout = AccessibilityTestLayout(this)
// 再看看實際使用,既然contentDescriptionValue的類型是CharSequence,那當然可以直接賦值
// 但實際上,這里編譯器幫我們干了很多事,實際編譯出來的代碼會調(diào)用setValue方法,但這無所謂
// 雖然這是語法糖,但好用就不行,只要知道這個過程中發(fā)生了什么就行
// 所以這行代碼就調(diào)用了setContentDescription方法和getParent...方法
accessibilityTestLayout.contentDescriptionValue = ""
// 如果沒有賦值,就相當于調(diào)用了getValue方法,最終就會執(zhí)行上面的view.getContentDescription()方法
// 而對于這里來說,只要調(diào)用contentDescriptionValue這個屬性就行,非常方便。
accessibilityTestLayout.contentDescriptionValue
再說一下上面這些代碼的作用,這樣以后遇到重復代碼時,才會考慮是否應(yīng)該用屬性委托去掉重復代碼。 Delegate的setValue的作用就是設(shè)置了contentDescription并通知Parent?View自己的contentDescription更新了。 這里如果不這樣做,也可以寫工具類去做,但不管怎么樣都要有一個方法來統(tǒng)一處理,否則就需要編寫重復代碼。此時,屬性委托就是方式之一。而具體要用什么方式,就具體問題具體分析。 再看看一個安卓相關(guān)的應(yīng)用:
class MyOnClickListener(): View.OnClickListener{
override fun onClick(v: View?) {
}
}
val aaOnClick = MyOnClickListener()
class CoroutinesTestActivity : AppCompatActivity(), View.OnClickListener by aaOnClick {
}
可以看到Activity的onClick交給了aaOnClick這個外部變量去做。如果在開發(fā)中,多個控件的點擊事件由一個類去做,代碼就可以這樣寫。
表達式
if表達式:在kotlin里面,沒有三元運算法,取而代之的是if表達式
val result = if(flag) 1 else 0
val result = if(flag) {
code...
1
} else {
code...
0
}
val result = if(flag){
code...
1
} else 0
kotlin的if-else,可以將最后一行作為返回值
for:kotlin的for和java完全不一樣,kotlin的for循環(huán)本質(zhì)是執(zhí)行forEach,至于為什么這樣說留到擴展方法講。下面是幾種for的用法:
// .. 表示i的取值范圍為[0, 10]
for(i in 0.. 10){}
// until 表示i的取值范圍為[0, 10)
for(i in 0 until 10){}
// step 2 表示每次遞增2,所以這里的值是0 2 4 6 8 10
for(i in 0.. 10 step 2){}
// 上面這三種方式都是遞增
// 下面這兩種方式都是遞減,10 downTo 0的取值范圍內(nèi)是[10, 0]
// stop 2就是每次遞減2
for(i in 10 downTo 0){}
for(i in 10 downTo 0 step 2){}
val list = listOf(1, 2, 3)
// 這里就是遍歷list所有的元素
for(item in list){}
// 也可以使用list的forEach擴展方法
list.forEach { }
when:kotlin用when代替了java的swich,kotlin的when比java的switch好用很多,語法不再那么啰嗦,功能也變得更強大。
val month = 1
var monthStr = ""
when(month){
1 ->{
monthStr = "一"
}
2 -> monthStr = "二"
3,4 -> monthStr = "3"
}
這是基礎(chǔ)用法,可以看到,可以寫大括號,也可以不寫,如果只有一行代碼,可以直接不寫。再看3,4那里,意思是,如果是3或4,就執(zhí)行后面的代碼。但這種方式還是太麻煩了,可以簡寫:
val monthStr = when(month){
1 ->{
"1"
}
2 -> "2"
3,4 -> "3"
else -> "0"
}
直接在when前面聲明monthStr,但這種方式就必須將額外情況寫出來。最后的else就是java?switch的defalut??梢钥吹?,也支持多種形式,可以像if那樣將最后一行返回。 java的switch還支持String類型,而kotlin是支持在when使用所有類型。不過有一點需要提前說清楚,kotlin雖然支持所有類型,但編譯出來的代碼可能會變成if-else,沒有switch該有的性能。但這無所謂,因為會編譯出這樣的代碼,說明本身就沒辦法用switch語法。而用kotlin的when,比較大的作用是讓代碼邏輯更加清晰,從而不用在一堆if-else里面尋找目標代碼。 就比如上面的例子,在第2行就返回,看代碼的人一看,就知道when里面是在獲取實際的值,如果不關(guān)心代碼細節(jié),看到這樣的代碼之后,就不用看when里面的代碼,而是看其他代碼。后面如果關(guān)心代碼實現(xiàn),再回頭看when里面的代碼也不遲。
val any: Any = ""
val str = when(any){
is String -> "String"
is Int -> "Int"
else -> "unknow"
}
// 上面這種寫法,還能這樣
val str = when{
any is String -> "String"
any is Int -> "Int"
else -> "unknow"
}
// 將when括號里面的代碼去掉,然后將any寫到判斷那里
// 既然可以將when括號的內(nèi)容去掉,那就還能直接拿when當if-else使用
// 此時會自上而下匹配,這里雖然有兩個結(jié)果為true,但state1的判斷放在state2上面,所以會返回state1的結(jié)果,不會返回state2的結(jié)果
val state1 = "s1_1"
val state2 = "s2_1"
val result = when{
state1 == "s1_1" -> "s11"
state2 == "s2_1" -> "s22"
else -> "error"
}
可以看到,代碼這樣寫了之后,就更加簡潔了,而不用寫一堆if-else。
try-catch:try-catch也可以將最后一行返回,具體如下:
val numberStr = "1"
val number = try {
numberStr.toInt()
}catch(e: Exception){
0
}
這里例子就是嘗試將String轉(zhuǎn)換為Int,如果轉(zhuǎn)換失敗就會拋異常執(zhí)行catch的代碼,此時可以寫0作為返回值。
方法 kotlin的方法聲明和使用跟java的不一樣。
// 這個方法的返回值是Unit
fun test1(){}
// 寫了:Int就表示返回值是InT
fun test1(): Int{}
// 可以在下面寫return
fun test1(): Int{
return 1
}
// 也可以不寫大括號,直接在=并寫返回值
fun test1(): Int = 1
// 還能去掉返回類型,直接寫返回值,kotlin會自動推斷返回類型
fun test1() = 1
// 這種用法再結(jié)合if或when,就能代碼看起來非常簡潔
fun test(state: String) = when(state){
"success" -> 0
"error" -> 1
else -> 2
}
方法重寫:kotlin方法重寫時,必須寫override關(guān)鍵字,否則會編譯報錯,而不是像java那樣,寫一個可有可無的注解。
override fun onCreate(savedInstanceState: Bundle?) {}
參數(shù)默認值:kotlin可以給方法的參數(shù)設(shè)置默認值:
fun test(i: Int, s: String = "")
// s設(shè)置了默認值,所以只需要傳第一個參數(shù)就可以了
test(2)
fun test(i: Int =1, s: String = "")
// 如果兩個都設(shè)置了默認值,就不需要傳參數(shù)了
test()
fun test(i: Int =1, s: String )
// 如果第一個設(shè)置了默認值,并且不想傳值,就必須寫明要傳值的變量,比如這里就是s
test(s = "1")
fun test(i: Int, s: String)
// 即使沒有使用參數(shù)默認值,也可以通過指定參數(shù)名稱來改變傳參順序
test(s = "1", i = 1)
可變數(shù)組:kotlin的可變數(shù)組和java的一樣,只是多了參數(shù)默認值之后,有一點不一樣:
// 使用vararg來聲明可變參數(shù)
fun test(i: Int, s: String, vararg intArray: Int)
// 正常情況下,使用起來和java一樣
test(1, "a", 1,2,3)
// 將可變數(shù)組聲明在第一個參數(shù)
fun test( vararg intArray:Int,i: Int ,s:String)
// 想給i傳值,就必須使用i =,s同理
test(1, 2, 3, i = 1,s = "a",)
// 將可變數(shù)組聲明在第二個參數(shù)
fun test( i: Int ,vararg intArray:Int,s:String)
// 第一個值會被視為i,并且也沒辦法通過i = 來給i設(shè)值
test(1, 2, 3,s = "a")
方法引用,kotlin也可以使用方法引用,用法和java差不多:
class Test{
fun test(){
Thread(::a)
}
fun a(){}
}
這里Thread需要一個Runnable對象,此時直接傳入a的方法引用。
匿名函數(shù):在方法里面聲明匿名函數(shù)有一些特殊的情況。如果將匿名函數(shù)方在方法的最后一個參數(shù),就可以寫在括號外部。如果只有一個參數(shù),并且這個參數(shù)是匿名函數(shù),括號都可以省略,這個可以看到List的forEach方法:
public inline fun
// 在調(diào)用forEach方法時,不需要寫括號
listOf(1).forEach { }
// 如果還有其他參數(shù),那就必須寫括號
public inline fun
// 可以看到,mapTo第一個參數(shù)不是匿名函數(shù),第二個才是,此時括號就沒辦法省略
val list =ArrayList
listOf(1).mapTo(list){}
如果有多個匿名函數(shù),或者匿名函數(shù)不是作為最有一個參數(shù),就只能將匿名函數(shù)寫在括號里面:
fun test(action1: ()-> Unit, action2:()->Unit){}
test({}){}
fun test(action: () -> Unit, i: Int) {}
test({}, 1)
SAM轉(zhuǎn)換:在java8之后,單一方法的接口稱為SAM(Single?Abstract?Method)接口,java8通過Lanbda可以大大簡化對SAM的調(diào)用,kotlin也是一樣的。假設(shè)要設(shè)置onClick,可以有:
// 使用object關(guān)鍵字創(chuàng)建匿名內(nèi)部類
test_btn.setOnClickListener(object: View.OnClickListener{
override fun onClick(v: View?) {
}
})
// 使用SAM簡化
test_btn.setOnClickListener(View.OnClickListener{
})
// 簡化
test_btn.setOnClickListener({
})
// 再簡化
test_btn.setOnClickListener{}
將匿名函數(shù)作為返回值:
fun main() {
test().invoke()
test()()
}
fun test(): () -> Unit{
return {
println("test")
}
}
如果有多個參數(shù):
fun test(): (Int, String) ->Unit{
return { i, s ->
println("test")
}
}
還能:
val action = {}
fun test(): () ->Unit{
return action
}
函數(shù)是kotlin的一等公民:在java中,不能直接在文件里面定義方法,而在kotlin里面,就可以直接定義方法和變量。比如:
public inline fun
如果點擊看listOf的源碼,就會發(fā)現(xiàn)listOf沒有依托于任何外部類,而是在kt文件獨立存在的,kotlin存在大量的這樣的方法。
// 變量也可以獨立存在
public val
inline:用inline修飾方法,方法就會變成一個內(nèi)聯(lián)方法,修飾類就會變成一個內(nèi)聯(lián)類。如果一個方法帶有inline關(guān)鍵字,在編譯完成后,該方法所有的代碼會出現(xiàn)在調(diào)用的方法里面,而不只是調(diào)用該方法。怎么理解?就拿上面的listOf舉例:
listOf
上面的listOf由于沒有傳參數(shù),所以調(diào)用的是emptyList,所以這里也不傳參數(shù),然后看看編譯后的代碼。
public final class TestKt {
public static final void main() {
CollectionsKt.emptyList();
}
public static void main(String[] var0) {
main();
}
}
編譯后就是直接調(diào)用emptyList,這就是inline的作用。inline可以減少代碼中的方法調(diào)用,不過這也會導致編譯后的代碼比編寫的代碼多得多。再看看forEach編譯后的代碼:
listOf(1,2,3).forEach {
println(it)
}
編譯后:
public static final void main() {
Iterable $this$forEach$iv = (Iterable)CollectionsKt.listOf(new Integer[]{1, 2, 3});
int $i$f$forEach = false;
Iterator var2 = $this$forEach$iv.iterator();
while(var2.hasNext()) {
Object element$iv = var2.next();
int it = ((Number)element$iv).intValue();
int var5 = false;
System.out.println(it);
}
}
可以看到,并沒有調(diào)用forEach方法,而是創(chuàng)建一個Iterator對象,再從Iterator里面遍歷出所有對象。
infix關(guān)鍵字:infix關(guān)鍵字可以用來實現(xiàn)中綴函數(shù),先看一個kotlin官方提供的中綴函數(shù)。
public infix fun A.to(that: B): Pair = Pair(this, that)
// 使用
val pair :Pair
可以看到,在調(diào)用to方法時,不需要使用括號,這就是infix關(guān)鍵字的作用。使用infix定義的方法,必須有且只有一個參數(shù),是否有返回值都有可以。這個特性在日常開發(fā)中用得比較少,但偶爾可能有作用,所以提出來。
operator關(guān)鍵字:使用operator修飾的方法,可以使用kotlin提供的預定義符號進行調(diào)用,而不用使用方法名稱。因此,在調(diào)用時,不需要使用括號。一個常見的例子:
listOf(1)[0]
這里的[0]就是預定義操作符之一,對著方括號的左邊按住ctrl鍵+鼠標左鍵就可以發(fā)現(xiàn),可以查看這個方括號的源碼,源碼是:
public operator fun get(index: Int): E
這個方法有operator關(guān)鍵字,名稱是get。前面說了,operator可以使用預定義的操作符,同樣的,也只能使用預定義的名稱。具體有: 一元操作符:
符號方法名稱“+”unaryPlus“-”unaryMinus“!”not
算數(shù)操作符:
符號方法名稱“+”plus“-”minus“*”times“/”div“%”rem
比較操作符:
符號方法名稱”==“ / “!=”equals“<”/“<=”/“>”/“>=”“compareTo”
集合操作符:
符號方法名稱“[]”get“[]”set“in”contains“…”rangeTo
"…"表示的是一個區(qū)間,取值范圍為:[n, m]。
一些例子:
// unaryMinus:這是compose的一個例子。
value class Dp(val value: Float) : Comparable
...
inline operator fun unaryMinus() = Dp(-value)
}
// 使用
-Dp(1f)
-1.dp
// "not"雖然也是一個操作符,但開發(fā)中用得比較少,更多的是直接調(diào)用not方法
// 使用”not“,就不用在編寫完代碼之后還將光標提到前面寫"!"
// 而且“not"在任何地方都可以用,只要想要取反,都可以調(diào)用"not"
val str = "str"
str.isEmpty().not()
// 算數(shù)操作符我在18年開發(fā)時用過一次,需要對經(jīng)緯度做加減乘除,所以我定義了下面這些方法
class LatLng(val lat: Float, val lng: Float)
operator fun LatLng.plus(latLng: LatLng): LatLng{
return LatLng(lat + latLng.lat, lng + latLng.lng)
}
operator fun LatLng.minus(latLng: LatLng): LatLng{
return LatLng(lat - latLng.lat, lng - latLng.lng)
}
// 這樣就可以對經(jīng)緯度做加減操作了
LatLng(1f, 1f) + LatLng(2f, 2f)
// 比較操作符最常見的例子就是String的equals操作,當使用 == 判斷字符串是否相等時
// 就是在調(diào)用equals這個operator方法在判斷
// compareTo也是一個很實用的操作符,而實際上,一個類只要實現(xiàn)了Comparable,就可以直接使用"<" ">"等操作符來比較,而不用使用operator關(guān)鍵字
data class LatLng(val lat: Float, val lng: Float): Comparable
override fun compareTo(other: LatLng): Int {
return 0
}
}
LatLng(1f, 1f) < LatLng(2f, 2f)
// 再看一個比較有用的例子,通過下面這種方式,就可以比較兩個BigDecimal,而不需要調(diào)用compareTo方法
BigDecimal.ZERO <= BigDecimal.ONE
// BigDecimal還有plus的operator,所以可以
BigDecimal.ZERO + BigDecimal.ONE
// 數(shù)組操作符也很實用,最常見的例子當然是最集合的操作,還能用這種方式對String進行操作
// 想要獲取某個字符,不再需要調(diào)用indexOf,而是像在使用數(shù)組那樣操作
val str = "str"
str[0]
// ".."符號可以用來聲明一個IntRange
val range = 1 .. 3
// 只是用".."確實沒什么用,但如果再加上"in"操作符就會變成很好用
// 再借助“in”操作符,就可以輕松的判斷一個數(shù)字是否在某個區(qū)間里面
// 如果是java,還需要判斷是否大于等于1,小于等于3
val result = 1 in 1..3
類 在聲明一個類時,如果沒有參數(shù),就不需要寫(),如果有就必須寫,這個下面會講清楚,先來將繼承和實現(xiàn)說清楚。繼承或?qū)崿F(xiàn)一個類,都是用":"。比如:
// 繼承一個類必須后邊寫上括號,如果有參數(shù)就必須傳參數(shù),比如:AppCompatActivity()
// 如果有多個繼承/實現(xiàn)類,使用","隔開。這里的CoroutineScope是一個接口
class CoroutinesTestActivity : AppCompatActivity(), CoroutineScope
// 這個類就有多個參數(shù),所以必須使用括號。在括號里面通過xx: XX這樣就可以聲明一個參數(shù)
// 在將參數(shù)傳給被繼承類時,就可以使用這些參數(shù)
// 使用 @JvmOverloads 就可以讓這個類生成多個構(gòu)造方法,而不用寫多個構(gòu)造方法
class AccessibilityTestLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: LinearLayout(context, attrs, defStyleAttr)
// 也可以使用constructor聲明多個構(gòu)造方法
class MyView : View{
constructor(context: Context):super(context){
val a = 1
}
constructor(context: Context, attrs: AttributeSet?):super(context, attrs){
val b = 1
}
}
// 設(shè)置構(gòu)造方法的可見性
// 可以通過這樣將構(gòu)造方法設(shè)置為private
class MyView private constructor(context: Context): View(context)
// 或者這樣
class MyView : View{
private constructor(context: Context):super(context){
val a = 1
}
}
// 如果在聲明類時就聲明了構(gòu)造方法,那要怎么初始化類的參數(shù)?使用init關(guān)鍵字
class MyView constructor(context: Context) : View(context) {
init {
val a = 1
}
}
// 在聲明時順便聲明成員變量
// 只要在變量前面加val/var,這個變量就是一個成員變量,可以在類的任何地方使用
class Person(val name: String, val age: Int)
// 如果不希望被外部訪問,還能加上權(quán)限修飾符
class Person(private val name: String, private val age: Int)
// 抽象類的聲明方式和java是一樣的
abstract class BasePerson(private val name: String, private val age: Int)
open關(guān)鍵字:kotlin在聲明類時,類會有一個final的修飾符,這使得聲明的類沒辦法被繼承,如果希望一個類被繼承,需要使用open關(guān)鍵字。成員變量和方法也是一樣,如果希望一個方法能夠被重寫,也必須使用open關(guān)鍵字。
class BaseTest{
}
class Test1: BaseTest(){
}
如果沒有使用open關(guān)鍵字,編譯會報錯:This type is final, so it cannot be inherited from. 在這句提示下面,還有一個可以點擊的Button,內(nèi)容為:Make ‘BaseTest’ ‘open’。
open class BaseTest{
}
class Test1: BaseTest(){
}
加了open之后就沒問題了。 成員變量和方法
open class BaseTest{
open val a = 1
open fun test(){
}
}
class Test1: BaseTest(){
override val a = 2
override fun test(){
}
}
inner關(guān)鍵字:在kotlin里面,內(nèi)部類默認為靜態(tài)內(nèi)部類,所以不會持有外部類的引用,如果希望這個類變成非靜態(tài)內(nèi)部類,需要使用inner修飾符,最常見的就是在Activity里面創(chuàng)建Hander。 如果沒有使用inner,引用外部類的變量會編譯報錯。
var a = 1
class MyHandler: Handler(){
override fun handleMessage(msg: Message) {
a++
}
}
在class前面加一個inner就沒問題了。
var a = 1
inner class MyHandler: Handler(){
override fun handleMessage(msg: Message) {
a++
}
}
一個文件編寫多個class:在java里面,一個類文件只能有一個public類和多個沒有public的類。而kotlin就可以在一個kt文件里面定義多個class,并且還能定義方法。
接口方法的默認實現(xiàn):java1.7的接口方法是不允許編寫代碼的,只有在java8才能在接口方法里面編寫代碼,但也必須使用default關(guān)鍵字。在kotlin里面,在定義接口方法的同時,也可以直接提供方法實現(xiàn)。
data?class,data?class可以用來聲明一些實體類,使用了data?class之后,就會自動生成hashCode和equals方法,還會提供componentN方法,先看看怎么用:
// 這樣會編譯報錯,構(gòu)造方法必須有參數(shù)
data class Person()
data class Person(val name: String, val age: Int)
println(Person("a",1))
// 打印結(jié)果:Person(name=a, age=1)
data?class還會自動生成深拷貝的方法,如果想要深拷貝一個對象,就不用自己手動編寫長長的拷貝代碼。
val p1 = Person("str",1)
val p2 = p1.copy()
val p3 = p1.copy("str2")
在深拷貝時,還能為某個變量重新賦值。
如果使用show kotlin bytecode就會發(fā)現(xiàn),短短的一行代碼,kotlin就會生成很多代碼,這里我就不將代碼貼出來了。
變量的get/set方法,在kotlin里面,可以給變量設(shè)置get和set方法,這樣就不用編寫額外的方法,而且用起來也比較方便。
class Test {
// 如果是val,就知只能重寫get方法,不能重寫set方法
val result1: Boolean
get() {
println("result1 set")
return System.currentTimeMillis() % 2 == 0L
}
val result2: Boolean
get() = System.currentTimeMillis() % 2 == 0L
var result3: Boolean = false
// field就是變量本身,通過field = value這種方式對變量賦值
set(value) {
if(field == value) {
return
}
field = value
}
get() = field
}
main(){
val test = Test()
println(test.result1)
test.result3 = true
println(test.result3)
}
// result1 set
// false
// result3 get
// true
而這個特性還不止于此,在kotlin使用java的get/set方法時,也能變成像在使用變量一樣。
// 這兩行代碼本質(zhì)上就是在調(diào)用TextView的setText和getText方法
test_tv.text = "aaa"
val txt = test_tv.text
而如果一個只有g(shù)et方法,而沒有set方法,也能使用這個特性
// java code
public class JavaTest {
public int getA(){
return 1;
}
}
// kotlin code
val test = JavaTest()
val a = test.a
// 由于沒有set方法,所以不能這樣用,否則會編譯報錯
// test.a = 1
object關(guān)鍵字,object關(guān)鍵字有兩個用途,一個是聲明匿名內(nèi)部類,另一個是聲明單例對象。先看匿名內(nèi)部類:
// 想要聲明匿名內(nèi)部類就只能用object關(guān)鍵字。如果父類是一個類就補上括號,如果是接口就不用
val click = object :View.OnClickListener{
override fun onClick(v: View?) {
}
}
// 這里的onClick還能簡寫成
val click = OnClickListener { }
聲明一個單例對象:
object Single{}
// 聲明成單例對象之后,甚至可以寫出這種意義不明的代碼,只是調(diào)用該對象,但什么都不做
Single
object Single{
fun test(){
}
}
// 不需要聲明伴生對象,就可以使用調(diào)用test方法
Single.test()
// 變量的用法也一樣
fun main() {
Single.VALUE_1
Single.VALUE_2
}
object Single {
val VALUE_1 = "v"
// 聲明編譯期常量也不用在伴生對象里面聲明
const val VALUE_2 = "v"
}
密封類:使用sealed聲明。密封類和枚舉有點像,但又不完全相同。密封類的子類可以攜帶自己獨有的狀態(tài)參數(shù)以及行為方法來記錄更多的實現(xiàn)信息以完成更多的功能,而枚舉類的參數(shù)只能和聲明類一樣。 沒有使用密封類時:
abstract class NetworkCode(val code: Int)
class SuccessCode: NetworkCode(0)
class ErrorCode: NetworkCode(1)
val networkCode: NetworkCode = SuccessCode()
val networkState = when(networkCode){
is SuccessCode -> "success"
is ErrorCode -> "failed"
else -> "failed"
}
可以看到,還需要寫else,否則就會編譯報錯 如果使用密封類,就不用寫else了。并且構(gòu)造方法可以隨意定義,不需要像枚舉那樣只有固定的構(gòu)造方法。
sealed class NetworkCode(val code: Int)
class SuccessCode(val str: String): NetworkCode(0)
class ErrorCode(val i: Int): NetworkCode(1)
val networkCode: NetworkCode = SuccessCode("")
val networkState = when(networkCode){
is SuccessCode -> "success"
is ErrorCode -> "failed"
}
inline?class和value?class:inline?class可以用來對一個某個變量的行為進行封裝,并且還能避免創(chuàng)建對象。比如:
fun main() {
val age = Age(-1)
age.getValidateAge()
}
inline class Age(val age: Int){
fun getValidateAge(): Int{
return if(age < 0){
0
}else age
}
fun isZero() = age == 0
}
這里聲明一個Age的inline?class,并在main方法調(diào)用??纯淳幾g后的代碼是什么:
public final class Age {
public static int constructor_impl/* $FF was: constructor-impl*/(int age) {
return age;
}
public static final int getValidateAge_impl/* $FF was: getValidateAge-impl*/(int $this) {
return $this < 0 ? 0 : $this;
}
}
int age = Age.constructor-impl(-1);
Age.getValidateAge-impl(age);
可以看到,編譯后雖然還是聲明了Age對象,但只是調(diào)用Age對象的靜態(tài)方法,并沒有創(chuàng)建Age對象。 在使用inline?class時需要注意:
inline?class必須在構(gòu)造方法聲明一個變量類型,并且也只能有一個參數(shù),參數(shù)必須使用val而不能使用varinline?class不能被繼承,因為在編譯時,inline?class會被加上final關(guān)鍵字,inline?class也不能繼承其他類運行時類型會被擦除
inline?class是在kotlin1.3版本被引入的,在kotlin1.5之后,inline?class進入穩(wěn)定版本,kotlin引入value?class,inline?class被棄用。如果在1.5以上的版本使用inline?class,會有一個warning: 'inline' modifier is deprecated. Use 'value' instead value?class對inline?class進行了一些優(yōu)化,目前,value&nsbp;class的用法和inline?class是一樣的,也只能使用一個參數(shù)。未來的kotlin版本可能會支持構(gòu)造方法多個參數(shù)的value?class。 在使用value?class時,還需要在聲明類時使用@JvmInline注解。
擴展屬性及函數(shù) 擴展函數(shù)是kotlin重要的組成部分,沒有擴展函數(shù),kotlin也就不會那么好用。
擴展屬性的語法是:
data class Person(var name: String, var age: Int, private var priateAge: Int)
val Person.aaaAge: Int get() = age
var Person.bbbAge: Int
get() = age
set(value) {
age = value
// 編譯報錯
priateAge = value
}
main(){
val person = Person("",1,2)
val bbbAAge = person.bbbAge
person.bbbAge = 3
}
var/val Xxxx.yyy get()/set()。 可以用var或val修飾,val必須提供get方法,var必須提供get和set方法,擴展屬性不能操作被擴展的屬性,因為擴展屬性本質(zhì)是對該屬性生成getter和setter方法,并沒有實際存在該屬性。 而無論是擴展屬性還是擴展方法,都可以訪問到被擴展類的public屬性和方法,所以可以在set方法里面調(diào)用age變量,如果是private屬性是沒辦法調(diào)用的。再看看編譯后的代碼:
Person person = new Person("", 1, 2);
int bbbAAge = getBbbAge(person);
setBbbAge(person, 3);
public static final int getBbbAge(@NotNull Person $this$bbbAge) {
Intrinsics.checkNotNullParameter($this$bbbAge, "$this$bbbAge");
return $this$bbbAge.getAge();
}
public static final void setBbbAge(@NotNull Person $this$bbbAge, int value) {
Intrinsics.checkNotNullParameter($this$bbbAge, "$this$bbbAge");
$this$bbbAge.setAge(value);
}
擴展屬性本質(zhì)是靜態(tài)調(diào)用,擴展函數(shù)也是一樣的,后面就不重復贅述。
擴展函數(shù)語法:
public inline fun
for (element in this) action(element)
}
fun Xxx.yyy()。 知道語法之后,日常開發(fā)就可以自己定義一些擴展函數(shù),因為kotlin的標準庫不可能滿足所有的開發(fā)需求。 擴展屬性/函數(shù)如果定義在一個普通的kotlin文件,作用域就是整個項目。擴展函數(shù)還能定義在類里面,這樣作用域就只是該類,外部訪問不到這些擴展函數(shù)。比如:
fun main() {
val person = Person("",0)
// 編譯報錯,訪問不到該方法
person.isZero()
}
data class Person(var name: String, var age: Int){
fun test(){
val isAgeZero = age.isZeor()
}
fun Int.isZeor() = this == 0
}
Function 這個是對匿名函數(shù)的原理進行解釋,不想了解也可以不看。
// 寫法1
var action: (() -> Unit)? = null
// 寫法2
var action: Functiuon0
// 使用
action = {}
可以看到,兩者可以用同一種用法,這里的Funtion0是什么?Funtion0就是kotlin匿名函數(shù)的底層對象,所有的() -> Unit這樣的語法,最終都會編譯成Funtion0這種形式。 0就是0個參數(shù),后面的泛型就是返回類型。如果有1個參數(shù),就是Funtion1,F(xiàn)unction最多支持到22。 看看Funtion的源碼:
public interface Function0
/** Invokes the function. */
public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}
/** A function that takes 3 arguments. */
public interface Function3
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3): R
}
/** A function that takes 4 arguments. */
public interface Function4
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4): R
}
/** A function that takes 5 arguments. */
public interface Function5
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5): R
}
可以看到,每個對象都有invoke方法,所以在調(diào)用時,也可以調(diào)用invoke方法。具體可以看看Functions.kt這個文件,可以看到最多支持到Funtion22。
正則表達式 kotlin使用正則表達式的方式和java有點不同。在kotlin里面,涉及到正則表達式的地方都需要使用toRegex將字符串轉(zhuǎn)換成正則表達式。
val regex = ""
val str = "str"
str.replace(regex,"")
如果不調(diào)用toRegex,那就不是做正則替換,只有調(diào)用了toRegex才可以
val regex = "".toRegex()
val str = "str"
str.replace(regex,"")
java的replace方法是不能傳入正則表達式的,只有調(diào)用replaceAll才可以傳入正則表達式,而kotlin直接調(diào)用replace方法,相應(yīng)的,kotlin沒有replaceAll方法。
kotlin常用的api 上面已經(jīng)將kotlin的用法寫完了,這里再看看kotlin有哪些常用的api。
also和let:also和let類型,區(qū)別是also會返回調(diào)用者本身的值,let會將最后一行作為返回值:
// also
val list = ArrayList
it.add(1)
it.add(2)
}
// let
val value = 1f / 3
val valueStr: String = value.let { DecimalFormat("0.00").format(value) }
// let配合as關(guān)鍵字,還能在開發(fā)中少編寫一些代碼,如果轉(zhuǎn)型失敗,也只會返回null。此時,不要繼續(xù)執(zhí)行就行了
val str: Any = "str"
str.let{ it as? String }?.let{ "$it, str1" }?.also{
println(it)
}
run、with、apply:他們都可以在代碼塊里面訪問到調(diào)用類的pubic屬性和函數(shù)。run和with都是將最后一行作為返回值,apply是返回調(diào)用者本身。
data class Person(var name: String, var age: Int)
val str1: String = Person("",0).run {
name = "a"
age = 1
""
}
val str2: String = with(Person("", -0)){
name = "a"
age = 1
""
}
Person("", 0).apply {
name = "a"
age = 1
}
這幾個方法在android中非常實用,經(jīng)常用在RecyclerView的onBindViewHolder里面,一般就是:
vh.apply{
entity.apply{
name_tv.text = name
}
}
這樣就不用每次都調(diào)用ViewHolder和Entity了。
takeIf:如果false,就返回null。
parentFile.listFiles()?.takeIf { it.isEmpty() }?.also {
parentFile.delete()
}
可以看到,如果文件列表為空,就繼續(xù)執(zhí)行,最后刪除文件。而如果為null就不繼續(xù)執(zhí)行,是一個非常實用的方法。
runCatching:如果執(zhí)行的過程中出現(xiàn)異常,不會直接拋異常,而是將異常捕獲起來,再自己決定要怎么處理這個問題。
fun String.safeToInt(defaultValue: Int): Int{
return this.runCatching {
toInt()
}.onSuccess {
// code
}.onFailure {
// code
}.getOrDefault(defaultValue)
}
這里用this調(diào)用runCatching之后就可以直接用代碼塊里面使用String的toInt,執(zhí)行runCatching之后,返回的是一個Result對象,可以調(diào)用該對象的一些方法對結(jié)果進行處理。而如果查看Result對象的源碼,就可以發(fā)現(xiàn)Result是一個value?class,所以在這個過程中不會創(chuàng)建對象。 除了上面提到的幾個方法,runCatching還有其他方法,可以自己去看看。
buildString:會創(chuàng)建一個StringBuilder,可以在代碼塊里面調(diào)用append方法,返回值是String。
val str: String = buildString {
append("a")
append("b")
}
joinToString:將數(shù)組或列表的元素組合成String。比如:
// 打印:1, 2, 3
listOf(1, 2, 3).joinToString().also(::println)
這個方法有很多個參數(shù),常用參數(shù)有第一個和最后一個。第一個參數(shù)是分隔符,默認是:", "。最后第一個是返回值,可以不傳,會調(diào)用對象的toString方法,也可以使用該方法重寫拼裝成String的邏輯。
repeat:重復n次。
// it就是次數(shù),從0開始
repeat(10){
printlnt(it)
}
TODO:這是一個非常有意思的方法。在java里面,如果一個方法想要防止自己忘記實現(xiàn),通??梢杂胻odo進行注釋,而到了kotlin,就有了TODO方法。使用TODO方法之后,代碼不但會變成藍色,在執(zhí)行到這行代碼之后,還會拋出異常。如果覺得需要通過異常這種方式來提醒自己,就可以這樣。
目前就這些方法吧,平時我在開發(fā)中,我也不會留意自己調(diào)用了哪些方法,需要的時候自然會想起來,下面是list和Map相關(guān)的方法。 先說清楚,list有的方法,array大部分也有,所以array也可以調(diào)用同樣的方法。 listOf相關(guān)的方法:通過可變數(shù)組構(gòu)建一個list。除了listOf,還有mutableListOf、arrayListOf、emptyList、linkedSetOf。這些都是構(gòu)建一個普通的list。
listOfNotNull相關(guān)的方法:這個方法會將傳入的元素過濾一下,保證list里面沒有空元素,是一個非常實用的方法,例子:
val realSecond = it / 1000L
val second = realSecond % 60
val min = realSecond % 3600 / 60
val hour = realSecond % 86400 / 3600
val day = realSecond / 86400
listOfNotNull(
day.takeIf {it != 0L}?.let {it to "天"},
hour.takeIf {it != 0L}?.let {it to "小時"},
min.takeIf {it != 0L}?.let {it to "分鐘"},
second.takeIf {it != 0L}?.let {it to "秒"},
).joinToString("") {"${it.first}${it.second}"}
這里的it類型是Long,這里的需求就是計算出幾天幾時幾分幾秒。使用listOfNotNull和takeIf就可以保證list里面的數(shù)據(jù)不為空且數(shù)值不是0,然后再調(diào)用joinToString方法拼裝成一個字符串。
forEach相關(guān)的方法:用來遍歷list。
// forEach
listOf(1, 2, 3).forEach{}
// forEachIndexed
listOf(1, 2, 3).forEachIndexed { index, it -> }
// indices,通過indices方法可以來遍歷list的角標
listOf(1, 2, 3).indices.forEach { }
map:進行類型轉(zhuǎn)換。
// 這樣就可以將一個List
listOf(1, 2, 3).map{ it.toString }
// mapNotNull,過濾空數(shù)據(jù),注意,mapNotNull之前的空數(shù)據(jù)還會保留,只有map過程中的空數(shù)據(jù)才會被過濾掉
listOf(1,2, 3).mapNotNull { it.takeIf { it % 2 != 0 }?.toString() }.forEach(::println)
// 1 3
// mapTo
// 1 2 3
val list = ArrayList
listOf(1, 2, 3).mapTo(list){it.toString()}
list.forEach(::println)
map還有mapIndexed、mapIndexedNotNull等方法,從名字也可以看出有什么用,我就不提供示例代碼了。
filter:過濾數(shù)據(jù)
listOf(1, 2, 3).filter { it % 2 == 1 }.forEach(::println)
// 1 3
// filterNot則是相反的結(jié)果
listOf(1, 2, 3).filterNot { it % 2 == 1 }.forEach(::println)
// 2
listOf(1, 2,null, 3).filterNotNull().forEach(::println)
// 1 2 3
listOf(1, 2,"str", 3).filterIsInstance
// 1 2 3
filter還有filterTo、filterIndexed等方法。
get相關(guān)的方法:get相關(guān)里面,有一個是getOrNull方法。我認為這類方法是非常重要的,使用這類方法,可以有效防止越界異常。
val list = listOf(1, 2, 3)
// 兩種寫法
list.get(0)
list[0]
// 獲取第1個元素
list.first()
// 如果不希望獲取時發(fā)生越界,可以調(diào)用firstOrNull。此時,如果沒第1個元素,就會返回空
list.firstOrNull()
// 獲取最后一個元素
list.last()
list.lastOrNull()
first和last都有一個帶有匿名函數(shù)參數(shù)的方法,意思是,從頭/偉尋找符合要求的元素,如果找到了,就會返回,否則就拋異常
val i = list.first { it % 2 == 0 }
它們也有orNull方法,如果不希望拋異常,就可以使用orNull方法。 除了first和last,也有g(shù)etOrNull和getOrElse方法,
list.getOrNull(4)
val result = list.getOrElse(4){5}
println(result)
// 5
在日常開發(fā)中,如果需要通過角標獲取元素,我更喜歡用orNull方法,這樣就可以盡量避免程序在運行時出現(xiàn)異常。在調(diào)用orNull之后,再調(diào)用“?”判斷即可,寫起來也不費勁。
take和takeLast:take就是獲取前n個元素。takeLast就是獲取后n個元素。和sublist是一樣的,但在這里,需要傳一個n的參數(shù)。
groupBy:從方法名稱就可以看出,這個方法是做數(shù)據(jù)歸集的。歸集后是一個Map
data class Person(val name: String, val age: Int)
fun main() {
listOf(
Person("str", 1),
Person("str1", 2),
Person("str2", 1),
Person("str3", 2),
Person("str4", 1),
).groupBy { it.age }.forEach{
println("${it.key}, ${it.value}")
}
}
1, [Person(name=str, age=1), Person(name=str2, age=1), Person(name=str4, age=1)]
2, [Person(name=str1, age=2), Person(name=str3, age=2)]
這里的key就是age,value就是List
find方法:這里的find和上面提到的first{}有點像,都是從list里面尋找合適的元素。但區(qū)別是:find當找不到時,會返回空,而不是拋出異常。這個方法還是非常實用的,可以用來從列表尋找目標對象,比如:
fun get(path: String) = values().find { it.path == path }
any:從list尋找合適的元素,如果找到,返回true,都沒有找到返回false。適合用來判斷l(xiāng)ist是否有某個元素符合要求。
all:遍歷list所有的元素,如果所有元素都符合要求,返回true,否則返回false。使用用來判斷l(xiāng)ist里面所有的元素是否符合要求。
List還有很多方法, 我也沒辦法一一例舉,剩下的可以自己隨便點list的某個方法,然后看看kt文件里面還有什么方法。
Map相關(guān)的方法: 構(gòu)建map:有一種非常簡單的構(gòu)建map的方法。
mapOf(
1 to "a",
2 to "b",
3 to "c",
)
使用to就可以輕松地構(gòu)建一個map。
遍歷map:遍歷map不再需要向java那樣麻煩,只需調(diào)用map的forEach方法,就可以輕松遍歷。
map.forEach { key, value ->
println("$key, $value")
}
map.forEach {
println("${it.key}, ${it.value}")
}
for((key, value) in map){
println("$key, $value")
}
get方法:get就沒有g(shù)etOrNull這樣的方法,只有g(shù)etOrDefault和getOrElse方法。而map獲取元素和設(shè)置元素還能像在使用array一樣:
val map = HashMap
map[1] = "a"
val a = map[1]
map方法:,Map的map方法返回的是List
val map = mapOf(1 to "a", 2 to "b", 3 to "c",)
map.map { "${it.key}${it.value}" }.forEach(::println)
// 1a 2b 3c
除了上面提到的方法,map還有filter、any、all等方法可以使用,但相對來說,沒有List的方法那么多。
File相關(guān)的方法:kotlin還為File提供了幾個特別有用的方法 writer和bufferWriter:
val file = File("")
val writer = file.writer()
writer.write("")
writer.close()
val writer = file.bufferedWriter()
writer.write("")
writer.newLine()
writer.close()
可以看到,想要使用文件寫入數(shù)據(jù),只需簡簡單單的一句writer或bufferedWriter,就可以完成,而不用像java那樣寫一堆代碼。
readLines:readLines也是一個非常實用的方法,只需一行,就能將一個文件讀到內(nèi)存中。
val file = File("")
val lines:List
如果想要合并lines,就可以使用上面提到的joinToString方法
lines.joinToString("")
delete:kotlin提供了刪除整個文件夾的api,想要刪除一整個文件夾,只需調(diào)用deleteRecursively方法即可,而不用手動做文件收集再刪除。
copy:既然刪除可以刪除整個文件夾,那復制也可以復制整個文件夾,可以調(diào)用copyRecursivel來復制整個文件夾。
walk方法:walk方法可以收集整個文件夾里面所有的文件。walk還有一個walkBottomUp方法,意思是倒序排序。
kotlin協(xié)程和flow 具體教程就不寫了,需要的自己查,后續(xù)我看看的之前記的筆記寫得怎么樣,如果比較好就寫出來。協(xié)程就是執(zhí)行異步任務(wù)的庫,flow是基于協(xié)程,實現(xiàn)異步數(shù)據(jù)處理的庫。
用協(xié)程來代替開發(fā)中一些異步任務(wù)還是很好用的,而且協(xié)程還能將異步代碼寫成同步代碼而又不阻塞當前線程,從減少Listener相關(guān)的代碼。這樣就不會讓代碼出現(xiàn)一個Listener套一個Listener,最后出現(xiàn)n層嵌套的問題。
KTX
只是介紹一下KTX,不打算寫教程,我平時在用一些API也很少會注意是不是KTX的API。 官網(wǎng)。 官方介紹:Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組 Kotlin 擴展程序。KTX 擴展程序可以為 Jetpack、Android 平臺及其他 API 提供簡潔的慣用 Kotlin 代碼。為此,這些擴展程序利用了多種 Kotlin 語言功能,其中包括:
擴展函數(shù)擴展屬性Lambda命名參數(shù)參數(shù)默認值協(xié)程
所以如果想要更好地使用kotlin,最好還是學一下KTX。
最后再說幾句
使用了kotlin寫代碼之后,就要用好的kotlin的特性,可以去知乎查一下kotlin的奇淫技巧,有一些編碼方式雖然不是很推薦,但可以打開的自己腦洞,幫助自己用好kotlin的特性。 像擴展函數(shù)也要多用,必要時,也要自己定義擴展函數(shù),我在開發(fā)中自己定義的擴展函數(shù)是非常多的。還要用好if-else、try-catch、when這些表達式,這些可以將最后一行作為返回值,而when的語法變?yōu)楦雍啙崳允欠浅:糜玫摹?如果還是帶著java的思維在寫kotlin的代碼,那開發(fā)效率和編寫出來的代碼就和用java差不多。有些人在用kotlin之后,還是帶著java的思維在編碼,事后就說kotlin也不怎么樣啊。 就先寫這些,這篇文章我也是邊寫邊想寫出來的,而不是去查看官方文檔,如果想要看看還有什么內(nèi)容漏了,可以看看kotlin中文站,后面我想到什么內(nèi)容也會補上來。
html dsl:這個項目是我今年(23年)年初閑著沒事寫出來的工具,用kotlin代碼生成html代碼。里面所有的代碼都是用kotlin寫的,如果不知道要閱讀什么源碼,這個項目或許可以幫助到您。
補充一點代碼格式化的內(nèi)容: 建議不要使用kotlin的代碼格式化,否則會變得不幸。kotlin的android studio代碼格式化做得挺差的,不管怎么調(diào)教,都沒辦法調(diào)出一個想要的效果。 java的代碼格式化就做得比較好,java在第一次格式化時,只會處理空格、縮進這些問題,只有第2次才會按照android studio的設(shè)置對代碼進行格式化。 通常情況下,只需第一次格式化就行了,第2次格式化可能會打亂我們的代碼風格。而kotlin就直接一步到位,沒有了第1步,導致我經(jīng)常在使用kotlin的格式化之后血壓升高。 舉個例子:
StringBuilder sb=new StringBuilder();
sb.append("")
.append("");
// 第一次格式化時,會變成
StringBuilder sb = new StringBuilder();
sb.append("")
.append("");
// 只有第二次格式化時,才可能變成
sb.append("").append("");
而如果使用kotlin,并切設(shè)置成鏈式調(diào)用不換行,就會一步到位執(zhí)行到第2次格式化的結(jié)果,但有時我們就是想要保持換行的效果。 這樣的例子在kotlin有一堆,所以也導致之后,通常在格式化之后血壓升高。而我在日常開發(fā)中,也有補上空格的習慣,所以對我來說,格式化可有可無。 只不過在寫代碼的時候,有時有所疏忽,導致需要用工具格式化。但這樣的代碼不多,所以對我來說,格式化不是必須的。但如果真的需要,就還是用吧。 kotlin代碼格式化的配置在:settings -> Editor -> Code Style -> Kotlin。這里面有各種配置,可以自己慢慢調(diào)教。
柚子快報激活碼778899分享:kotlin使用教程
參考鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。