欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報激活碼778899分享:kotlin使用教程

柚子快報激活碼778899分享:kotlin使用教程

http://yzkb.51969.com/

寫在前面:現(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(value: T)

// 在使用時,可以指定泛型的類型

Test(1)

// 如果該類或者方法有某個參數(shù)使用到該泛型,并且傳值了,可以省略泛型

Test(1)

泛型的邊界:kotlin可以使用來制定泛型邊界

class Test(value: T)

Test("")

// 如果給泛型邊界加上"?",在傳參時還能傳null,并且不需要在T旁邊寫"?"

class Test(value: T)

Test(null)

如果有多個邊界,可以使用where關(guān)鍵字。多個邊界只能有一個是類,其他的只能是接口,并且類必須放在最前面

class Test() where T: CharSequence, T: View.OnClickListener, T: View.OnTouchListener

在kotlin中,如果聲明了變量類型,在調(diào)用返回值為泛型的方法時,可以不用寫泛型,這在安卓開發(fā)中很常見

// 聲明了text_tv的類型時

val text_tv: TextView = findViewById(R.id.text_tv)

// 如果沒有聲明text_tv的類型,就必須指定泛型

val text_tv = findViewById(R.id.text_tv)

真實的泛型,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(context: Context){

startActivity(Intent(context, T::class.java))

}

// 使用

startActivity(this)

可以看到,使用了reified關(guān)鍵字之后,就可以直接使用T的class。在聲明reified泛型時,必須在方法前面寫上inline關(guān)鍵字,這個關(guān)鍵字的作用下面會講。

協(xié)變和逆變

協(xié)變:kotlin的類似java的逆變:kotlin的類似java的

無邊界通配符,kotlin使用<*>來表示無邊界通配符。

class Test(value: T)

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 Iterable.forEach(action: (T) -> Unit): Unit

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(R.id.text_tv)

}

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 Iterable.forEach(action: (T) -> Unit): Unit

// 在調(diào)用forEach方法時,不需要寫括號

listOf(1).forEach { }

// 如果還有其他參數(shù),那就必須寫括號

public inline fun > Iterable.mapTo(destination: C, transform: (T) -> R): C

// 可以看到,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(): List = emptyList()

如果點擊看listOf的源碼,就會發(fā)現(xiàn)listOf沒有依托于任何外部類,而是在kt文件獨立存在的,kotlin存在大量的這樣的方法。

// 變量也可以獨立存在

public val List.lastIndex: Int

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 = 1 to "s"

可以看到,在調(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 Iterable.forEach(action: (T) -> Unit): Unit {

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? = null

// 使用

action = {}

可以看到,兩者可以用同一種用法,這里的Funtion0是什么?Funtion0就是kotlin匿名函數(shù)的底層對象,所有的() -> Unit這樣的語法,最終都會編譯成Funtion0這種形式。 0就是0個參數(shù),后面的泛型就是返回類型。如果有1個參數(shù),就是Funtion1,F(xiàn)unction最多支持到22。 看看Funtion的源碼:

public interface Function0 : Function {

/** Invokes the function. */

public operator fun invoke(): R

}

/** A function that takes 1 argument. */

public interface Function1 : Function {

/** Invokes the function with the specified argument. */

public operator fun invoke(p1: P1): R

}

/** A function that takes 2 arguments. */

public interface Function2 : Function {

/** 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 : Function {

/** 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 : Function {

/** 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 : Function {

/** 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().also{

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轉(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().forEach(::println)

// 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 = file.readLines()

如果想要合并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使用教程

http://yzkb.51969.com/

參考鏈接

評論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://m.gantiao.com.cn/post/19162725.html

發(fā)布評論

您暫未設(shè)置收款碼

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