Kotlin "談" "彈" "潭"

Kotlin "談" "彈" "潭"

本篇針對(duì)使用Java的Android開發(fā)者,快速入手Kotlin,期間可能啰啰嗦嗦稀里糊涂緩緩乎乎穿插一些我中過的坑。這里不講Kotlin異步并發(fā)(協(xié)程)、不講Kotlin反射,如果你是來看它們的。那我現(xiàn)在也木有。

image

目錄

一、為什么要學(xué)習(xí)kotlin
二、基本使用
1、Java寫法和Kotlin的寫法對(duì)比
2、基本類型
  • Kotlin中數(shù)字類型
  • 運(yùn)算符號(hào)
  • 布爾值
  • 數(shù)組
3、基本表達(dá)式
  • 表達(dá)式
  • 流程控制
  • when使你力腕狂瀾
  • 使用表達(dá)式比語句更加安全
  • for循環(huán)的奧義
4、各種類
  • 類、接口基本概念
  • 伴生對(duì)象
  • 單例類
  • 匿名內(nèi)部類
  • 數(shù)據(jù)類
5、各種函數(shù)
  • lambda表達(dá)式
  • 內(nèi)聯(lián)函數(shù)
  • 擴(kuò)展函數(shù)
  • 方法默認(rèn)參數(shù)
6、各種集合
  • List
  • Set
  • Map
  • 集合操作
  • 惰性求值和序列
7、函數(shù)方法值類型
8、高階函數(shù)
9、空安全
10、關(guān)于設(shè)計(jì)模式和Kotlin
  • 工廠模式
  • 觀察者和代理模式
11、快速對(duì)比Java代碼必備技能
三、參考

一、為什么要學(xué)習(xí)kotlin

  • 1、Google推薦
    現(xiàn)在的新特性文檔以及例子有一些是只有kotlin的了

  • 2、更加簡潔的語法減少啰嗦代碼

  • 3、都是基于JVM編譯成字節(jié)碼,與Java基本兼容基本沒有太大問題,因此可以用java寫的很多庫,生態(tài)強(qiáng)大

  • 4、強(qiáng)大的語言特性

二、基本使用

1、Java寫法和Kotlin的寫法對(duì)比

from-java-to-kotlin

2、基本類型

Kotlin中所有的東西都是對(duì)象,包括基本類型,一般來說數(shù)字、字符、布爾值、數(shù)組與字符串是組成一門語言的基本數(shù)據(jù)類型。

Kotlin中數(shù)字類型
Type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

每個(gè)數(shù)字類型支持如下的顯示轉(zhuǎn)換,比如

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
運(yùn)算符號(hào)
  • shl(bits) – 有符號(hào)左移 (Java 的 <<)
  • shr(bits) – 有符號(hào)右移 (Java 的 >>)
  • ushr(bits) – 無符號(hào)右移 (Java 的 >>>)
  • and(bits) – 位與
  • or(bits) – 位或
  • xor(bits) – 位異或
  • inv() – 位非
布爾值

Boolean 類型,有兩個(gè)值true 與 false

數(shù)組

數(shù)組在 Kotlin 中使用 Array 類來表示,它定義了 get 與 set 函數(shù)(按照運(yùn)算符重載約定這會(huì)轉(zhuǎn)變?yōu)?[])以及 size 屬性

比如說

val persons = Array<Person>(3) {
            Person("2")
            Person("3")
            Person("4")
        }
persons.size

當(dāng)然,kotlin也支持簡單的基本原生基本類型,無裝箱開箱的開銷的ByteArray、 ShortArray、IntArray 等等

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

3、基本表達(dá)式

表達(dá)式

什么是表達(dá)式呢,在Java中,以;結(jié)尾的一段代碼,即為一個(gè)表達(dá)式。

setContentView(R.layout.activity_main)

public static final int CAT_DRIVERS = 20;

Log.e(TAG, "貓司機(jī)的腿數(shù)量: " + catCount * 3);

這個(gè)也是kotlin中表達(dá)式的概念。

流程控制

Kotlin的流程控制和Java的基本相同,最大的區(qū)別是Kotlin沒有switch語句,kotlin中是更加強(qiáng)大的when語句。而且kotlin的if語句和when語句可以當(dāng)做表達(dá)式來使用,就跟Java中的三目運(yùn)算符一樣。

Java:
String name = isOne ? "是一個(gè)人" : "是半個(gè)人";

可以說when是Java中if和switch的一次強(qiáng)大的聯(lián)合。比如說,可以有這種寫法

var single3 = when (single) {
        0, 1 -> aLog("single == 0 or single == 1")
        else -> aLog("其他")
    }

if和while都是和java一樣的,區(qū)別在于if可以當(dāng)做表達(dá)式來使用,比如

