Kotlin "談" "彈" "潭"
本篇針對(duì)使用Java的Android開(kāi)發(fā)者,快速入手Kotlin隘庄,期間可能啰啰嗦嗦稀里糊涂緩緩乎乎穿插一些我中過(guò)的坑。這里不講Kotlin異步并發(fā)(協(xié)程)、不講Kotlin反射倦沧,如果你是來(lái)看它們的蔗怠。那我現(xiàn)在也木有墩弯。
目錄
一、為什么要學(xué)習(xí)kotlin
二寞射、基本使用
1渔工、Java寫(xiě)法和Kotlin的寫(xiě)法對(duì)比
2、基本類型
- Kotlin中數(shù)字類型
- 運(yùn)算符號(hào)
- 布爾值
- 數(shù)組
3桥温、基本表達(dá)式
- 表達(dá)式
- 流程控制
- when使你力腕狂瀾
- 使用表達(dá)式比語(yǔ)句更加安全
- 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悔政、更加簡(jiǎn)潔的語(yǔ)法減少啰嗦代碼
3、都是基于JVM編譯成字節(jié)碼延旧,與Java基本兼容基本沒(méi)有太大問(wèn)題谋国,因此可以用java寫(xiě)的很多庫(kù),生態(tài)強(qiáng)大
4迁沫、強(qiáng)大的語(yǔ)言特性
二芦瘾、基本使用
1捌蚊、Java寫(xiě)法和Kotlin的寫(xiě)法對(duì)比
2、基本類型
Kotlin中所有的東西都是對(duì)象近弟,包括基本類型缅糟,一般來(lái)說(shuō)數(shù)字、字符祷愉、布爾值窗宦、數(shù)組與字符串是組成一門(mén)語(yǔ)言的基本數(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) – 無(wú)符號(hào)右移 (Java 的 >>>)
- and(bits) – 位與
- or(bits) – 位或
- xor(bits) – 位異或
- inv() – 位非
布爾值
Boolean 類型赴涵,有兩個(gè)值true 與 false
數(shù)組
數(shù)組在 Kotlin 中使用 Array 類來(lái)表示,它定義了 get 與 set 函數(shù)(按照運(yùn)算符重載約定這會(huì)轉(zhuǎn)變?yōu)?[])以及 size 屬性
比如說(shuō)
val persons = Array<Person>(3) {
Person("2")
Person("3")
Person("4")
}
persons.size
當(dāng)然订讼,kotlin也支持簡(jiǎn)單的基本原生基本類型髓窜,無(wú)裝箱開(kāi)箱的開(kāi)銷的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沒(méi)有switch語(yǔ)句屋吨,kotlin中是更加強(qiáng)大的when語(yǔ)句蜒谤。而且kotlin的if語(yǔ)句和when語(yǔ)句可以當(dāng)做表達(dá)式來(lái)使用,就跟Java中的三目運(yùn)算符一樣至扰。
Java:
String name = isOne ? "是一個(gè)人" : "是半個(gè)人";
可以說(shuō)when是Java中if和switch的一次強(qiáng)大的聯(lián)合鳍徽。比如說(shuō),可以有這種寫(xiě)法
var single3 = when (single) {
0, 1 -> aLog("single == 0 or single == 1")
else -> aLog("其他")
}
if和while都是和java一樣的敢课,區(qū)別在于if可以當(dāng)做表達(dá)式來(lái)使用阶祭,比如
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")
}
我們也可以檢測(cè)一個(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")
}
另一種可能性是檢測(cè)一個(gè)值是(is)或者不是(!is)一個(gè)特定類型的值濒募。注意: 由于智能轉(zhuǎn)換,你可以訪問(wèn)該類型的方法與屬性而無(wú)需任何額外的檢測(cè)圾结。
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when 也可以用來(lái)取代 if-else if鏈瑰剃。 如果不提供參數(shù),所有的分支條件都是簡(jiǎn)單的布爾表達(dá)式筝野,而當(dāng)一個(gè)分支的條件為真時(shí)則執(zhí)行該分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
這些用法晌姚,可以說(shuō)一個(gè)when讓你不再寫(xiě)出混亂的if嵌套粤剧。
使用表達(dá)式比語(yǔ)句更加安全
先看一段Java代碼
private void dididada(boolean needInit) {
String a = null;
if(needInit){
a = "a is Dog that not girlfriend";
}
Log.e(TAG, a.toString());
}
這段代碼可能有潛在的問(wèn)題,比如說(shuō)a必須在if語(yǔ)句外部聲明挥唠,它被初始化為null抵恋。這段代碼中,我們忽略了else分支宝磨,如果needInit為false弧关,那么會(huì)導(dǎo)致a.toString()空指針異常問(wèn)題。并且如果needInit的值時(shí)很深的路徑傳遞過(guò)來(lái)的唤锉,那么可能會(huì)導(dǎo)致這個(gè)問(wèn)題更容易被忽略梯醒。
如果,你用了表達(dá)式
比如你這樣子寫(xiě)了
fun dididada(boolean needInit) {
String a = if(needInit){
"a is Dog that not girlfriend"
} else{
""
}
Log.e(TAG, a.toString())
}
實(shí)際上腌紧,可以更簡(jiǎn)化點(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)制需要你寫(xiě)出else語(yǔ)句茸习,也就不再存在上面說(shuō)的漏掉else的問(wèn)題了。
for循環(huán)的奧義
上面加when的時(shí)候出現(xiàn)的..關(guān)鍵字和in關(guān)鍵字這些壁肋,..這個(gè)叫做區(qū)間号胚,比如說(shuō)1..4代表從1到4的數(shù)列。比如說(shuō)
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寫(xiě)出類似的邏輯弃秆,你需要
for (int i = 1; i <= 8; i += 2) {
print(i)
}
可見(jiàn)Kotlin相對(duì)于Java的簡(jiǎn)潔
剛才上面所說(shuō)的區(qū)間都是左閉右也閉的,你還可以使用until來(lái)代替..實(shí)現(xiàn)左閉右開(kāi)區(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è)接口必盖,也是可以使用:來(lái)達(dá)到目的拌牲,可以說(shuō)是統(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) { ... }
什么情況可以省略呢,比如說(shuō)你不需要給name添加注解或修改可見(jiàn)性的時(shí)候,那比如說(shuō)装盯,如果你想在主構(gòu)造器初始化的時(shí)候在里面寫(xiě)一串初始化邏輯坷虑,要怎么樣呢?用Java的時(shí)候你應(yīng)該是這樣的
class Pig {
public Pig(String name) {
//初始化邏輯
}
}
但是現(xiàn)在kotlin使用主構(gòu)造器初始化的話埂奈,你在也看不到可愛(ài)的構(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("名字長(zhǎng)度:${name.length}")
}
}
執(zhí)行的時(shí)候
Pig("你是豬豬豬", 5)
輸出:
你是豬豬豬
名字長(zhǎng)度:5
次構(gòu)造器就跟Java中差不多了,但是不同的地方在于必須要有constructor關(guān)鍵字來(lái)聲明
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("名字長(zhǎng)度:${name.length}")
}
}
執(zhí)行的時(shí)候
Pig("你是豬豬豬")
輸出:
1.你是豬豬豬
2.名字長(zhǎng)度:5
3.次構(gòu)造器
可見(jiàn)液茎,不管你的init塊在哪里,init塊會(huì)先執(zhí)行完畢
伴生對(duì)象
kotlin中沒(méi)有靜態(tài)內(nèi)部類的概念辞嗡,取而代之的是伴生對(duì)象這貨
class Pig {
companion object Factory {
fun create(): pig = Pig()
}
}
該伴生對(duì)象的成員可通過(guò)只使用類名作為限定符來(lái)調(diào)用捆等,比如說(shuō)
kotlin中調(diào)用
val instance = Pig.create()
Java中調(diào)用
Pig pig = Pig.Companion.create()
可見(jiàn),伴生對(duì)象可以讓我們輕松實(shí)現(xiàn)工廠模式续室,造豬運(yùn)動(dòng)
單例類
平時(shí)用到最多的類就是單例類栋烤,kotlin自帶單例特性,比如
object Demo{
fun dadada(){
}
}
使用的時(shí)候挺狰,只需要寫(xiě)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)部類版本的單例寫(xiě)法薯定。
當(dāng)然,如果你想自己寫(xiě)單例趁耗,你可以用Java那套自己去擼沉唠。比較高端的用法,由于Kotlin有委托屬性的概念苛败,可以用lazy屬性來(lái)寫(xiě)一個(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)聽(tīng)事件的事件回調(diào)等等。
kotlin中的匿名內(nèi)部類是要以object開(kāi)頭的篇亭,比如Java中的
mExpandMenu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
kotlin的寫(xiě)法
view.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
//onClick代碼塊
}
})
你可以更精簡(jiǎn)
view.setOnClickListener {
//onClick代碼塊
}
數(shù)據(jù)類
我們經(jīng)常會(huì)使用到一些只保存數(shù)據(jù)的類缠捌,java中需要自己去寫(xiě)屬性和get和set方法,在kotlin中,可以使用數(shù)據(jù)類來(lái)避免模版代碼曼月。
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這種寫(xiě)法是函數(shù)默認(rèn)值寫(xiě)法聪姿,后面會(huì)單獨(dú)介紹碴萧。
又new了一個(gè)對(duì)象出來(lái),達(dá)到復(fù)制的目的末购。
data class數(shù)據(jù)類能讓我們少寫(xiě)bean類很多代碼破喻。
5、各種函數(shù)
lambda表達(dá)式
上面有個(gè)地方其實(shí)有提到過(guò)kotlin的lambda表達(dá)式
view.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
//onClick代碼塊
}
})
你可以更精簡(jiǎn)
view.setOnClickListener {
//onClick代碼塊
}
精簡(jiǎn)后的其實(shí)就是lambda表達(dá)式本身盟榴,下面我們來(lái)看一個(gè)例子
val list = listOf<String>()
val filterList = list.filter { it == "pig" }
創(chuàng)建一個(gè)List然后過(guò)濾出來(lái)數(shù)據(jù)時(shí)pig的元素
來(lái)看一下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
}
可以看見(jiàn)filter接收的是一個(gè)(T) -> Boolean類型的predicate參數(shù)曹质,其實(shí)它就是lambda類型(Kotlin中所有的東西都是對(duì)象,包括這個(gè)玩意)擎场,表示接收一個(gè)T類型的參數(shù)返回一個(gè)Boolean類型lambda表達(dá)式咆繁。這里的predicate會(huì)一直往下傳,直到真正的過(guò)濾函數(shù)filterTo中顶籽,這里寫(xiě)的比較簡(jiǎn)潔玩般,可以嘗試還原,加上大括號(hào)
for (element in this) {
if (predicate(element)) {
destination.add(element)
}
}
return destination
其實(shí)就是遍歷該集合內(nèi)容礼饱,條件是傳進(jìn)來(lái)的lambda表達(dá)式坏为,我們現(xiàn)在外層傳進(jìn)來(lái)的是it == "pig",再度還原镊绪,得到如下代碼匀伏,當(dāng)元素等于pig的時(shí)候,將元素添加到返回的集合中蝴韭,最后返回新的集合够颠。
for (element in this) {
if (element == "pig") {
destination.add(element)
}
}
return destination
Kotlin中提供了簡(jiǎn)潔的語(yǔ)法去定義函數(shù)的類型,大致如下榄鉴,當(dāng)然還有很多變種
() -> Unit//表示無(wú)參數(shù)無(wú)返回值的Lambda表達(dá)式類型
(T) -> Unit//表示接收一個(gè)T類型參數(shù)履磨,無(wú)返回值的Lambda表達(dá)式類型,這種其實(shí)就是上面說(shuō)的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種使用場(chǎng)景,很明顯是更加簡(jiǎn)潔易懂,不啰嗦(當(dāng)然聊品,前提是得知道原理飞蹂,不然一臉懵),但是也有可能造成調(diào)試bug成本增加(難以定位問(wèn)題代碼)翻屈。各有利弊
內(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開(kāi)銷的產(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語(yǔ)法篇之lambda編譯成字節(jié)碼過(guò)程完全解析(七)
擴(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)類中使用哭尝。我們可以拿這一特性來(lái)做很多事情。比如說(shuō)剖煌,ImageView結(jié)合Gilde的使用材鹦、findViewById、比如工廠方法模式的擴(kuò)展(后面具體講)耕姊。
以我們項(xiàng)目中常用的Glide工具類為準(zhǔn)桶唐,這里先看下ImageView這些怎么拓展。
比如說(shuō)我們之前是將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中怎么樣消除冗長(zhǎng)的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中(比如說(shuō)Activity的布局是activity_main)
import kotlinx.android.synthetic.main.activity_main.*
比如我們現(xiàn)在的布局xml是這樣子寫(xiě)
<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來(lái)對(duì)view進(jìn)行操作
toolbar.setLogo(R.mipmap.ic_launcher)
方法默認(rèn)參數(shù)
意思就是,方法的參數(shù)可以指定默認(rèn)的值规脸,函數(shù)調(diào)用的時(shí)候坯约,如果沒(méi)有攜帶參數(shù),那么使用函數(shù)中的默認(rèn)值燃辖,以我們的常用的未重構(gòu)未Builder模式之前的GuideViewHelper為例鬼店,看一下要怎么樣用kotlin來(lái)重構(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ù)黔龟,我們可以少寫(xiě)很多重載方法
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ù)名稱航徙,類似第三種寫(xiě)法
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)庫(kù)提供了基本類型的實(shí)現(xiàn):Set陷虎、List以及Map到踏。一對(duì)接口代表每種集合類型:
- 只讀接口:提供訪問(wèn)集合元素的操作【以listOf()創(chuàng)建的集合】
- 可變接口:具有增刪查改的功能的集合【以mutableListOf<String>()創(chuàng)建的集合】
集合圖譜:
List
比如說(shuō)我們來(lái)看一下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)竟然沒(méi)有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是沒(méi)有只讀集合的锉罐,那么有可能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一樣吧秒紧,有只讀和可變的寫(xiě)法。另外更重要的是挨下,map有些寫(xiě)法很特別
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") // 同上
無(wú)論鍵值對(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è)具有寫(xiě)操作的 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è)人覺(jué)得就是跟Java Stream一樣的東西臭笆,但是又沒(méi)有版本兼容問(wèn)題叙淌。
//Java 8 Android 7.0以上
List<String> list = new ArrayList<>();
list.stream().filter(s -> TextUtils.equals(s, "biubiu"));
Kotlin 無(wú)安卓版本限制
val list = mutableListOf<String>()
list.filter { TextUtils.equals(it, "biubiu") }
甚至可以一條龍操作
list.filter { TextUtils.equals(it, "biubiu") }.map {
//map操作
}.takeWhile {
//takeWhile操作
}
再也不用寫(xiě)那么復(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)的操作闻鉴,最后通過(guò)toList()轉(zhuǎn)換為集合列表。實(shí)際使用過(guò)程中茂洒,只有調(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à)錢(qián):$priceTemp")
}
這樣設(shè)計(jì)有利有弊智哀,某種程度上保證了傳遞進(jìn)來(lái)的數(shù)據(jù)不被內(nèi)部"污染"次询,但是有時(shí)候可能會(huì)增加內(nèi)存的使用。
8盏触、高階函數(shù)
Kotlin提供了不少高端的語(yǔ)法特性渗蟹。比如let、with赞辩、run雌芽、apply、also等我們可以用它來(lái)改善項(xiàng)目中的一些寫(xiě)法辨嗽。
比如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 // 如果這樣寫(xiě)累铅,就會(huì)報(bào)錯(cuò)。要是在java中站叼,會(huì)在運(yùn)行時(shí)候報(bào)錯(cuò)
str1 = null // 如果可控類型這樣寫(xiě)娃兽,就不會(huì)報(bào)錯(cuò)
對(duì)于可空類型,為了防止空指針尽楔,你可以使用安全調(diào)用操作符?.
比如
fun driveTrain(train: Train?) {
train?.openDoor() ?: print("train為空了")
}
?:是Elvis操作符投储,上面的調(diào)用它的意思是說(shuō)第练, 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í)是覺(jué)得當(dāng)前你的用法不安全冲泥。可以加上!!壁涎,變成這樣凡恍,程序執(zhí)行的時(shí)候有兩種可能,要么正確返回name怔球,要么拋出空指針異常嚼酝。
train!!.openDoor()
10、關(guān)于設(shè)計(jì)模式和Kotlin
工廠模式
常用于一個(gè)父類多個(gè)子類的時(shí)候竟坛,通過(guò)其來(lái)創(chuàng)建子類對(duì)象
比如說(shuō)現(xiàn)在有一個(gè)汽車(chē)工廠闽巩,同時(shí)生產(chǎn)奧迪和五菱宏光,我們用熟悉的工廠模式來(lái)描述其業(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)
}
這是簡(jiǎn)單的用kotlin模仿java的工廠模式担汤,最后的創(chuàng)建工廠去生產(chǎn)車(chē)輛這里涎跨,kotlin中還可以更簡(jiǎn)化,因?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í)候可以變得很簡(jiǎn)潔
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)
可以看到變得更加簡(jiǎn)潔
這里的操作符的意思其實(shí)是這樣的隅很,比如CarFactorySingletonOperator() ----> CarFactorySingletonOperator.invoke(),所以上面重載運(yùn)算符后可以直接變成后面那樣調(diào)用了率碾,跟直接創(chuàng)建一個(gè)類的實(shí)例沒(méi)什么區(qū)別啦
更強(qiáng)大的叔营,你還可以用上kotlin的伴生對(duì)象和操作符重載的特性去更加簡(jiǎn)潔的生成五菱宏光。
現(xiàn)在我們直接把生成的方法直接寫(xiě)到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、寫(xiě)一個(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開(kāi)發(fā)者聯(lián)盟公眾號(hào)