5.1 簡化表達(dá)
舉個Android里面最常用的例子,java總普遍的用法
view.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
...
}
})
翻譯成kotlin并且簡化
view.setOnClickListener(object:OnClickListener {
override fun void onClick(v:View){
...
}
})
按照J(rèn)ava 單一抽象方法OnClickListener 可以用Kotlin函數(shù)替代
fun setOnclickListener(listener:(View)->Unit)
->Lambda
view.setOnClickListener({
沉御。塘娶。。
})
-> kotlin語法糖衅鹿,listener是唯一的參數(shù)毅厚,所以可以省略括號
view.setOnClickListener{
塞颁。。吸耿。
}
帶有接收者的Lambda
View 接收者類型擴展invisible方法
fun View.invisible(){
visibility = View.INVISIBLE
}
接收者函數(shù)類型
val sum:Int.(Int)->Int = { other ->plus(other)}
fun main() {
println(2.sum(1))
}
int型變量調(diào)用sum傳入一個int型變量參數(shù)祠锣,進(jìn)行plus操作
官網(wǎng)一個小例子
class HTML{
fun body(){
println("Test custom HTML")
}
}
fun html(init:HTML.()->Unit):HTML{
val html=HTML()
html.init()
return html
}
// 調(diào)用
html{
body()
}
with 和 apply
作用:再寫Lambda時候,省略需要多次書寫的對象名咽安,默認(rèn)用this指向它
Android中我們會給視圖控件綁定屬性伴网。with例子
fun bindData(bean:ContentBean){
val titleTv = findViewById<TextView>(R.id.titleTv)
val contentTv = findViewById<TextView>(R.id.contentTv)
with(bean){
titleTv.text = this.title
contentTv.text = this.content
titleTv.textSize = this.titleFontSize
contentTv.textSize = this.contentFontSize
}
}
不用with會寫很多重復(fù)的bean
with在kotlin中的定義
public inline fun <T, R> with(receiver: T, block: T.() -> R): R
第一個參數(shù)是一個接收者,第二個參數(shù)是一個創(chuàng)建這個類型的block妆棒。因此在接收者調(diào)用block的時候可以在Lambda直接使用this代替bean
看下apply 的寫法
fun bindData(bean:ContentBean){
val titleTv = findViewById<TextView>(R.id.titleTv)
val contentTv = findViewById<TextView>(R.id.contentTv)
bean.apply {
titleTv.text = this.title
contentTv.text = this.content
titleTv.textSize = this.titleFontSize
contentTv.textSize = this.contentFontSize
}
}
apply 的定義
public inline fun <T> T.apply(block: T.() -> Unit): T
直接聲明了一個T的擴展澡腾,block參數(shù)是一個返回Unit類型的函數(shù)
with的block返回自由的類型沸伏。with和apply很多情況可以互相替代
5.2 集合的高階函數(shù)API
map 簡化
java8 之前的遍歷
int[] list = {1,2,3,4,5,6};
int newList[] = new int[list.length];
for (int i = 0; i < list.length; i++) {
newList[i] = list[i] * 2;
}
java8
int newList[] = Arrays.stream(list).map(x-> x*2).toArray();
kotlin
val list = listOf(1,2,3,4,5,6);
val newList = list.map{it*2}
map 后面的表達(dá)式就是一個匿名帶參的函數(shù), map接收函數(shù)
fun foo3(bar:Int) = bar * 2
val newList2 = list.map { foo(it) }
map 接收函數(shù),然后將集合每個元素用這個函數(shù)操作动分,將操作結(jié)果返回毅糟,最后生成一個新的集合
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
}
首先定義了map的擴展方法,他的實現(xiàn)主要依賴mapTo澜公。兩個參數(shù)姆另,第一個MutableCollection 集合,第二個是一個方法 (transform: (T) -> R)坟乾。就是將transform方法產(chǎn)生的結(jié)果添加到新集合里面去迹辐,返回一個新集合,避免我們for并且產(chǎn)生臨時變量
filter甚侣、count 集合篩選
data class Student(val name:String,val age:Int,val sex:String,val score:Int)
val jielun = Student("jielun",30,"m",85);
val david = Student("david",35,"f",80);
val lilei = Student("lilei",32,"m",90);
val lili = Student("",31,"m",97);
val jack = Student("jack",18,"m",92);
val pan = Student("pan",20,"m",82);
val students = listOf(jielun,david,lilei,lili,jack,pan)
val mstudents= students.filter { it.sex == "m" }
看下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
}
看完map源碼filter就比較好理解明吩,依賴filterTo,接收兩個參數(shù),第一個MutableCollection 集合渺绒,第二個 predicate: (T) -> Boolean 返回boolean的函數(shù)(Lambda表達(dá)式)函數(shù)返回true保留否則丟棄
其他過率方法還有
filterNot,過率掉滿足條件的
filterNotNull,過率掉null元素
count,統(tǒng)計滿足條件的元素個數(shù)----顯得到滿足條件列表再統(tǒng)計贺喝,效率有點低
val stuCount= students.filter { it.sex == "m" }.size
sumBy菱鸥、sum宗兼、fold、reduce 別樣的求和方式
java中我們做法是for然后累加氮采,用sumBy 一行
val scoleTotal = students.sumBy { it.score }
拿最開始的list的int數(shù)組求和殷绍,sum和sumBy差不多
val total = list.sum()
val total2 = list.sumBy { it }
fold 是一個比較強的API ,先看下實現(xiàn)
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
var accumulator = initial
for (element in this) accumulator = operation(accumulator, element)
return accumulator
}
fold 接收兩個參數(shù)鹊漠,第一個主到,initial 初始值,第二個是一個operation函數(shù)躯概。 實現(xiàn)的時候通過for遍歷集合每個元素登钥,每次都調(diào)用operation函數(shù)---也有兩個參數(shù),第一個是上一次調(diào)用這個函數(shù)的結(jié)果(第一次使用initial出事值)娶靡,第二個參數(shù)就是當(dāng)前遍歷的元素牧牢。
如何使用
val foldTotal = students.fold(0){acumulator,student->acumulator+student.score}
fold很好的利用了遞歸思想
reduce 和 fold很相似,唯一區(qū)別沒有初始值,看下源碼
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
val iterator = this.iterator()
if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
var accumulator: S = iterator.next()
while (iterator.hasNext()) {
accumulator = operation(accumulator, iterator.next())
}
return accumulator
}
如果我們不需要初始值姿锭,那么可以用reduce
val reduceTotal = students.reduce{acumulator,student->acumulator+student.score}
groupBy 分組
通常我們用java 寫很多for和if進(jìn)行循環(huán)和條件判斷塔鳍,kotlin groupBy提供了語法糖
students.groupBy { it.sex }
返回 Map<String,List<Student>>
{m=[Student(name=jielun, age=30, sex=m, score=85), Student(name=lilei, age=32, sex=m, score=90), Student(name=, age=31, sex=m, score=97), Student(name=jack, age=18, sex=m, score=92), Student(name=pan, age=20, sex=m, score=82)], f=[Student(name=david, age=35, sex=f, score=80)]}
扁平化--處理嵌套集合:flatMap,flatten
將list2 變成和前面list一樣的普通集合
val list2 = listOf(listOf(jielun, david), listOf(lilei, lili, jack), listOf(pan))
println(list2.flatten())
>>>>>輸出
[Student(name=jielun, age=30, sex=m, score=85), Student(name=david, age=35, sex=f, score=80), Student(name=lilei, age=32, sex=m, score=90), Student(name=, age=31, sex=m, score=97), Student(name=jack, age=18, sex=m, score=92), Student(name=pan, age=20, sex=m, score=82)]
看下源碼
public fun <T> Iterable<Iterable<T>>.flatten(): List<T> {
val result = ArrayList<T>()
for (element in this) {
result.addAll(element)
}
return result
}
就是循環(huán)遍歷多個集合合并成了一個集合
如果我們需要得到的是加工一下的集合 flatMap
list3.flatMap { it.map {it.name} }
>>>>>輸出
[jielun, david, lilei, , jack, pan]
看下flatMap源碼
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
return flatMapTo(ArrayList<R>(), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
flatMapTo 接收兩個參數(shù),第一個為一個列表呻此,改列表是一個空列表轮纫。另外一個是一個函數(shù)。改函數(shù)返回一個序列焚鲜。
遍歷集合的元素掌唾,然后將集合元素傳入transform的到一個列表放前,將列表元素添加到空列表destination中,這樣經(jīng)過transform函數(shù)處理的扁平化列表糯彬。
如果需要扁平化處理集合flattern就好犀斋,需要對元素加工那么用flatMap
5.3 集合的相關(guān)設(shè)計
1.list 是一個可以重復(fù)的列表,元素存儲方式是線性存儲
println(listOf(1,2,3,4,5,6))
>>>>
[1, 2, 3, 4, 4, 6]
- map 沒有實現(xiàn)Iterator和Collection情连。Map用來表示鍵值對元素集合叽粹,鍵不能重復(fù)
println(mapOf(1 to 1 ,2 to 2,3 to 3,3 to 4))
>>>
{1=1, 2=2, 3=4}
- Set 表示一個補課重復(fù)的集合。實現(xiàn)有兩種HashSet 是Hash散列存儲無序和TreeSet 底層是二叉樹却舀,有序虫几。我們一般說的是無序
println(setOf(1,2,3,3,4,4,5))
>>>
[1, 2, 3, 4, 5]
可變集合與只讀集合
kotlin 雖然是基于java但是做了改動,分為可變集合和不可變集合
- 可變集合挽拔,都有一個前綴“Mutable”
val mutableList = mutableListOf(1,2,3,4)
mutableList[0] = 0
println(mutableList)
>>>>
[0, 2, 3, 4]
2 只讀集合
val list = listOf(1,2,3,4) 如果嘗試改變編譯器報錯
特殊情況:被改
val writeList = mutableListOf(1,2,3,4,5)
val readList:List<Int> = writeList
writeList[0] = 0
println(readList)
>>>
[0, 2, 3, 4, 5]
和java互相操作
-》java
public static List<Integer> fooJava(List<Integer> list){
for (int i = 0; i < list.size(); i++) {
list.set(i,list.get(i) * 2);
}
return list;
}
-》kotlin
fun bar(list:List<Int>){
println(fooJava(list))
}
->
val list = listOf(1,2,3,4)
bar(list)
println(list)
》》》
[2, 4, 6, 8]
[2, 4, 6, 8]
傳入的list被改變了
5.4 惰性集合
如果集合元素數(shù)量比較大辆脸,使用上線的操作效率比較低
通過序列化提高效率
val list = listOf(1,2,3,4)
list.filter{it > 2}.map{it * 2}
這樣會操作兩個臨時集合先filter,然后返回集合在用map處理產(chǎn)生新的集合
如果filter處理數(shù)據(jù)量大螃诅,開銷就很大
使用序列
list.asSequence().filter{it > 2}.map{it * 2}
中間操作
list.asSequence().filter{
println("filter$it")
it > 2
}.map{
println("map$it")
it * 2
}
什么也沒輸出啡氢,知道末尾加上“.toList()”才輸出了結(jié)果
末端操作
toList() 就是末端操作,就是鏈?zhǔn)秸{(diào)用最后需要輸出結(jié)果而不是序列化的東西术裸。
對比asSequence 和不加輸出的結(jié)果
list.asSequence().filter{
println("filter($it)")
it > 2
}.map{
println("map($it)")
it * 2
}.toList()
>>>>>>>>>>>
filter(1)
filter(2)
filter(3)
map(3)
filter(4)
map(4)
所有的中間操作被執(zhí)行
ilter{
println("filter($it)")
it > 2
}.map{
println("map($it)")
it * 2
}
>>>>>>>>
filter(1)
filter(2)
filter(3)
filter(4)
map(3)
map(4)
普通的鏈?zhǔn)秸{(diào)用先執(zhí)行玩filter在執(zhí)行map倘是。所以建議能先用filter的盡量先用filter。
序列可以是無限的
惰性計算最大的好處就是構(gòu)造出來一個無限的數(shù)據(jù)類型袭艺。
// 創(chuàng)造無限序列
val naturalNumList = generateSequence(0){it +1 }
println(naturalNumList.takeWhile { it<=9 }.toList())
>>>>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
雖然不能窮舉狀態(tài)搀崭,但是我們可以通過條件控制我們想要多少數(shù)據(jù)。給我們一個無限的感覺
和 java8 Stream對比
- java8 使用函數(shù)式API
上面students 學(xué)生按照性別篩選
students.stream().filter(it -> it.sex == "m").collect(toList());
類似kotlin的序列猾编,java需要將集合轉(zhuǎn)換成stream流瘤睹,操作完成后,還要將stream轉(zhuǎn)換為List答倡,java8這種也是惰性計算的
- java8 的stream是一次性的
如果創(chuàng)建了一個stream轰传,在這個stream上只能遍歷一次,這個流就被消費掉瘪撇。必須創(chuàng)建新的stream才能再次遍歷
students.stream().filter(it -> it.sex == "m").collect(toList());
students.stream().filter(it -> it.sex == "f").collect(toList());
3.Stream 能夠并行處理數(shù)據(jù)(kotlin 目前貌似還不行)
students.parallelStream().filter(it -> it.sex == "m").collect(toList());
多核架構(gòu)可以并行處理
5.4 內(nèi)聯(lián)函數(shù)
Lambda會帶來一定的開銷获茬。內(nèi)聯(lián)函數(shù)主要是針對Lambda的優(yōu)化。
java卻不需要设江,因為java7之后jvm引入了invokedynamic
的技術(shù)锦茁,他會自動做lambda優(yōu)化
優(yōu)化Lambda
Lambda雖然語法簡單,但是在Kotlin每聲明一個Lambda就會產(chǎn)生一個匿名函類叉存,該匿名類包含一個invoke方法码俩,作為Lambda的調(diào)用方法,每次調(diào)用還會創(chuàng)建一個對象歼捏。因為Kotlin主要是對Android的開發(fā)語言稿存,Kotlin在Android中必須引入某有方法來優(yōu)化笨篷,就是內(nèi)聯(lián)函數(shù)
1.java的Invokedynamic
與kotlin這種再編譯器通過硬編碼生成Lambda轉(zhuǎn)換類機制不同,java在se7 之后通過invokedynamic實現(xiàn)了在運行期才產(chǎn)生相應(yīng)翻譯代碼瓣履。在invokedynamic 被調(diào)用的時候會產(chǎn)生一個匿名類替換中間代碼invokedynamic率翅,后續(xù)會直接使用這個匿名類。
- 因為運行時候產(chǎn)生袖迎,字節(jié)碼只能看到固定的invokedynamic冕臭,需要靜態(tài)生成類的個數(shù)及字節(jié)碼大小顯著減小。
- 編譯時候?qū)懰雷止?jié)碼不同燕锥,利用invokedynamic可以把實際的翻譯策略隱藏在jdk庫中辜贵,提高了靈活性,向后兼容归形,后期可以繼續(xù)對翻譯策略優(yōu)化
- JMM天然支持針對這種方式Lambda的翻譯和優(yōu)化托慨,開發(fā)者開發(fā)時候不需要關(guān)心這些優(yōu)化。
2 內(nèi)聯(lián)函數(shù)
Kotlin 為了Android要兼容java se6 所以不能采用invokedynamic暇榴。
采用了另一種解決方案內(nèi)聯(lián)函數(shù)厚棵。c++和C#等語言也支持這種特性。簡單說可以用inline關(guān)鍵字來修飾函數(shù)蔼紧。這些函數(shù)體編譯期間被嵌入到每一個被調(diào)用的地方婆硬,減少額外生成匿名類和減少時間開銷。
我們看一個普通自定義高階函數(shù)foo歉井,接受一個類型()->Unit 的Lambda
fun foo(block:()->Unit){
println("before block")
block()
println("after block")
}
fun main() {
foo {
println("dive into kotlin...")
}
}
反編譯查看
public final class KT內(nèi)聯(lián)函數(shù)Kt {
public static final void foo(@NotNull Function0 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
String var1 = "before block";
boolean var2 = false;
System.out.println(var1);
block.invoke();
var1 = "after block";
var2 = false;
System.out.println(var1);
}
public static final void main() {
foo((Function0)null.INSTANCE);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
調(diào)用foo產(chǎn)生一個Function0的類型的block類柿祈,然后通過invoke方法執(zhí)行哈误,這樣會增加額外的開銷哩至。我們給foo函數(shù)加上inline修飾符
public final class KT內(nèi)聯(lián)函數(shù)Kt {
public static final void foo(@NotNull Function0 block) {
int $i$f$foo = 0;
Intrinsics.checkParameterIsNotNull(block, "block");
String var2 = "before block";
boolean var3 = false;
System.out.println(var2);
block.invoke();
var2 = "after block";
var3 = false;
System.out.println(var2);
}
public static final void main() {
int $i$f$foo = false;
String var1 = "before block";
boolean var2 = false;
System.out.println(var1);
int var3 = false;
String var4 = "dive into kotlin...";
boolean var5 = false;
System.out.println(var4);
var1 = "after block";
var2 = false;
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
上面集合高階函數(shù)API也是用內(nèi)聯(lián)函數(shù)實現(xiàn)的,所以內(nèi)聯(lián)函數(shù)優(yōu)化上很有必要蜜自。
內(nèi)聯(lián)函數(shù)不是萬能菩貌,不要隨便用
對于普通函數(shù)沒必要用內(nèi)聯(lián)
避免有大量函數(shù)體的函數(shù)內(nèi)聯(lián),會導(dǎo)致過多字節(jié)碼量
一旦一個函數(shù)被內(nèi)聯(lián)重荠,就不能獲取閉包的私有成員箭阶。除非聲明為internal
noinline 避免函數(shù)參數(shù)被內(nèi)聯(lián)
有時候函數(shù)需要接收多個參數(shù),我們只想讓Lambda參數(shù)內(nèi)聯(lián)戈鲁。我們可以noinline加到不想內(nèi)聯(lián)參數(shù)的開頭
做個測試
inline fun foo(block:()->Unit,noinline block2:()->Unit){
println("before block")
block()
block2()
println("after block")
}
fun main() {
foo ({
println("dive into kotlin inline...")
},{
println("no inline...")
})
}
對比查看字節(jié)碼
public final class KT內(nèi)聯(lián)函數(shù)Kt {
public static final void foo(@NotNull Function0 block, @NotNull Function0 block2) {
int $i$f$foo = 0;
Intrinsics.checkParameterIsNotNull(block, "block");
Intrinsics.checkParameterIsNotNull(block2, "block2");
String var3 = "before block";
boolean var4 = false;
System.out.println(var3);
block.invoke();
block2.invoke();
var3 = "after block";
var4 = false;
System.out.println(var3);
}
public static final void main() {
Function0 block2$iv = (Function0)null.INSTANCE;
int $i$f$foo = false;
String var2 = "before block";
boolean var3 = false;
System.out.println(var2);
int var4 = false;
String var5 = "dive into kotlin inline...";
boolean var6 = false;
System.out.println(var5);
block2$iv.invoke();
var2 = "after block";
var3 = false;
System.out.println(var2);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
可以看到block2 沒有被內(nèi)聯(lián)
非局部返回
fun foo(){
println("before local return")
localReturn()
println("after local return")
return
}
fun localReturn() {
return
}
fun main() {
foo()
}
>>>>>輸出
before local return
after local return
localReturn 沒有起作用
fun foo(returnning:()->Unit){
println("before local return")
returnning()
println("after local return")
return
}
fun main() {
foo{return}
------報錯
}
報錯了仇参,Lambda表達(dá)式正常情況不能return,修改加inline
inline fun foo(returnning:()->Unit){
println("before local return")
returnning()
println("after local return")
return
}
fun main() {
foo{return}
}
》》》》》
before local return
中間返回了,因為inline lamba 被替代婆殿,所以return生效
crossinline
為了避免帶有return的Lambda參數(shù)產(chǎn)生破壞诈乒,可以阻止此類問題,用crossinline關(guān)鍵字修飾
fun main() {
------這里報錯
foo{return}
// testfoo { return@testfoo }
}
inline fun testfoo(crossinline returnning:()->Unit){
println("before local return")
returnning()
println("after local return")
return
}
具體化參數(shù)類型 reified
由于Kotlin也有泛型擦除機制婆芦,我們無法獲得一個參數(shù)的類型怕磨。然而喂饥,由于內(nèi)聯(lián)函數(shù)直接在字節(jié)碼生成相應(yīng)函數(shù)體現(xiàn),我們又可以獲得具體類型肠鲫。我們可以用reifield修飾符實現(xiàn)
fun main() {
getType<Int>()
}
inline fun <reified T>getType(){
println(T::class)
}
>>>>>
class java.lang.Integer (Kotlin reflection is not available)
這個在Android里比較有用
inline fun <reified T:Activity> Activity.startActivity(){
startActivity(this,T::class.java)
}
調(diào)用员帮,簡化代碼
startActivity<MainActivity>()