private val single2 = if (single >= 3) {
        aLog("大于等于3")
    } else {
        aLog("小于3")
    }
when使你力腕狂瀾

如果很多分支需要用相同的方式處理,則可以把多個(gè)分支條件放在一起,用逗號(hào)分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

我們可以用任意表達(dá)式(而不只是常量)作為分支條件

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

我們也可以檢測一個(gè)值在(in)或者不在(!in)一個(gè)區(qū)間或者集合中:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

另一種可能性是檢測一個(gè)值是(is)或者不是(!is)一個(gè)特定類型的值。注意: 由于智能轉(zhuǎn)換,你可以訪問該類型的方法與屬性而無需任何額外的檢測。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when 也可以用來取代 if-else if鏈。 如果不提供參數(shù),所有的分支條件都是簡單的布爾表達(dá)式,而當(dāng)一個(gè)分支的條件為真時(shí)則執(zhí)行該分支:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

這些用法,可以說一個(gè)when讓你不再寫出混亂的if嵌套。

使用表達(dá)式比語句更加安全

先看一段Java代碼

private void dididada(boolean needInit) {
    String a = null;
    if(needInit){
        a = "a is Dog that not girlfriend";
    }
    Log.e(TAG, a.toString());
}

這段代碼可能有潛在的問題,比如說a必須在if語句外部聲明,它被初始化為null。這段代碼中,我們忽略了else分支,如果needInit為false,那么會(huì)導(dǎo)致a.toString()空指針異常問題。并且如果needInit的值時(shí)很深的路徑傳遞過來的,那么可能會(huì)導(dǎo)致這個(gè)問題更容易被忽略。

如果,你用了表達(dá)式


image

比如你這樣子寫了


fun dididada(boolean needInit) {
    String a = if(needInit){
            "a is Dog that not girlfriend"
    } else{
        ""
    }
    Log.e(TAG, a.toString())
}

實(shí)際上,可以更簡化點(diǎn)

fun dididada(boolean needInit) {
    String a = if(needInit) "a is Dog that not girlfriend" else ""
    Log.e(TAG, a.toString())
}

因?yàn)楸磉_(dá)式強(qiáng)制需要你寫出else語句,也就不再存在上面說的漏掉else的問題了。

for循環(huán)的奧義

上面加when的時(shí)候出現(xiàn)的..關(guān)鍵字和in關(guān)鍵字這些,..這個(gè)叫做區(qū)間,比如說1..4代表從1到4的數(shù)列。比如說

if (i in 1..4) {  // 等同于 1 <= i && i <= 4
    print(i)
}

for (i in 1..4) {
    print(i)
}
輸出:1,2,3,4  

如果你要倒序呢,你可以這樣

for (i in 4 downTo 1) {
    print(i)
}
輸出:4,3,2,1  

downTo關(guān)鍵字代表反向遍歷,如果你想輸出一個(gè)等差數(shù)列,你可以用step關(guān)鍵字

for (i in 1..8 step 2){
    print(i)
}
輸出:1,3,5,7

用Java寫出類似的邏輯,你需要

for (int i = 1; i <= 8; i += 2) {
  print(i)
}

可見Kotlin相對(duì)于Java的簡潔

剛才上面所說的區(qū)間都是左閉右也閉的,你還可以使用until來代替..實(shí)現(xiàn)左閉右開區(qū)間,就比如

for (i in 1 until 10) {       // i in [1, 10), 10 is excluded
    print(i)
}

4、各種類

類、接口基本概念

kotlin中類、接口的聲明跟Java中是一致的

class Pig { ... }

interface Motion {
    fun run()
}

如果你要繼承一個(gè)類或者實(shí)現(xiàn)一個(gè)接口,都可以使用:符號(hào),另外接口想繼承另外一個(gè)接口,也是可以使用:來達(dá)到目的,可以說是統(tǒng)一extends和implement關(guān)鍵字,有利有弊。
比如Pig繼承Animal類和實(shí)現(xiàn)Motion接口


class Animal { 
    var name:String?
}

inteface Motion {
    fun run()
}

class Pig : Animal, Motion{
    override fun run() {
        val pigName = name
    }
}

kotlin的類的聲明有一個(gè)很重要的概念是主構(gòu)造函數(shù)和次構(gòu)造函數(shù),主構(gòu)造函數(shù)是類頭的一部分,它跟在類名的后面,比如

class Pig constructor(name: String) { ... }

你甚至可以省略主構(gòu)造器的constructor,就像這樣

class Pig(name: String) { ... }

什么情況可以省略呢,比如說你不需要給name添加注解或修改可見性的時(shí)候,那比如說,如果你想在主構(gòu)造器初始化的時(shí)候在里面寫一串初始化邏輯,要怎么樣呢?用Java的時(shí)候你應(yīng)該是這樣的

class Pig { 
    public Pig(String name) {
        //初始化邏輯
    }
}

