充分理解Kotlin,快速上手寫業務

MDove發表於2018-12-08

充分理解Kotlin,快速上手寫業務

前言

即使每天10點下班,即使需求很多,我也要用這腐朽的聲音喊出:我要學習,我要寫文章!!

幾個特性,快速上手Kotlin

充分理解Kotlin,快速上手寫業務

快速切換至Kotlin for Android模式

聊一聊Kotlin中的協程,真香

又是一篇Kotlin的文章,為啥...還不是因為工作需要。毫無疑問,最好的學習方式是通過官方文件去學習。不過個人覺得官方文件多多少少有一些不夠高效。

中文官方文件

因此這篇是從我學習的個人視角以文件的形式去輸出Kotlin語言基礎的學習。 不扯淡了,開整。

正文

高階函式

高階函式是將函式用作引數或返回值的函式。

個人為什麼把這個語言特性放在第一位,因為我覺得這是我們Java語言中所不支援的。然而這個特性基本貫穿整個Kotlin語言體系。所以個人把它放到了第一位,也希望各位小夥伴能夠重視這個小小的特性。先看看一個小小的demo,感受一下這個特別特性:

// 函式定義
fun funtion(num: Int, innerFun: (Int) -> Int): Int {
    return innerFun(num)
}
複製程式碼

簡單解釋一下上邊的程式碼。我們先看函式定義,這裡定義了一個名為funtion並且返回值為Int的函式,此外這個函式接受一個Int引數和一個函式型別的引數(這個函式需要傳遞一個Int引數,並且返回一個Int值)。 接下來我們呼叫一下:

// 函式呼叫
val result = funtion(1) {
	it + 666
}
複製程式碼

對於Lambda來說,入參如果是一個的話可以用it來表示

說實話,第一次我看到這種呼叫的時候,是一臉懵逼的。不知道剛入坑的小夥伴是不是和我一樣的感受?因為這種寫法包含小tips: 我們的innerFun是可以被簡化為一個Lambda,而當Lambda作為函式的最後一個引數時,是可以將其寫在函式之外的。也就是Demo中的funtion(1){}。

此外我們要注意一點,這裡我們的函式例項,並沒有return,這是因為。lambda 表示式中的最後一個表示式是返回值 實際上這就是相當於呼叫了funtion方法的倆個引數,我們們換一種傳統的寫法,二者是等價的:

val inner = fun(num: Int): Int {
	return num + 1
}
val result = funtion(1, inner)
複製程式碼

OK,接下來我們們趁熱打鐵,再感受感受函式操作的“騷操作”。接下來我們看一看其實情況下的用法:

// 函式定義1
fun funtion(num: Int, innerFun: (Int, Int) -> Int): Int {
    return innerFun(num, num + 1)
}
// 函式呼叫1(上文提到,入參為1個可以用it表示,那麼倆個或者多個呢?我們可以如下,自定義)
val result = funtion(1) { num1, num2 ->
    num1 + num2 + 666
}
// 函式呼叫2(除了Lambda的呼叫方式,我們還可以用匿名函式,二者是一樣的)
val result = funtion(1, fun(num1: Int, num2: Int): Int {
	return num1 + num2 + 666
}
複製程式碼

OK,關於高階函式的內容,就聊這麼多,因為有了這個其實,很多內容也就很好上手了。接下來就讓我們一同看看基於高階函式的內建封裝。

操作符

集合操作符

個人覺得,Kotlin操作符中。開發中頗為常用的是集合操作符,比如我們有時需要對一些資料進行連續的轉化,我們可能會使用RxJava;

Observable.create(new ObservableOnSubscribe<List<CustomModel>>() {
            @Override
            public void subscribe(ObservableEmitter<List<CustomModel>> e) throws Exception {
                // 省略構建List<CustomModel>
                e.onNext(data);
            }
        }).flatMap(new Function<List<CustomModel>, ObservableSource<CustomModel>>() {
            @Override
            public ObservableSource<CustomModel> apply(List<CustomModel> customModels) throws Exception {
                return Observable.fromIterable(customModels);
            }
        }).map(new Function<CustomModel, String>() {
            @Override
            public String apply(CustomModel customModel) throws Exception {
                return customModel.name;
            }
        }).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                // 操作s
            }
        });
複製程式碼

這裡簡單,寫了一個RxJava的Demo:我們有一個List集合,先把它一個個傳送,然後把每一個資料轉成String,最後去String進行處理。 而在Kotlin中,應對上述的需求,我們該怎麼做?由於操作符的支援,我們可以極為方便的進行這類操作:

