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

目錄
一、為什么要學(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ì)比
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á)式

比如你這樣子寫了
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)造器方法了

莫慌,你還可以這樣,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)建的集合】
集合圖譜:

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ù)???


再創(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ì)是可變集合。

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)的

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類型的,不能去改變的

如果非要修改,你需要
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ū)別啦

更強(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é)碼了,

- 3、點(diǎn)擊字節(jié)碼窗口的Decompile,字節(jié)碼會(huì)轉(zhuǎn)化為java代碼
然后就是熟悉的Java代碼了

三、參考
- Kotlin中文文檔
- Kotlin系列之let、with、run、apply、also函數(shù)的使用
- Kotlin 資源大全 - 學(xué) Kotlin 看這一篇教程就夠了
- Kotlin開發(fā)者聯(lián)盟公眾號(hào)