但是現(xiàn)在kotlin使用主構(gòu)造器初始化的話,你在也看不到可愛的構(gòu)造器方法了


image

莫慌,你還可以這樣,kotlin中提供了一種init代碼塊,它會(huì)在調(diào)用構(gòu)造方法的時(shí)候按照在類中的init塊的順序從上往下執(zhí)行,比如

class Pig(name: String) {

    //第一個(gè)init塊
    init {
        println("${name}")
    }
   
    //第二個(gè)init塊
    init {
        println("名字長度:${name.length}")
    }
}

執(zhí)行的時(shí)候

Pig("你是豬豬豬", 5)

輸出:
你是豬豬豬
名字長度:5

次構(gòu)造器就跟Java中差不多了,但是不同的地方在于必須要有constructor關(guān)鍵字來聲明

class Pig(name:String) {
    
    constructor(name: String, length:Int):this(name) {
        println("次構(gòu)造器")
    }
    

如果同時(shí)有主構(gòu)造器和次構(gòu)造器以及init塊,他們的執(zhí)行順序是什么樣的呢?實(shí)際上,init塊會(huì)作為主構(gòu)造器的一部分。比如

class Pig(name:String) {
     //第一個(gè)init塊
    init {
        println("${name}")
    }

    constructor(name: String, length:Int):this(name) {
        println("次構(gòu)造器")
    }
    
    //第二個(gè)init塊
    init {
        println("名字長度:${name.length}")
    }
}


執(zhí)行的時(shí)候

Pig("你是豬豬豬")

輸出:
1.你是豬豬豬
2.名字長度:5
3.次構(gòu)造器

可見,不管你的init塊在哪里,init塊會(huì)先執(zhí)行完畢

伴生對(duì)象

kotlin中沒有靜態(tài)內(nèi)部類的概念,取而代之的是伴生對(duì)象這貨

class Pig {
    companion object Factory {
        fun create(): pig = Pig()
    }
}

該伴生對(duì)象的成員可通過只使用類名作為限定符來調(diào)用,比如說


kotlin中調(diào)用
val instance = Pig.create()

Java中調(diào)用
Pig pig = Pig.Companion.create()

可見,伴生對(duì)象可以讓我們輕松實(shí)現(xiàn)工廠模式,造豬運(yùn)動(dòng)

單例類

平時(shí)用到最多的類就是單例類,kotlin自帶單例特性,比如

object Demo{
    fun dadada(){
        
    }
}

使用的時(shí)候,只需要寫Demo.dadada()就能使用這個(gè)單例類的方法,當(dāng)然不要有誤解,這個(gè)不是靜態(tài)類的靜態(tài)方法。反編譯kotlin代碼

public final class Demo {
   public static final Demo INSTANCE;

   public final void dadada() {
   }

   static {
      Demo var0 = new Demo();
      INSTANCE = var0;
   }
}

可以看到這個(gè)是Java中靜態(tài)內(nèi)部類版本的單例寫法。

當(dāng)然,如果你想自己寫單例,你可以用Java那套自己去擼。比較高端的用法,由于Kotlin有委托屬性的概念,可以用lazy屬性來寫一個(gè)懶加載的單例類

class Pig private constructor() {
    companion object {
        val instance: Pig by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        Pig() 
        }
    }
}

等同于Java中的雙重校驗(yàn)單例

public class Pig {
    private volatile static Pig instance;
    private Pig(){} 
    public static Pig getInstance(){
        if(instance==null){
            synchronized (Pig.class){
                if(instance==null){
                    instance=new Pig();
                }
            }
        }
        return instance;
    }
}

更多單例的實(shí)現(xiàn)可以去Kotlin下的5種單例模式查看

匿名內(nèi)部類

在Android中,匿名內(nèi)部類是必不可少的,比如button的點(diǎn)擊事件、各個(gè)監(jiān)聽事件的事件回調(diào)等等。

kotlin中的匿名內(nèi)部類是要以object開頭的,比如Java中的

mExpandMenu.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

kotlin的寫法

view.setOnClickListener(object: View.OnClickListener {
            override fun onClick(v: View?) {
                 //onClick代碼塊
            }
        })

你可以更精簡

view.setOnClickListener {
            //onClick代碼塊
        }
數(shù)據(jù)類

我們經(jīng)常會(huì)使用到一些只保存數(shù)據(jù)的類,java中需要自己去寫屬性和get和set方法,在kotlin中,可以使用數(shù)據(jù)類來避免模版代碼。

data class User(val name: String, val age: Int)

編譯器自動(dòng)從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員:

  • equals()/hashCode() 對(duì);
  • toString() 格式是 "User(name=John, age=42)";
  • componentN() 函數(shù) 按聲明順序?qū)?yīng)于所有屬性;
  • copy() 函數(shù)