val data = arrayListOf<CustomModel>(// ...省略構建的過程)
val newData=data.map {
    it.name
}.forEach {
    // 操作it
}
複製程式碼

用法很簡單,乍一看很唬人。但是其實很簡單,回憶一下我們再開篇奠定的高階函式的基礎。這裡其實就是呼叫了data的擴充套件函式map(擴充函式是一種語法,可以擴充現有的類。比如這個map就是擴充了Iterable,下文我們會展開它的實現)。對它進行型別轉變,然後進而呼叫forEach自動對這個集合進行遍歷。

接下來讓我們點進去,看一看具體的實現:

// 注意看,這裡map的**返回值**,是一個List<R>,也就是說最終將返回map後的型別集合。因此我們可以繼續進行鏈式呼叫。
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}
複製程式碼

說白了,這裡的集合操作符就是把轉化的實現通過引數的形式暴露出來,由我們自行去處理,其他的事情,有內部封裝去做。 當然,這其中還有很多有趣的操作符,各位小夥伴可以自行去了解。不過原理都是大同小異~

作用域函式

上述我們聊的是作用在集合上的操作符,說白了。它們就是擴充在對應集合類上的函式而已。那麼可能有小夥伴會問,有沒有作用在物件上的?沒錯,是有的。也就是我們接下來要聊的:作用域函式

其實很常見:

"Kotlin".let{
	it.toInt()
}
"Kotlin".run{
	this.toInt()
}

"Kotlin".apply{
}
"Kotlin".also{
}

public inline fun <T, R> T.let(block: (T) -> R): R
public inline fun <T, R> T.run(block: T.() -> R): R

public inline fun <T> T.apply(block: T.() -> kotlin.Unit): T
public inline fun <T> T.also(block: (T) -> kotlin.Unit): T

複製程式碼

我們們看一下,它們哥四個的定義,其實很清晰。這裡以返回值的型別,倆倆分類:

  • let/run。這倆個函式都可以返回另一種型別;不同在於let有入參,而run沒有。
  • also/apply。 這倆個函式返回值都是本身,其中apply沒有入參,而also有。

不過即使沒有入參,也可以通過this,得到本身。

上述函式的作用還是挺方便的,比如:

Model model ;
// ...省略初始化過程
if(model!=null){
	// 省略大段的操作邏輯
}
複製程式碼

這種寫法,在kotlin中可以這麼寫:

model?.alse{
	// 省略大段的操作邏輯
}
// 有需求我們可以繼續呼叫它的作用域函式
複製程式碼

如果你還有一些else操作,那麼沒轍了,kotlin中也只能老老實實if-else

當然,還有一些有趣的作用域函式,比如在Java中:

boolean isTure;
// 省略boolean判斷邏輯
if(isTure){
	// 省略大段的操作邏輯
}
複製程式碼

到Kotlin中可以這樣來搞:

某物件.takeIf{
	// boolean判斷邏輯
}?.{
	// 省略大段的操作邏輯
}
// 有需求我們可以繼續呼叫它的作用域函式
複製程式碼

takeIf:會根據入參的函式的返回值(true/false),決定自己(takeIf)的返回值是null還是呼叫者。如果是false,那麼就會返回null,因此這裡使用?的方式繼續去呼叫後續操作。 我個人比較喜歡作用域函式, 因為可以省略很多if。接下來我們來換個稍稍複雜的邏輯:已知一個方法,入參是一個Model,我們要判斷當這個Model物件的age屬性大於10,列印這個Model物件的名字。 對於Java來說,我們可能這麼寫:

public void fun(Model model) {
	// 寫了3個if。
    if (model != null) {
        if (model.age>10){
            if (model.name!=null){
                // 列印model.name
            }
        }
    }
}
    
class Model {
    String name;
    int age;
}
複製程式碼

對於Kotlin來說,我們可以這麼寫:

fun funtion(model : Model){
	model?.takeIf{
		it.age > 10
	}?.let{
		// 列印it.name
	}
}
複製程式碼

是不是簡潔了很多?但是單說簡潔這種東西,似乎並沒有什麼卵用...不過寫起來,真的很爽!不信,大家可以試試~~

尾聲

這篇文章,想聊的內容就這麼多。個人認為其實理解了高階函式,(PS:不知道為啥起了個名叫高階函式,整得好像很高階似的。)Kotlin就可以很快的上手,並且能感受到Kotlin寫起來的爽快之處~ 這裡的確有些安利的意味。那是因為自己最開始對新技術其實接受度並不高,總認為新東西有各種各樣的問題...當自己去了頭條之後,才發現身邊的同事那種高昂的學習勁頭,(Kotlin?對他們來說不叫新東西...Jatpack,Flutter都已經線上上專案中跑了)自己還有什麼理由說:求求別更新了,我學不動了... 還是那句話:幹就完了!

我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,以及我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:IT面試填坑小分隊

相關文章