前三包含了set、get方法和基本的類方法,copy函數(shù)實(shí)際上是

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

name: String = this.name這種寫法是函數(shù)默認(rèn)值寫法,后面會(huì)單獨(dú)介紹。

又new了一個(gè)對(duì)象出來,達(dá)到復(fù)制的目的。

data class數(shù)據(jù)類能讓我們少寫bean類很多代碼。

5、各種函數(shù)

lambda表達(dá)式

上面有個(gè)地方其實(shí)有提到過kotlin的lambda表達(dá)式

view.setOnClickListener(object: View.OnClickListener {
            override fun onClick(v: View?) {
                 //onClick代碼塊
            }
        })

你可以更精簡

view.setOnClickListener {
            //onClick代碼塊
        }

精簡后的其實(shí)就是lambda表達(dá)式本身,下面我們來看一個(gè)例子

val list = listOf<String>()
val filterList = list.filter { it == "pig" }

創(chuàng)建一個(gè)List然后過濾出來數(shù)據(jù)時(shí)pig的元素

來看一下filter的源碼

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}


public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

可以看見filter接收的是一個(gè)(T) -> Boolean類型的predicate參數(shù),其實(shí)它就是lambda類型(Kotlin中所有的東西都是對(duì)象,包括這個(gè)玩意),表示接收一個(gè)T類型的參數(shù)返回一個(gè)Boolean類型lambda表達(dá)式。這里的predicate會(huì)一直往下傳,直到真正的過濾函數(shù)filterTo中,這里寫的比較簡潔,可以嘗試還原,加上大括號(hào)

for (element in this) {
    if (predicate(element)) {
        destination.add(element)
    }
}
return destination

其實(shí)就是遍歷該集合內(nèi)容,條件是傳進(jìn)來的lambda表達(dá)式,我們現(xiàn)在外層傳進(jìn)來的是it == "pig",再度還原,得到如下代碼,當(dāng)元素等于pig的時(shí)候,將元素添加到返回的集合中,最后返回新的集合。

for (element in this) {
    if (element == "pig") {
        destination.add(element)
    }
}
return destination

Kotlin中提供了簡潔的語法去定義函數(shù)的類型,大致如下,當(dāng)然還有很多變種

() -> Unit//表示無參數(shù)無返回值的Lambda表達(dá)式類型

(T) -> Unit//表示接收一個(gè)T類型參數(shù),無返回值的Lambda表達(dá)式類型,這種其實(shí)就是上面說的filter

(T) -> R//表示接收一個(gè)T類型參數(shù),返回一個(gè)R類型值的Lambda表達(dá)式類型

(T, P) -> R//表示接收一個(gè)T類型和P類型的參數(shù),返回一個(gè)R類型值的Lambda表達(dá)式類型

(T, (P,Q) -> S) -> R//表示接收一個(gè)T類型參數(shù)和一個(gè)接收P、Q類型兩個(gè)參數(shù)并返回一個(gè)S類型的值的Lambda表達(dá)式類型參數(shù),返回一個(gè)R類型值的Lambda表達(dá)式類型

lamda函數(shù)在實(shí)際使用中,上述列了2種使用場景,很明顯是更加簡潔易懂,不啰嗦(當(dāng)然,前提是得知道原理,不然一臉懵),但是也有可能造成調(diào)試bug成本增加(難以定位問題代碼)。各有利弊

內(nèi)聯(lián)函數(shù)

上面的filter函數(shù)中,有個(gè)地方用了inline關(guān)鍵字,其實(shí)這個(gè)就是內(nèi)聯(lián)函數(shù)

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

內(nèi)聯(lián)函數(shù)其實(shí)是為了優(yōu)化lambda開銷的產(chǎn)生的,平時(shí)我們制造lambda的函數(shù)盡量加上inline。具體怎么優(yōu)化的,可以了解下kotlin是怎么實(shí)現(xiàn)lambda函數(shù)(本質(zhì)上是繼承Lambda抽象類并且實(shí)現(xiàn)了FunctionBase生成的類,比如Function3)(兼容JDK 6的情況下),然后怎么對(duì)其做優(yōu)化的。淺談Kotlin語法篇之lambda編譯成字節(jié)碼過程完全解析(七)

擴(kuò)展函數(shù)

擴(kuò)展函數(shù)我個(gè)人認(rèn)為是kotlin的最精辟的地方。又回到剛才講的filter函數(shù),它還有眾多的兄弟姐妹,比如map、zip等等等等。但是他們本質(zhì)上都是擴(kuò)展函數(shù)


public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}


fun Activity.log(content: String) {
    Log.e("minminaya", content)
}

基于這種類型的(擴(kuò)展的類.擴(kuò)展函數(shù)名字)叫做就是擴(kuò)展函數(shù)。它僅僅限于擴(kuò)展對(duì)象中的方法,擴(kuò)展函數(shù)不能在靜態(tài)類中使用。我們可以拿這一特性來做很多事情。比如說,ImageView結(jié)合Gilde的使用、findViewById、比如工廠方法模式的擴(kuò)展(后面具體講)。

以我們項(xiàng)目中常用的Glide工具類為準(zhǔn),這里先看下ImageView這些怎么拓展。

比如說我們之前是將Glide的加載代放到一個(gè)工具類里


    public void displayImage(ImageView imageView, String url, RequestOptions requestOptions) {
        if (!isWithValid(imageView)) {
            return;
        }
        Glide.with(imageView.getContext()).asBitmap().apply(requestOptions).load(parseUrl(url)).into(imageView);
    }

然后使用的時(shí)候是這樣子使用

 GlideLoader.getInstance().displayImage(mIvShareView.getContext(), mIvShareView,
                GlideLoader.wrapFile(imagePath),
                mRequestOptions);

如果將displayImage作為ImageView的擴(kuò)展函數(shù)

public fun ImageView.displayImage(url: String, requestOptions: RequestOptions) {
    if (GlideLoader.isWithValid(this)) {
        return
    }
    Glide.with(context).load(GlideLoader.parseUrl(url)).apply(requestOptions).into(this)
}

使用方式可以變成這樣

val imageView = ImageView(this)
        imageView.displayImage("url", RequestOptions())
然后再看下Activity中怎么樣消除冗長的findViewById

之前的用法比如像這樣

       mIvLongVideoTimeTip = mRootView.findViewById(R.id.iv_long_record_time);
        mLongVideoEffectOptContainer = mRootView.findViewById(R.id.long_record_opt_container);
        mIvLongVideoArEffect = mRootView.findViewById(R.id.iv_selfie_camera_long_video_ar_effect);

如果用擴(kuò)展函數(shù),你可以這樣

fun <T : View> Activity._view(@IdRes id: Int): T {
    return findViewById(id)
}


在Activity中使用:

val tootBar = _view<Toolbar>(R.id.toolbar)

當(dāng)然kotlin為我們提供了更加方便的findId插件,你只需要在gradle文件中

apply plugin: 'kotlin-android-extensions'

Activity中(比如說Activity的布局是activity_main)

import kotlinx.android.synthetic.main.activity_main.*

比如我們現(xiàn)在的布局xml是這樣子寫

<androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

那代碼中就可以直接使用這個(gè)id來對(duì)view進(jìn)行操作

 toolbar.setLogo(R.mipmap.ic_launcher)
方法默認(rèn)參數(shù)

意思就是,方法的參數(shù)可以指定默認(rèn)的值,函數(shù)調(diào)用的時(shí)候,如果沒有攜帶參數(shù),那么使用函數(shù)中的默認(rèn)值,以我們的常用的未重構(gòu)未Builder模式之前的GuideViewHelper為例,看一下要怎么樣用kotlin來重構(gòu)。

public class GuideViewHelper {

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final int arrowId) {
        return showGuideView(activity, attachView, layoutId, alignView, false, arrowId);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId) {
        return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, 0);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId, final int yOffset) {
        return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, yOffset, null);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId, final int yOffset, final IResetLocationListener resetLocationListener) {
        return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, yOffset, resetLocationListener, null);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId, final int yOffset,
                                     final IResetLocationListener resetLocationListener, final GuideViewAnimator guideViewAnimator) {
        if (attachView == null || activity == null || activity.isFinishing()) {
            return null;
        }

        final View contentView = getContentView(activity);
        if (contentView instanceof FrameLayout) {
            FrameLayout parent = (FrameLayout) contentView;
            final View guideView = LayoutInflater.from(activity).inflate(layoutId, parent, false);
            if (guideViewAnimator != null) {
                guideView.setTag(R.id.guide_view_animator, guideViewAnimator);
            }
            parent.addView(guideView);
            guideView.setVisibility(View.INVISIBLE);
            guideView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    setGuideViewLocation(alignView, contentView, guideView, attachView, below, arrowId, yOffset, resetLocationListener);
                    if (guideView.getHeight() > 0) {
                        guideView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                }
            });
            return guideView;
        }
        return null;
    }
    ....
}

使用kotlin的默認(rèn)參數(shù),我們可以少寫很多重載方法

class GuideViewHelperFunc {

    companion object {
        fun showGuideView(
            activity: Activity?,
            attachView: View?,
            layoutId: Int,
            @IdRes arrowId: Int,
            alignView: Boolean = false,
            below: Boolean = false,
            yOffset: Int = 0,
            resetLocationListener: IResetLocationListener? = null,
            guideViewAnimator: GuideViewAnimator? = null
        ): View? {
            attachView?.let {
                activity?.let {
                    if (!it.isFinishing) {
                        val contentView = getContentView(activity)
                        if (contentView is FrameLayout) {
                            val parent = contentView as FrameLayout?
                            val guideView =
                                LayoutInflater.from(activity).inflate(layoutId, parent, false)
                            if (guideViewAnimator != null) {
                                guideView.setTag(R.id.guide_view_animator, guideViewAnimator)
                            }
                            parent!!.addView(guideView)
                            guideView.visibility = View.INVISIBLE
                            guideView.viewTreeObserver.addOnGlobalLayoutListener(object :
                                ViewTreeObserver.OnGlobalLayoutListener {
                                override fun onGlobalLayout() {
                                    setGuideViewLocation(
                                        alignView,
                                        contentView,
                                        guideView,
                                        attachView,
                                        below,
                                        arrowId,
                                        yOffset,
                                        resetLocationListener
                                    )
                                    if (guideView.height > 0) {
                                        guideView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                                    }
                                }
                            })
                            return guideView
                        }
                    }
                }
            }
            return null
        }

        fun setGuideViewLocation(
            alignView: Boolean,
            contentView: View?,
            guideView: View,
            attachView: View?,
            below: Boolean,
            arrowId: Int,
            yOffset: Int,
            resetLocationListener: IResetLocationListener?
        ) {
        }

        fun getContentView(activity: Activity?): ViewGroup? {
            return activity?.findViewById(android.R.id.content)
        }
    }

使用的時(shí)候,我們可以使用必填的幾個(gè)參數(shù)或者必傳參數(shù)+選傳參數(shù),必要時(shí),我們升值可以不按順序?qū)?,這里還是推薦,按照順序,并且賦值加上參數(shù)名稱,類似第三種寫法



 val layoutId = 0x22211
        val arrowId = 0x2223
        val viewGroup: ViewGroup = LinearLayout(this)

        //必傳參數(shù)
        GuideViewHelperFunc.showGuideView(this, viewGroup, layoutId, arrowId)

        //必傳參數(shù)+選傳(按順序)
        GuideViewHelperFunc.showGuideView(
            this,
            viewGroup,
            layoutId,
            arrowId,
            alignView = true,
            yOffset = 20
        )

        //必傳參數(shù)+選傳(順序打亂)
        GuideViewHelperFunc.showGuideView(
            this,
            attachView = viewGroup,
            layoutId = layoutId,
            arrowId = arrowId,
            alignView = true,
            yOffset = 20
        )

        GuideViewHelperFunc.showGuideView(
            this,
            layoutId = layoutId,
            attachView = viewGroup,
            yOffset = 20,
            alignView = true,
            arrowId = arrowId
        )

6、各種集合

Kotlin標(biāo)準(zhǔn)庫提供了基本類型的實(shí)現(xiàn):Set、List以及Map。一對(duì)接口代表每種集合類型:

  • 只讀接口:提供訪問集合元素的操作【以listOf()創(chuàng)建的集合】
  • 可變接口:具有增刪查改的功能的集合【以mutableListOf<String>()創(chuàng)建的集合】

集合圖譜:


image
List

比如說我們來看一下List集合相關(guān)的內(nèi)容,然后其實(shí)Set和Map都是差不多是如此設(shè)計(jì)的。

創(chuàng)建一個(gè)只讀的List

val list1= listOf<String>()

然后你看list1的相關(guān)api,會(huì)發(fā)現(xiàn)竟然沒有add函數(shù)???

image
image

再創(chuàng)建一個(gè)可變的List

val list2 = mutableListOf<String>()   

本質(zhì)上其實(shí)是ArrayList

然后你看list2的相關(guān)api,這下正常多了吧,這才是Java中應(yīng)該有的樣子是吧。

可變集合其實(shí)就是Java中的List最原始的樣子,而只讀集合其實(shí)就是List最原始的樣子去掉了增刪改查的各個(gè)api。這樣做,算是kotlin另辟蹊徑。在某些情況下只讀集合能保證多線程的穩(wěn)定性。當(dāng)然只讀集合并不一定是真正的永不改變的,因?yàn)镵otlin設(shè)計(jì)的與Java的互操作性,而Java是沒有只讀集合的,那么有可能Kotlin傳入Java的集合會(huì)是可變集合。

image
Set

set和list一樣也是一對(duì)集合


val set1 = setOf<String>()
val set2 = mutableSetOf<String>()

其中,mutableSetOf本質(zhì)上是LinkedHashSet。如果你想創(chuàng)建別的set集合。你可以看下這個(gè),其實(shí)是和TreeSet,HashSet等等一一對(duì)應(yīng)的

image
Map

Map和上面的List和set一樣吧,有只讀和可變的寫法。另外更重要的是,map有些寫法很特別

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")    
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // 同上

無論鍵值對(duì)的順序如何,包含相同鍵值對(duì)的兩個(gè) Map 是相等的

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)    
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)

println("The maps are equal: ${numbersMap == anotherMap}")

MutableMap 是一個(gè)具有寫操作的 Map 接口,可以使用該接口添加一個(gè)新的鍵值對(duì)或更新給定鍵的值

val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11

println(numbersMap)

{one=11, two=2, three=3}

mutableMapOf的默認(rèn)實(shí)現(xiàn)是LinkedHashMap

集合操作

Kotlin中最牛逼的我個(gè)人覺得就是跟Java Stream一樣的東西,但是又沒有版本兼容問題。

//Java 8 Android 7.0以上
 List<String> list = new ArrayList<>();
 list.stream().filter(s -> TextUtils.equals(s, "biubiu"));

Kotlin 無安卓版本限制
val list = mutableListOf<String>()
list.filter { TextUtils.equals(it, "biubiu") }

甚至可以一條龍操作

 list.filter { TextUtils.equals(it, "biubiu") }.map {
            //map操作
        }.takeWhile {
            //takeWhile操作
        }

再也不用寫那么復(fù)雜的for循環(huán)去操作數(shù)據(jù)了。

惰性求值和序列

如果擔(dān)心產(chǎn)生太多的集合,那可以用asSequence()和toList避免產(chǎn)生太多的中間list對(duì)象


list.asSequence().filter { TextUtils.equals(it, "biubiu") }.map {
            //map操作
        }.takeWhile {
            //takeWhile操作
        }.toList()

這個(gè)操作是先將集合變成序列,然后再這個(gè)序列上進(jìn)行相應(yīng)的操作,最后通過toList()轉(zhuǎn)換為集合列表。實(shí)際使用過程中,只有調(diào)用了toList()【又叫做末端操作】,才會(huì)去真正的去求值。


【中間操作】
list.asSequence().filter { TextUtils.equals(it, "biubiu") }.map {
            //map操作
        }.takeWhile {
            //takeWhile操作
        }     

7、函數(shù)方法值類型

kotlin中的方法參數(shù)中聲明的變量是final類型的,不能去改變的

image

如果非要修改,你需要

fun driveTrain(price: Int) {

        var priceTemp = price
        priceTemp = 33
        Log.e(TAG, "driverTrain一次的價(jià)錢:$priceTemp")
    }

這樣設(shè)計(jì)有利有弊,某種程度上保證了傳遞進(jìn)來的數(shù)據(jù)不被內(nèi)部"污染",但是有時(shí)候可能會(huì)增加內(nèi)存的使用。

8、高階函數(shù)

Kotlin提供了不少高端的語法特性。比如let、with、run、apply、also等我們可以用它來改善項(xiàng)目中的一些寫法。
比如let函數(shù),定義一個(gè)變量在特定的作用域范圍內(nèi)生效,返回R值。

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

比如這個(gè)是Java中Adapter中常用的操作數(shù)據(jù)的邏輯

if (holder.musicSingerTv == null) {
  return;
}
holder.musicSingerTv.setText(entity.singer);
holder.musicSingerTv.setTextColor(Color.YELLOW);

如果換成kotlin,你可以這樣

holder.musicSingerTv?.let {
            it.setText(entity.singer);
            it.setTextColor(Color.YELLOW);
        }

其他另外五種,可以去看下Kotlin系列之let、with、run、apply、also函數(shù)的使用

9、空安全

Kotlin中,變量分為可空類型和非空類型。

var str1: String? = null   可空類型
var str2: String = "ddd"   非空類型


str2 = null // 如果這樣寫,就會(huì)報(bào)錯(cuò)。要是在java中,會(huì)在運(yùn)行時(shí)候報(bào)錯(cuò)

str1 = null // 如果可控類型這樣寫,就不會(huì)報(bào)錯(cuò)

對(duì)于可空類型,為了防止空指針,你可以使用安全調(diào)用操作符?.

比如

    fun driveTrain(train: Train?) {
        train?.openDoor() ?: print("train為空了")
    }

?:是Elvis操作符,上面的調(diào)用它的意思是說, train如果為空,那么不執(zhí)行openDoor(),執(zhí)行print("train為空了")

還有一個(gè)操作符是!!

    fun driveTrain(train: Train?) {
        train.openDoor()
    }

比如現(xiàn)在train是可空類型,但是你非要調(diào)用它的openDoor()方法,你會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Train

其實(shí)是覺得當(dāng)前你的用法不安全。可以加上!!,變成這樣,程序執(zhí)行的時(shí)候有兩種可能,要么正確返回name,要么拋出空指針異常。

 train!!.openDoor()

10、關(guān)于設(shè)計(jì)模式和Kotlin

工廠模式

常用于一個(gè)父類多個(gè)子類的時(shí)候,通過其來創(chuàng)建子類對(duì)象

比如說現(xiàn)在有一個(gè)汽車工廠,同時(shí)生產(chǎn)奧迪和五菱宏光,我們用熟悉的工廠模式來描述其業(yè)務(wù)邏輯如下:

interface ICar {
    val name: String
}

class AudiCar(override val name: String) : ICar {
    fun transportPig(count: Int) {
        print("運(yùn)豬頭數(shù):$count")
    }
}

class SGMWCar(override val name: String) : ICar {
    fun transportPig(count: Int) {
        print("運(yùn)火箭枚數(shù):$count")
    }
}


class CarFactory {

    companion object {
        const val CAR_TYPE_AUDI = 0x001
        const val CAR_TYPE_SGMW = 0x002
    }

    fun produceCar(carType: Int): ICar? {
        return when (carType) {
            CAR_TYPE_AUDI -> AudiCar("奧迪")
            CAR_TYPE_SGMW -> SGMWCar("五菱")
            else -> null
        }
    }
}

fun main(args: Array<String>) {
    val audi = CarFactory().produceCar(CarFactory.CAR_TYPE_AUDI)
    audi?.transport(3)
}

這是簡單的用kotlin模仿java的工廠模式,最后的創(chuàng)建工廠去生產(chǎn)車輛這里,kotlin中還可以更簡化,因?yàn)閗otlin天生支持單例,只需要將class改為object


object CarFactorySingleton {

    const val CAR_TYPE_AUDI = 0x001
    const val CAR_TYPE_SGMW = 0x002

    fun produceCar(carType: Int):ICar? {
        return when (carType) {
            CAR_TYPE_AUDI -> AudiCar("奧迪")
            CAR_TYPE_SGMW -> SGMWCar("五菱")
            else -> null
        }
    }
}

然后使用的時(shí)候可以變得很簡潔


fun main(args: Array<String>) {
    val audi = CarFactorySingleton.produceCar(CarFactorySingleton.CAR_TYPE_AUDI)
    audi?.transport(33)
}

kotlin支持一種叫做operator操作符重載的功能【具體看操作符重載

比如上述的代碼還可以修改為


object CarFactorySingletonOperator {

    const val CAR_TYPE_AUDI = 0x001
    const val CAR_TYPE_SGMW = 0x002

    operator fun invoke(carType: Int): Car? {
        return when (carType) {
            CAR_TYPE_AUDI -> AudiCar("奧迪")
            CAR_TYPE_SGMW -> SGMWCar("五菱")
            else -> null
        }
    }
}

調(diào)用的時(shí)候直接


    //運(yùn)算符invoke
    val sgmw = CarFactorySingletonOperator(CarFactorySingletonOperator.CAR_TYPE_SGMW)
    sgmw?.transport(23)

可以看到變得更加簡潔

這里的操作符的意思其實(shí)是這樣的,比如CarFactorySingletonOperator() ----> CarFactorySingletonOperator.invoke(),所以上面重載運(yùn)算符后可以直接變成后面那樣調(diào)用了,跟直接創(chuàng)建一個(gè)類的實(shí)例沒什么區(qū)別啦
image

更強(qiáng)大的,你還可以用上kotlin的伴生對(duì)象和操作符重載的特性去更加簡潔的生成五菱宏光。
現(xiàn)在我們直接把生成的方法直接寫到ICar中,如下


interface ICar {
    val name: String
    fun transport(count: Int)

    companion object {
        operator fun invoke(carType: Int): ICar? {
            return when (carType) {
                CarFactorySingletonOperator.CAR_TYPE_AUDI -> AudiCar("奧迪")
                CarFactorySingletonOperator.CAR_TYPE_SGMW -> SGMWCar("五菱")
                else -> null
            }
        }
    }
}


    //伴生對(duì)象直接生產(chǎn)工廠對(duì)象
    val sgmw = ICar(CarFactory.CAR_TYPE_SGMW)
    sgmw?.transport(3)

觀察者和代理模式

觀察者模式和代理模式是kotlin自身支持的特性,具體可以了解下委托屬性的用法和實(shí)現(xiàn)。

委托屬性

11、快速對(duì)比Java代碼必備技能

Kotlin工具給我們提供了很好用的反編譯工具,具體操作如下

  • 1、寫一個(gè)kotlin版本的類和方法


    image
  • 2、點(diǎn)擊Android Studio的Tools中的Show Kotlin ByteCode (裝了Kotlin插件的Jertbain全家桶應(yīng)該都是這樣),這個(gè)時(shí)候你就能看到字節(jié)碼了,

image
  • 3、點(diǎn)擊字節(jié)碼窗口的Decompile,字節(jié)碼會(huì)轉(zhuǎn)化為java代碼

然后就是熟悉的Java代碼了


image

三、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容