前言
以一個java
老鳥的角度氨距,如何去看 kotlin
针贬。 Java源代碼應(yīng)該如何用Kotlin重構(gòu)。 如何正確學(xué)習(xí)kotlin并且應(yīng)用到實際開發(fā)中赋兵。本文將會探究笔咽。
本文分兩大塊,重難點和潛規(guī)則霹期。
重難點:Kotlin中可以獨立出來講解的大塊知識點叶组。提供單獨Demo。這部分大多數(shù)是Kotlin開創(chuàng)的新概念(相比于Java)历造。
潛規(guī)則:Kotlin是谷歌用來替換Java的甩十,它和java百分百完全兼容,但是實際上java轉(zhuǎn)成kotlin之后吭产,需要我們手動修改很多東西侣监,甚至某些部分必須打散重構(gòu)來達到最優(yōu)編碼。其中臣淤,kotlin的某些特性和java不同橄霉,甚至完全反轉(zhuǎn)。這部分知識點比較零碎邑蒋,單獨Demo不方便提供姓蜂,就以小例子的形式來寫。
正文大綱
-
重難點
- lambda以及操作符
- 高階函數(shù)以及操作符
-
Kotlin
泛型 - 集合操作
- 協(xié)程
- 操作符重載
-
潛規(guī)則
Kotlin文件和類不存在一對一關(guān)系
共生體
繼承
修飾符
空指針問題
正文
重難點
lambda表達式
lambda表達式是JDK1.8提出的寺董,目的是為了改善Java中大量出現(xiàn)的只有一個方法的回調(diào)函數(shù)的累贅寫法覆糟。這里不討論Java的lambda. Kotlin中l(wèi)ambda已經(jīng)融入代碼核心,而不是像java一樣是個注解+語法糖遮咖。
基礎(chǔ):
一個lambda表達式如圖:
lambda 表達式的4個部分
- 外圍的{}大括號
- 參數(shù)列表:x:Int,y:Int
- 連接符 ->
- 方法體 x+y
舉個栗子
這是一個kotlin文件:
/**
* 比如說這樣滩字,我要給這個doFirst方法傳一個執(zhí)行過程,類型就是一個輸入2個Int御吞,輸出一個Int的拉姆達表達式
*/
fun calculate(p1: Int, p2: Int, event1: (Int, Int) -> Int, event2: (Int, Int) -> Int) {
println("執(zhí)行doFirst")
println("執(zhí)行event1 ${event1(p1, p2)}")
println("執(zhí)行event2 ${event2(p1, p2)}")
}
//測試lambda表達式
fun main() {
val sum = { x: Int, y: Int ->
print("求和 ")
x + y
}
val diff = { x: Int, y: Int ->
print("求差 ")
x - y
}
//kotlin里面麦箍,可以把拉姆達表示當作一個普通變量一樣,去傳遞實參
calculate(p1 = 1, p2 = 2, event1 = sum, event2 = diff)
}
定義了一個calculate函數(shù), p1陶珠,p2 是Int挟裂,而event1和event2 則是 lambda表達式. 高級語言特性,一等函數(shù)公民:函數(shù)本身可以被當作普通參數(shù)一樣去傳遞揍诽,并且調(diào)用诀蓉。那么栗竖, kotlin的lambda內(nèi)核是怎么樣的?
上圖能夠看出,
- calculate方法的后面2個參數(shù)渠啤,被編譯成了 Function2 類型狐肢。
- 執(zhí)行event1,event2沥曹,則是調(diào)用其invoke方法
- main函數(shù)中份名,出現(xiàn)了null.INSTANCE, 這里比較詭異,INSTANCE應(yīng)該是用來獲取實例的妓美,但是為什么是null.INSTANCE
而看了Function2的源碼僵腺,簡直亮瞎了我的鈦合金狗眼:
Function.kt文件中:
Function接口,F(xiàn)unction2接口.....Function22接口壶栋。好了辰如,不要質(zhì)疑谷歌大佬的設(shè)計思路,反正大佬寫的就是對的...這里用22個接口(至于為什么是22個委刘,猜想是谷歌覺得不會有哪個腦子秀逗的開發(fā)者真的弄22個以上的參數(shù)擠在一起吧)丧没,表示kotlin開發(fā)中可能出現(xiàn)的lambda表達式參數(shù)列表。
再舉個栗子
給一個Button 設(shè)置點擊事件,kotlin的寫法是:
val btn = Button(this)
val lis = View.OnClickListener { println("111") }
btn.setOnClickListener(lis)
或者:
val btn = Button(this)
btn.setOnClickListener { println("111") }
setOnClickListener 方法的參數(shù)是 OnClickListener 接口:
public interface OnClickListener {
void onClick(View v);
}
類似這種符合lambda表達式特征的接口锡移,都可以使用上述兩種寫法來大大簡化代碼量呕童。
最后一個栗子
不得不提一句,lambda表達式有一個變形淆珊,那就是:當lambda表達式作為方法的最后一個參數(shù)時夺饲,可以lambda表達式放到小括號外面。而如果只有一個參數(shù)就是lambda表達式施符,那么括號可以省略
這個非常重要往声,不了解這個,很多地方都會感覺很蛋疼戳吝。
fun testLambda(s: String, block: () -> String) {
println(s)
block()
}
fun testLambda2(block: () -> String) {
block()
}
fun main() {
testLambda("第一個參數(shù)") {
println("block函數(shù)體")
"返回值"
}
testLambda2 {
println("block函數(shù)體")
"返回值"
}
}
總結(jié)
Kotlin中l(wèi)ambda表達式可以當作普通參數(shù)一樣去傳遞浩销,去賦值,去使用听哭。
高階函數(shù)以及操作符
上文提到慢洋,kotlin中l(wèi)ambda表達式已經(jīng)融入了語言核心,而具體的體現(xiàn)就是高階函數(shù)陆盘,把lambda表達式當作參數(shù)去使用. 這種將lambda表達式作為參數(shù)或者返回值的函數(shù)普筹,就叫高階函數(shù)。
官方高階函數(shù)
Kotlin谷歌已經(jīng)給我們封裝了一些高階函數(shù)隘马。
- run
- with
- apply
- also
- let
- takeif 和 takeunless
- repeat
- lazy
run函數(shù)詳解
代碼如下(這里為了展示代碼全貌太防,忽視androidStudio的代碼優(yōu)化提示):
class A {
val a = 1
val b = "b"
}
fun testRun() {
//run方法有兩種用法,一個是不依賴對象酸员,也就是作為全局函數(shù)
run<String> {//我可以規(guī)定返回值的類型
println("我是全局函數(shù)")
"返回值"
}
val a = A()
//另一種則是 依賴對象
a.run<A,String> {//這里同樣可以規(guī)定返回值的類型
println(this.a)
println(this.b)
"返回值"
}
}
fun main() {
testRun()
}
如上所示:
run函數(shù)分為兩類
不依賴對象的全局函數(shù)蜒车。
-
依賴對象的"類似"擴展函數(shù)讳嘱。
兩者都可以規(guī)定返回值類型(精通泛型的話,這里應(yīng)該不難理解醇王,泛型下一節(jié)詳解)呢燥。
閱讀源碼:
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
run函數(shù)被重載,參數(shù)有所不同
- 前者 參數(shù)類型為 ()->R ,返回值為 R 寓娩,函數(shù)體內(nèi)執(zhí)行block(),并且返回執(zhí)行結(jié)果
- 后者 參數(shù)類型為 T.()->R 呼渣,返回值為R 棘伴, T.() 明顯是依賴 T類的(貌似T的擴展函數(shù)),返回值依然是R屁置,執(zhí)行完之后返回結(jié)果焊夸。
- 并且,可以發(fā)現(xiàn) 兩者都是內(nèi)聯(lián)函數(shù) inline (執(zhí)行代碼時不進行方法的壓棧出棧蓝角,而是類似于直接在目標處執(zhí)行代碼段)
所以阱穗,前者不需要依賴對象,后者必須依賴對象(因為它是T類的"擴展函數(shù)")
使用場景
根據(jù)run方法的特性使鹅,無論是不是依賴對象揪阶,它都是封裝了一段代碼,而且還是inline的患朱。所以:
-
如果你不想把一段代碼獨立成方法鲁僚,又不想讓它們看上去這么凌亂,最重要的是告訴后來的開發(fā)者 這段代碼是一個整體裁厅,不要隨便給我在里面插代碼冰沙,或者拆分。那就用run方法执虹,把他們整合到一個作用域中拓挥。
run { println("這是一段代碼邏輯很相近的代碼段") println("但是我不想把他獨立成一個方法") println("又擔心別人會隨便改") println("所以用run方法把他們放在同一個作用域中") println("小組中其他人看到這段,就知道不要把無關(guān)代碼插進來") }
-
更神奇的是袋励,這個run函數(shù)是有返回值的侥啤,參數(shù)返回值可以利用起來:
fun testRun2(param1: String, param2: String, param3: String) { //我想讓這幾個參數(shù)都不為空,如果檢查是空插龄,就不執(zhí)行方法主體 val checkRes: Boolean = run<Boolean> { when { param1.isNullOrEmpty() -> { false } param2.isNullOrEmpty() -> { false } param3.isNullOrEmpty() -> { false } else -> true } } if (checkRes){ println("參數(shù)檢查完畢愿棋,現(xiàn)在可以繼續(xù)接下來的操作了") }else{ println("參數(shù)檢查不通過,不執(zhí)行主體代碼") } } fun main() { testRun2("1", "2", "") }
main運行結(jié)果:
參數(shù)檢查完畢均牢,現(xiàn)在可以繼續(xù)接下來的操作了
小結(jié)論
run方法在整合小段代碼的功效上糠雨,還是很實用的
其他高階函數(shù)
上面列舉出來的這些系統(tǒng)高階函數(shù)原理上都差不多,只是使用場景有區(qū)別徘跪,因此除了run之外甘邀,其他的不再詳解琅攘,而只說明使用場景。
apply
和run只有一個區(qū)別松邪,run是返回block()執(zhí)行之后的返回值坞琴,而,apply 則是返回this逗抑,因此 apply必須依賴對象剧辐。而由于返回了this,因此可以連續(xù)apply調(diào)用邮府。
fun testApply() {
val a = A()
a.apply {
println("如果一個對象要對它進行多個階段的處理")
}.apply {
println("那么多個階段都擠在一起則容易混淆荧关,")
}.apply {
println("此時可以用apply將每一個階段分開擺放")
}.apply {
println("讓程序代碼更加優(yōu)雅")
}
}
fun main() {
testApply()
}
with
下面的代碼應(yīng)該都很眼熟,Glide圖片加載框架的用法褂傀,with(context)然后鏈式調(diào)用
Glide.with(this).load(image).asGif().into(mImageView);
Kotlin中的with貌似把這一寫法發(fā)揚光大了(只是類比忍啤,不用深究),場景如下:
class A {
val a = 1
val b = "b"
fun showA() {
println("$a")
}
fun showB() {
println("$a $b")
}
}
fun testWith() {
val a = A()
with(a) {
println("作用域中可以直接引用創(chuàng)建出的a對象")
this.a
this.b
this.showA()
this
}.showB()
}
fun main() {
testWith()
}
細節(jié)
- with(a){} 大括號內(nèi)的作用域里面仙辟,可以直接使用 當前a對象的引用同波,可以this.xxx 也可以 a.xxx
- with(a){} 大括號作用域內(nèi)的最后一行是 返回值,如果我返回this叠国,那么with結(jié)束之后未檩,我可以繼續(xù) 調(diào)用a的方法
also
also和with一樣,必須依賴對象煎饼,返回值為this讹挎。因此它也支持鏈式調(diào)用,它和apply的區(qū)別是:
apply的block吆玖,沒有參數(shù)筒溃,但是 also 則將this作為了參數(shù)。這么做造成的差異是:
作用域 { } 內(nèi)調(diào)用當前對象的方式不同沾乘。
class A {
val a = 1
val b = "b"
fun showA() {
println("$a")
}
fun showB() {
println("$a $b")
}
}
fun testApply() {
A().apply {
this.showA()
println("=======")
}.showB()
}
fun testAlso() {
A().also {
it.showA()
println("=======")
}.showB()
}
apply 必須用this.xxx
, also則用 it.xxx
.
let
類比到run:
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
只有一個區(qū)別:run的block執(zhí)行不需要參數(shù)怜奖,而let 的block執(zhí)行時需要傳入this。
造成差異為:
A().run {
println("最后一行為返回值")
this
}.showA()
A().let {
println("最后一行為返回值")
it
}.showA()
run{} 作用域中 只能通過this.xxx操作當前對象翅阵,let 則用 it.xxx
takeif 和 takeunless
這兩個作用相反歪玲,并且他們必須依賴對象≈澜常看源碼:
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
return if (predicate(this)) this else null
}
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
return if (!predicate(this)) this else null
}
predicate 是 (T)->Boolean 類型的lambda表達式滥崩,表示斷言判斷,如果判斷為true讹语,則返回自身钙皮,否則返回空
class A {
val a = 0
val b = "b"
fun showA() {
println("$a")
}
fun showB() {
println("$a $b")
}
}
fun testTakeIfAndTakeUnless() {
println("test takeIf")
A().takeIf { it.a > 0 }?.showB()
println("==========")
println("test takeUnless")
A().takeUnless { it.a > 0 }?.showB()
}
fun main() {
testTakeIfAndTakeUnless()
}
執(zhí)行結(jié)果:
test takeIf
==========
test takeUnless
0 b
takeIf / takeUnless
適用于將條件判斷的代碼包在一個作用域{}中,然后 用 ?.xxxx的安全調(diào)用符 來 執(zhí)行對象操作。
repeat
repeat是 多次循環(huán)的傻瓜版寫法短条。
fun testRepeat() {
repeat(10) {
print("$it ")
}
}
fun main() {
testRepeat()
}
執(zhí)行結(jié)果:
0 1 2 3 4 5 6 7 8 9
lazy
lazy的作用是: 延遲初始化val定義的常量导匣。
class B {
val i: Int by lazy {
println("執(zhí)行i初始化")
20
}
init {
println("構(gòu)造函數(shù)執(zhí)行")
}
}
如果只是初始化B對象,卻沒有使用到變量i
, 那么延遲初始化不會執(zhí)行茸时。
fun main() {
B()
}
執(zhí)行結(jié)果:
構(gòu)造函數(shù)執(zhí)行
而如果使用到了變量i
贡定,才會去在調(diào)用之前初始化:
fun main() {
println("i 變量的值是:" + B().i)
}
執(zhí)行結(jié)果:
構(gòu)造函數(shù)執(zhí)行
執(zhí)行i初始化
i 變量的值是:20
總結(jié)
Kotlin官方提供的高階函數(shù),run可都,apply缓待,also,let汹粤,with等命斧,旨在使用{}作用域,將聯(lián)系緊密的代碼封在一個作用域內(nèi)嘱兼,讓一個方法內(nèi)部顯得 有條不紊,閱讀觀感更好贤徒,可維護性更高芹壕,代碼更優(yōu)雅。上述接奈,除了lazy之外踢涌,所有的 函數(shù)都在Standart.kt文件內(nèi)部。
Kotlin泛型
類型擦除
我們在編碼java的時候序宦,寫一個泛型類睁壁,可能是這樣的
class Plate<T>{
T t;
Plate(T t){
this.t = t;
}
T get(){
return t;
}
void set(T t){
this.t =
t;
}
}
以上是java代碼。
Java泛型是偽泛型互捌,在編譯之后潘明,所有的泛型寫法都會被移除,而會用實際的類型去替換秕噪。
mian函數(shù)運行的時候钳降,<T>
被移除。而原來的T腌巾,就變成了Object遂填。
所以,Plate的字節(jié)碼反編譯過來就應(yīng)該是
class Plate{
Object t;
Plate(Object t){
this.t = t;
}
Object get(){
return t;
}
void set(Object t){
this.t = t;
}
}
那么既然運行的時候澈蝙,泛型限制全都沒有了吓坚。那么怎么保證 泛型的作用呢?
答案:編碼的時候灯荧,編譯器幫我們進行校驗礁击。
strList.add(123);//報錯
PECS 法則 和 上下邊界的問題
public class Panzi<T> {
T mT;
public Panzi(T t) { mT = t;}
public T get() { return mT; }
public void set(T t) { mT = t; }
}
class Fruit {}
class Banana extends Fruit {}
class Apple extends Fruit {}
Panzi<Apple> applePanzi = new Panzi<>(new Apple());
Panzi<Fruit> fruitPanzi = new Panzi<>(new Fruit());
fruitPanzi = applePanzi;
雖然 Apple和Fruit是父子繼承關(guān)系。但是Panzi<Apple>
和 Panzi<Fruit>
是半毛錢關(guān)系都沒有,如果你想fruitPanzi = applePanzi
,把后者賦值給前者客税,是會報編譯錯誤的况褪。
如果想讓裝水果的盤子和 裝 蘋果的盤子發(fā)生一點關(guān)系,能夠后者賦值給前者.
Panzi<Apple> applePanzi = new Panzi<>(new Apple());
Panzi<? extends Fruit> fruitPanzi = new Panzi<>(new Fruit());
fruitPanzi = applePanzi;
那就必須使用到上下邊界的關(guān)鍵字 extends
extends 在泛型中表示指定上界更耻,也就是說测垛,實際類型都必須在Fruit之下(包括Fruit自己)。那么既然apple也是Fruit的子類秧均,那么賦值就可以做到食侮。
-
PECS法則
剛才說到了上邊界 extends。而下邊界是 super關(guān)鍵字
Panzi<? extends Fruit> extendsFruit = new Panzi<>(new Apple()); Panzi<? super Fruit> superFruit = new Panzi<>(new Fruit());
super關(guān)鍵字目胡,在泛型中表示定義下界锯七,實際類型必須在Fruit之上,同時也在Object之下(包括Fruit和Object)
所以會出現(xiàn)這么一個情況:
Panzi<? extends Fruit> extendsFruit = new Panzi<>(new Apple()); Panzi<? super Fruit> superFruit = new Panzi<>(new Object());
我們有這么兩個Panzi誉己,前者是 Fruit作為泛型上界眉尸,一個是Fruit作為下界。
現(xiàn)在巨双,我們從Panzi中去調(diào)用get/set方法噪猾。會發(fā)現(xiàn)。
PE:
extendsFruit.set(new Apple()); // 編譯報錯筑累! Fruit fruit2 = extendsFruit.get();// 編譯正常
為何袱蜡?因為 Fruit作為上界,我get出來的類型可以確定一定是Fruit類型的慢宗。但是我set進去的時候坪蚁,JVM無法判定實際類型(因為泛型被擦除,JVM只人為set(Object t) 的參數(shù)是一個Object)镜沽,JVM要求是Object敏晤,但是你卻給了一個Apple,編譯器無法處理淘邻。所以干脆 java的泛型茵典,? extends 定義了上界,只允許get宾舅,不允許set统阿。這就是PECS中的PE,意思就是 Pruducer Extends 筹我,生產(chǎn)者 Extends扶平,只取不存。
相對應(yīng):
CS: 則是 Cunsumer super 消費者只存不取蔬蕊。
Object object = superFruit.get(); //get结澄,get出來雖然不報錯,但是沒有任何意義。因為不能確定類型麻献,只知道是一個Object们妥,無法調(diào)用API superFruit.set(new Fruit());// 但是set進去的時候,可以確定一定是一個Fruit的
這就是java泛型的 PECS法則.
kotlin泛型使用實例
java
泛型里面比較糾結(jié)的難點就是類型擦除和PECS
法則了勉吻。
那么kotlin
泛型监婶,原理上和java
泛型和沒有區(qū)別齿桃。只是寫法上有了區(qū)別惑惶。
open class Fruit
class Apple : Fruit()
class Banana : Fruit()
class Panzi<T>(t: T) {
var t: T = t
fun get(): T {
return t
}
fun set(t: T) {
this.t = t
}
}
fun test1() {
// 試試能不能相互賦值
var fruitPanzi: Panzi<Fruit> = Panzi(Fruit()) //一個水果盤子
var applePanzi: Panzi<Apple> = Panzi(Apple()) //一個蘋果盤子
//試試相互賦值
// fruitPanzi = applePanzi // 編譯報錯
// applePanzi = fruitPanzi // 編譯報錯
//雙方完全是不相干的類,不能相互賦值 ,
}
/**
* 加邊界之后再賦值
*/
fun test2() {
//如果你非要認為蘋果盤子歸屬于水果盤子短纵,那么可以這樣
var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit()) //一個水果盤子
var applePanzi2: Panzi<Apple> = Panzi(Apple()) //一個蘋果盤子
fruitPanzi2 = applePanzi2 //那么這就是out決定泛型上邊界的案例
}
/**
* PECS法則带污,OUT表示 ? extends 決定上界,上界生產(chǎn)者只取不存
*/
fun test3() {
//看一下get set方法
// 決定上界之后的泛型香到,只取不存
var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit())
fruitPanzi2.get()
// fruitPanzi2.set(Apple()) // 這里編譯報錯鱼冀,和java泛型的表現(xiàn)一樣
}
/**
* PECS法則,IN表示 ? super 決定下界悠就,下界消費者雷绢,只存不取
*/
fun test4() {
//試試泛型下界 in
var fruitPanzi: Panzi<in Fruit> = Panzi(Fruit())
fruitPanzi.set(Fruit())//可以set,但是看看get
val get = fruitPanzi.get()//不會報錯理卑,get出來的類型就完全不能確定了瞬捕,只知道是 頂級類Any? 的子類,獲得它也沒有意義
}
集合操作
Kotlin的集合扇单,并沒有重新開創(chuàng)一套規(guī)則,它的底層依然是java的Collection爆土。Kotlin提供了可變集合和不可變集合的接口鹉究。
不可變集合:
List
宇立,Set
,Map
(內(nèi)部元素不可以 增減 或者 修改自赔,在定義的時候就已經(jīng)將容量和內(nèi)部元素定死)不可變集合:
MutableList
,MutableSet
,MutableMap
(聲明的時候可以隨意指定初始值妈嘹,后續(xù)可以隨意增刪和修改內(nèi)部元素)
集合操作分為:對象的創(chuàng)建和 api的調(diào)用
對象的創(chuàng)建
方式有多種,以不可變集合 List 為例绍妨,kotlin的List底層和Java的List一致润脸,底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組。
靜態(tài)指定元素值
fun main() {
val listOf = listOf<String>("str1", "str2", "str3")
listOf.forEach { print("$it ") }
}
執(zhí)行結(jié)果:
str1 str2 str3
通過動態(tài)創(chuàng)建過程來指定元素值
fun main() {
val list = List(3) {
"str$it"
}
list.forEach { print("$it ") }
}
執(zhí)行結(jié)果:
str0 str1 str2
api的調(diào)用
對象已經(jīng)創(chuàng)建他去,我們要利用kotlin提供的方法來完成業(yè)務(wù)代碼毙驯。
一級難度api(all,any灾测,count爆价,find,groupBy)
fun testCollectionFun() {
val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)
//那么是不是所有的元素都大于10
ages.apply { print("all:") }.all { it > 10 }.apply { println(this) } //結(jié)果是false
//是不是存在任意一個元素大于10
ages.apply { print("any:") }.any { it > 10 }.apply { println(this) }
// 符合指定條件的元素個數(shù)
ages.apply { print("count:") }.count { it < 10 }.apply { println(this) }
//找到第一個符合條件的元素
ages.apply { print("find:") }.find { it > 10 }.apply { println(this) }
// 按照條件進行分組
val groupBy = ages.apply { print("groupBy:") }.groupBy { it > 10 }
groupBy[false].apply { println(this) }
groupBy[true].apply { println(this) }
}
fun main() {
testCollectionFun()
}
針對數(shù)組元素的簡單判斷,上述提供了簡明的示例代碼铭段,用List為例骤宣,至于Set和Map類似⌒蛴蓿可以自主去推斷寫法憔披。
執(zhí)行結(jié)果:
all:false
any:true
count:7
find:100
groupBy:[1, 2, 3, 4, 5, 6, 7]
[100, 200]
**二級難度api **(filter,map,flatMap,flatten)
- filter和map
fun testCollectionFun2() {
//二級難度api
val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)
// 只保留大于10的元素,并返回一個新數(shù)組
ages.filter { it > 10 }.apply { println(this) }
//遍歷List的所有元素,根據(jù)條件返回值展运,創(chuàng)建新的元素內(nèi)容并放到新List中返回出來
ages.map { if (it > 10) "大于10" else "小于等于10" }.apply { println(this) }
}
fun main() {
testCollectionFun2()
}
執(zhí)行結(jié)果:
[100, 200]
[小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 小于等于10, 大于10, 大于10]
- flatMap活逆,因為稍復(fù)雜
// 比如一個學(xué)生,作為一個實體
class Student(name: String, math: Int, chinese: Int) {
val name: String = name
val score = Score(math, chinese)
}
//學(xué)生的成績分數(shù)作為一個主體
class Score(math: Int, chinese: Int) {
val math: Int = math
val chinese: Int = chinese
}
fun testFlatMap() {
val students = listOf(
Student("zero", 100, 80),
Student("alvin", 70, 70),
Student("lance", 90, 60)
)
//我只想統(tǒng)計所有的數(shù)學(xué)成績的總分和平均分,怎么辦
students.flatMap { listOf(it.score.math) }.apply {
var total = 0
this.forEach {
total += it
}
println("數(shù)學(xué)成績的總分是:$total 平均分是:${total / this.size}")
}
}
fun main() {
testFlatMap()
}
執(zhí)行結(jié)果:
數(shù)學(xué)成績的總分是:260 平均分是:86
當面對復(fù)雜數(shù)據(jù)結(jié)構(gòu)時拗胜,我們想要提煉出其中的某一層數(shù)據(jù)蔗候,并不關(guān)心其他無關(guān)字段。就適合使用 flatMap 進行扁平化提煉埂软。
- flatten
意味:"平鋪"
fun testFlatten(){
val list = listOf(listOf("Str1","Str3"), listOf("Str4","Str2"))
val listsOfList = list.flatten()
println("平鋪之前:$list \n平鋪之后$listsOfList")
}
fun main() {
testFlatten()
}
執(zhí)行結(jié)果:
平鋪之前:[[Str1, Str3], [Str4, Str2]]
平鋪之后[Str1, Str3, Str4, Str2]
在存在List嵌套的情況下锈遥,flatten可以把復(fù)合式的數(shù)據(jù)結(jié)構(gòu) 變成 扁平式的。它和flatMap不同勘畔,flatMap適合在復(fù)雜數(shù)據(jù)結(jié)構(gòu)中在指定的層級形成一個集合所灸,方便統(tǒng)計和計算。而flatten則更適用于類型相似的集合嵌套扁平化操作炫七。適用場景還是有差別的爬立。
協(xié)程
想了很久,關(guān)于協(xié)程的內(nèi)容万哪,在官網(wǎng)上確實有很多內(nèi)容侠驯,基礎(chǔ)知識概念,基本使用奕巍,以及 流操作吟策,通道,異常處理的止,并發(fā)處理等檩坚,不方便在這里展開。具體的參照:Kotlin中文網(wǎng)
這里具體去學(xué)習(xí)诅福。本章節(jié)匾委,只總結(jié)一下近期查閱資料并經(jīng)本人驗證的知識點。
概念
英文 coroutines : /,k?uru:'ti:n/ 意: 協(xié)同程序权谁。 簡稱協(xié)程剩檀。
Kotlin提出協(xié)程概念,是為了簡化異步編程旺芽,讓開發(fā)者更容易控制函數(shù)的執(zhí)行流程沪猴。
協(xié)程和線程的聯(lián)系和區(qū)別
在操作系統(tǒng)OS中辐啄,進程是資源分配的最小單位,線程是任務(wù)調(diào)度的最小單位运嗜。而協(xié)程則是處在線程內(nèi)部的“微線程”壶辜,或者說輕量級線程。 由于線程在OS中是稀缺資源担租,所有OS平臺的線程數(shù)量都是有上限的砸民,平時編程我們會用線程池來管理線程數(shù)量,熟悉線程池的同學(xué)應(yīng)該知道奋救,線程池管理線程岭参,無論是核心線程還是非核心線程,都不會隨意去創(chuàng)建尝艘,除非迫不得已演侯。
用線程解決異步問題
- 多線程同步編程可以通過加鎖解決數(shù)據(jù)的線程安全問題,但是加鎖會降低程序執(zhí)行效率背亥,并且鎖多了秒际,會有死鎖隱患
- 線程的狀態(tài)轉(zhuǎn)換完全由內(nèi)核控制,程序員開發(fā)者無法干涉
- 線程的是稀缺資源狡汉,不能隨意創(chuàng)建娄徊,使用線程解決異步問題,線程的初始化盾戴,上下文切換(CPU輪轉(zhuǎn))寄锐,線程狀態(tài)切換(sleep,yield...), 變量加鎖操作(synchronized關(guān)鍵字)尖啡,都會使得線程的使用代價比較大
用協(xié)程解決異步問題
- 協(xié)程是運行在線程之上的優(yōu)化產(chǎn)物锐峭,或稱“微線程”。協(xié)程依賴線程運行可婶,復(fù)雜的底層邏輯被封裝在庫內(nèi),使用時無需關(guān)心所處線程狀態(tài)
- 使用協(xié)程援雇,開發(fā)者可以自己控制協(xié)程的狀態(tài)(suspend掛起矛渴,resume恢復(fù)),而不會像線程那樣依賴底層調(diào)度惫搏,時間片爭奪具温。
- 一個線程可以跑多個協(xié)程,一個協(xié)程也可以分段在多個線程上執(zhí)行
- 協(xié)程 是 非阻塞的筐赔,當前協(xié)程掛起之后铣猩,所在線程資源并不會浪費,它會去執(zhí)行其他協(xié)程(如果有的話)
- 協(xié)程 相對于線程這種OS中的稀缺資源茴丰,它是極其輕量級的达皿,就算你開一百萬個協(xié)程天吓,對于系統(tǒng)的壓力也不會像大量線程那樣大(別說一百萬個,linux系統(tǒng)的線程數(shù)量上線是1000峦椰,超過這個值系統(tǒng)就無法正常運行).
- 總之一句話 : 協(xié)程的出現(xiàn)龄寞,讓程序開發(fā)者對程序邏輯的掌控提升到了一個新的境界,想象一下汤功,一個函數(shù)正在執(zhí)行物邑,你想讓他在某個時刻暫停,然后在另一個時刻繼續(xù)滔金。利用線程恐怕很難做到色解。協(xié)程中,輕而易舉餐茵。
基本使用
module級別的build.gradle 中 引入庫依賴,推薦使用最新版(目前穩(wěn)定版是1.3.3)
dependencies {
//...
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" // 如果你需要用到協(xié)程調(diào)度器Dispatchers的話,必須加上這個依賴
}
協(xié)程的創(chuàng)建
創(chuàng)建協(xié)程有多種方式科阎,全局/獨立,同步/異步钟病,并且可以指定 "協(xié)程調(diào)度器"
-
runBlocking
fun main() { runBlocking { println("這是runBlocking協(xié)程...") } }
-
launch
fun main() { runBlocking { println("這是runBlocking協(xié)程...") launch { println("這是runBlocking內(nèi)部的runBlocking協(xié)程...") delay(2000) println("延遲了2000MS之后萧恕,再打印") } } }
-
GlobalScope.launch
fun main() { println("協(xié)程,相對于主線程來說肠阱,都是異步的票唆,也就是說,你在這里插入?yún)f(xié)程邏輯屹徘,主線程的邏輯并不會被阻塞") GlobalScope.launch { delay(3000) println("GlobalScope.launch 創(chuàng)建協(xié)程 ") } println("主線程繼續(xù)") Thread.sleep(5000) }
-
Global.async
fun main() { runBlocking { val async: Deferred<String> = GlobalScope.async { println("這是一個異步協(xié)程走趋,他將返回一個Deferred") delay(2000) "異步任務(wù)返回值" } println("主線程繼續(xù):" + async.await()) } Thread.sleep(5000) }
“騷操作”
關(guān)心協(xié)程的人一般都會十分關(guān)注它到底能給我們異步編程帶來怎樣的便利。這里總結(jié)幾個不用協(xié)程實現(xiàn)起來很麻煩的騷操作噪伊。
-
如果有一個函數(shù)簿煌,它的返回值需要等到多個耗時的異步任務(wù)都執(zhí)行完畢返回之后,組合所有任務(wù)的返回值作為 最終返回值
fun test6(): String = runBlocking { var finalRes = "" coroutineScope { launch { delay(1000) finalRes = finalRes.plus("1") } launch { delay(2000) finalRes = finalRes.plus("2") } launch { delay(3000) finalRes = finalRes.plus("3") } } finalRes } fun main() { val test6 = test6() println("最終返回值是: $test6") }
最終返回結(jié)果為(延遲3秒之后打印):
最終返回值是: 123
-
如果有一個函數(shù)鉴吹,需要順序執(zhí)行多個網(wǎng)絡(luò)請求姨伟,并且后一個請求依賴前一個請求的執(zhí)行結(jié)果
import kotlinx.coroutines.* suspend fun getToken(): String { for (i in 0..10) { println("異步請求正在執(zhí)行:getToken :$i") delay(100) } return "ask" } suspend fun getResponse(token: String): String { for (i in 0..10) { println("異步請求正在執(zhí)行:getResponse :$token $i") delay(100) } return "response" } fun setText(response: String) { println("setText 執(zhí)行,時間: ${System.currentTimeMillis()}") } fun main() { GlobalScope.launch(Dispatchers.Unconfined) { var token = GlobalScope.async(Dispatchers.Unconfined) { return@async getToken() }.await() // 創(chuàng)建異步任務(wù)豆励,并且 阻塞執(zhí)行 await 是阻塞執(zhí)行取得結(jié)果 var response = GlobalScope.async(Dispatchers.Unconfined) { return@async getResponse(token) }.await() // 創(chuàng)建異步任務(wù)夺荒,并且立即執(zhí)行 setText(response) } Thread.sleep(20000) }
執(zhí)行結(jié)果:
異步請求正在執(zhí)行:getToken :0 異步請求正在執(zhí)行:getToken :1 異步請求正在執(zhí)行:getToken :2 異步請求正在執(zhí)行:getToken :3 異步請求正在執(zhí)行:getToken :4 異步請求正在執(zhí)行:getToken :5 異步請求正在執(zhí)行:getToken :6 異步請求正在執(zhí)行:getToken :7 異步請求正在執(zhí)行:getToken :8 異步請求正在執(zhí)行:getToken :9 異步請求正在執(zhí)行:getToken :10 異步請求正在執(zhí)行:getResponse :ask 0 異步請求正在執(zhí)行:getResponse :ask 1 異步請求正在執(zhí)行:getResponse :ask 2 異步請求正在執(zhí)行:getResponse :ask 3 異步請求正在執(zhí)行:getResponse :ask 4 異步請求正在執(zhí)行:getResponse :ask 5 異步請求正在執(zhí)行:getResponse :ask 6 異步請求正在執(zhí)行:getResponse :ask 7 異步請求正在執(zhí)行:getResponse :ask 8 異步請求正在執(zhí)行:getResponse :ask 9 異步請求正在執(zhí)行:getResponse :ask 10 setText 執(zhí)行,時間: 1578904290520
-
當前正在執(zhí)行一項異步任務(wù)良蒸,但是你突然不想要它執(zhí)行了技扼,隨時可以取消
fun main() { // 協(xié)程任務(wù) val job = GlobalScope.launch(Dispatchers.IO) { for (i in 0..100){// 每次掛起100MS,100次也就是10秒 println("協(xié)程正在執(zhí)行 $i") delay(100) } } // 但是我在1秒之后就取消協(xié)程 Thread.sleep(1000) job?.cancel() println( "btn_right 結(jié)束協(xié)程") }
執(zhí)行結(jié)果(本該執(zhí)行100輪的打印嫩痰,只持續(xù)了10輪):
協(xié)程正在執(zhí)行 0 協(xié)程正在執(zhí)行 1 協(xié)程正在執(zhí)行 2 協(xié)程正在執(zhí)行 3 協(xié)程正在執(zhí)行 4 協(xié)程正在執(zhí)行 5 協(xié)程正在執(zhí)行 6 協(xié)程正在執(zhí)行 7 協(xié)程正在執(zhí)行 8 協(xié)程正在執(zhí)行 9 btn_right 結(jié)束協(xié)程 Process finished with exit code 0
-
如果你想讓一個任務(wù)最多執(zhí)行3秒剿吻,超過3秒則自動取消
import kotlinx.coroutines.* fun main() = runBlocking { println("限時任務(wù)中結(jié)果是:" + getResFromTimeoutTask()) } suspend fun getResFromTimeoutTask(): String? { // 忘了,它會保證內(nèi)部的協(xié)程代碼都執(zhí)行完畢串纺,所以不能這么寫 return withTimeoutOrNull(1300) { for (i in 0..10) { println("I'm sleeping $i ...") delay(500) } "執(zhí)行結(jié)束" } }
執(zhí)行結(jié)果
I'm sleeping 0 ... I'm sleeping 1 ... I'm sleeping 2 ... 限時任務(wù)中結(jié)果是:null Process finished with exit code 0
總結(jié)
協(xié)程作為kotlin 區(qū)別于java的新概念丽旅,它的出現(xiàn)是為了解決java不好解決的問題椰棘,比如層層回調(diào)導(dǎo)致代碼臃腫,比如 異步任務(wù)執(zhí)行流程不好操控等魔招。本章節(jié)篇幅有限晰搀,無法展開說明,但是對于新手而言办斑,看完本章應(yīng)該能對協(xié)程的作用有一個大概的認知外恕。本人也是初步研究,后續(xù)有更深入的了解之后乡翅,再進行專文講解吧鳞疲。
操作符重載
概念
說人話,像是一元操作符 ++自加蠕蚜,二元操作符 +相加 尚洽,默認只支持數(shù)字類型,比如Int. 但是通過操作符的重載靶累,我們可以讓任意類 都能 ++自加腺毫,且返回一個想要的對象。操作符執(zhí)行的邏輯挣柬,完全看我們?nèi)绾稳ピO(shè)計潮酒。
分類
按元素級別
-
一元
表達式 對應(yīng)函數(shù) +a a.unaryPlus() -a a.unaryMinus() !a a.not() a++ a.inc() a-- a.dec() -
二元
表達式 對應(yīng)函數(shù) a+b a.plus(b) a-b a.minus(b) a*b a.times(b) a/b a.div(b) a%b a.rem(b) a..b a.range(b) a in b b.contains(a) a !in b !b.contains(a) a[i] a.get(i) a[i,j] a.get(i,j) a[i_1,...,i_n] a.get(i_1,...,i_n) a[i]=b a.set(i,b) a[i,j]=b a.set(i,j,b) a[i_1,...,i_n]=b a.set(i_1,...,i_j,b) a() a.invoke() a(i) a.invoke(i) a(i,j) a.invoke(i,j) a(i_1,...,i_n) a.invoke(i_1,...,i_n) a+=b a.plusAssign(b) a-=b a.minusAssign(b) a*=b a.timesAssign(b) a/=b a.divAssign(b) a%=b a.modAssign(b) a > b a.compareTo(b)>0 a < b a.compareTo(b)<0 a>=b a.compareTo(b)>=0 a<=b a.compareTo(b)<=0
按實現(xiàn)方式
成員函數(shù)
擴展函數(shù)
栗子
看到上面的一大堆,肯定有點懵邪蛔,看個例子解決疑問急黎。上面我用兩種維度來對操作符重載進行了分類,那么侧到,先試試:成員函數(shù)的方式來重載一個一元操作符
class A(i: Int, j: Int) {
var i: Int = i
var j: Int = j
/**
* 重載++操作
*/
operator fun inc(): A {
return A(i++, j++)
}
override fun toString(): String {
return "[i=$i , j=$j]"
}
}
如上代碼勃教,注意看:
operator fun inc(): A {
return A(i++, j++)
}
Kotlin的操作符重載和 c++,dart語言內(nèi)的操作符重載寫法完全不同匠抗,它不再是直接把操作符放到了 重寫的過程中故源,而是每一種支持重載的操作符都有一個對應(yīng)的 函數(shù)名
正如:上表格中的 a++ 操作符,對應(yīng)的函數(shù)就是 a.inc()
調(diào)用的時候:
fun main() {
var a = A(1, 2)
println("a:$a")
println("a++:${a++}")
}
打印結(jié)果:
a:[i=1 , j=2]
a++:[i=2 , j=3]
再看一個二元運算符重載的栗子汞贸,這次我們不用成員函數(shù)心软,而是用擴展函數(shù):
class A(i: Int, j: Int) {
var i: Int = i
var j: Int = j
override fun toString(): String {
return "[i=$i , j=$j]"
}
}
/**
* 重載A類的 x+y 操作
*/
operator fun A.plus(a: A): A {
return A(this.i + a.i, this.j + a.j)
}
fun main() {
val x = A(1,1)
val y = A(2,2)
println(x+y)
}
這里演示的是 A類的 x+y 操作符重載。細節(jié)應(yīng)該不用多說著蛙。
打印結(jié)果:
[i=3 , j=3]
再來一個較為復(fù)雜的栗子, 重載 a[i]
/**
* 比如,B類中有一個成員耳贬,list踏堡,我想重載操作符,直接取到list中的元素
*/
class B {
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
}
//a[i]
operator fun B.get(i: Int): Int {
return list[i]
}
fun main() {
val b = B()
println("${b[2]}")
}
打印結(jié)果:
3
最后一個栗子:a > b咒劲,對應(yīng)函數(shù)為:a.compare(b)
/**
* 學(xué)生class
*/
data class Student(val math: Int = 0, val chinese: Int = 0, val english: Int = 0)
fun Student.toString():String{
return "[math:${math} chinese:${chinese} english:${english}]"
}
fun Student.totalScore(): Int {
return math + chinese + english
}
/**
* 比如顷蟆,我們要直接比較兩個學(xué)生的總分
*/
operator fun Student.compareTo(s: Student): Int {
return this.totalScore() - s.totalScore()//比較2個學(xué)生的總分
}
fun main() {
val s1 = Student(math = 50, chinese = 90, english = 100)
val s2 = Student(math = 80, chinese = 70, english = 60)
println("s1:${s1}")
println("s2:${s2}")
//比如存在這兩個學(xué)生诫隅,我要知道他們的總分誰高誰低
println("學(xué)生s1,s2的總分:${if(s1 > s2) "s1比較高" else "s2比較高" }")
}
打印結(jié)果:
s1:Student(math=50, chinese=90, english=100)
s2:Student(math=80, chinese=70, english=60)
學(xué)生s1帐偎,s2的總分:s1比較高
總結(jié)
通過以上幾個栗子逐纬,應(yīng)該能看出,kotlin的操作符重載削樊,編碼和使用都十分簡單豁生。重載之前需要確定兩件事
- 根據(jù)業(yè)務(wù)需求,確定要重載的是哪一個操作符漫贞,雖然操作符的最終執(zhí)行邏輯完全由我們自定義甸箱,但是我們重載操作符的目的是為了 讓使用者更簡單的理解業(yè)務(wù)代碼,所以迅脐,要選擇原本意義更加符合業(yè)務(wù)需求的操作符芍殖。Kotlin支持的操作符在上述表格中都列舉出來了,支持大部分一元二元操作符谴蔑,但是二元中不支持
===
的重載豌骏,不支持三元操作符bool?a:b
這種 。 - 確定重載函數(shù)的 入?yún)㈩愋?/strong>隐锭,個數(shù)窃躲,以及返回值類型,并且編寫操作符的執(zhí)行邏輯成榜。
潛規(guī)則
從Java轉(zhuǎn)到kotlin框舔,基本上都會存在java代碼與kotlin共存的問題。而且為了快速轉(zhuǎn)型赎婚,可能會直接把java類轉(zhuǎn)成kotlin類刘绣,而這個過程中,涉及到j(luò)ava和kotlin的交互挣输,往往會磕磕碰碰纬凤,以下總結(jié)了一部分 java kotlin交互方面的問題.
Kotlin文件和類不存在一對一關(guān)系
kotlin的文件,可以和類名一致撩嚼,也可以不一致停士。這種特性,和c++有點像完丽,畢竟c++的.h 和 .cpp文件是分開的恋技。只要最終編譯的時候?qū)Φ纳希募鋵崯o所謂的逻族。Java中蜻底,一個類文件的類名和文件名不一致,如果是public類聘鳞,就會報異常薄辅。
在kotlin中要拂,可以寫成一致,如:
不一致:
這樣做的意義在于:
如果有很多個行數(shù)很短的類:在java中可能要占用大量的文件個數(shù)(Java中可以用內(nèi)部類的形式解決)站楚,kotlin中則可以把這些類都放到同一個kt文件中脱惰,不用內(nèi)部類也能解決。
共生體
Java中的靜態(tài) static關(guān)鍵字窿春,在kotlin中不復(fù)存在拉一,作為替換,Kotlin提出了共生體的概念谁尸。如果是kt文件去調(diào)用kt類的“靜態(tài)”方法(不依賴對象)舅踪,則要求后者的類結(jié)構(gòu)中增加一個 companion object 成員變量。并且可以在 成員中寫上 你想要定義的"靜態(tài)"成員變量和成員方法
class Test001(_name: String) : Person(_name) {
companion object {
const val s: String = ""
const val s2: String = ""
fun t1(){
}
}
}
fun main(){
Test001.s
Test001.t1()
}
注:每一個kotlin類中良蛮,只能有一個共生體對象.
但是在java調(diào)用kt的"靜態(tài)"成員方法時抽碌,必須帶上共生體,但是决瞳,訪問"靜態(tài)"成員變量货徙,則不能帶:
public static void main(String[] args) {
Test001.Companion.t1();//Java訪問kt的t1()共生體方法,必須帶上Companion
String s2 = Test001.s;// 而訪問共生體成員變量皮胡,不能帶Companion
}
好糾結(jié)痴颊。為什么要這么設(shè)計。算了屡贺。查了一下kt反編譯之后的Java源碼:
共生體變成了Java類中的靜態(tài)內(nèi)部類蠢棱,包含t1()方法。而s甩栈,s2 則是普通的靜態(tài)變量泻仙。
修飾符
修飾符指的是 類 和 成員變量,成員方法 前面的 權(quán)限訪問關(guān)鍵字量没。原 Java擁有 private ,protected玉转,default ,public ,訪問權(quán)限分別為: 本類內(nèi)部,同包名或子類殴蹄,同包名究抓,全局。
然而袭灯,kotlin新增了一個概念刺下,internal ,表示稽荧,相同Module內(nèi)可訪問橘茉,跨Module則不行。
并且,java和kotlin的 private ,protected捺癞,default ,public 的訪問權(quán)限還有區(qū)別,但是我這里就不詳述了构挤,因為我覺得意義不大髓介。能不能訪問,寫代碼的時候編譯器會告訴你筋现,當場警告你唐础,你就會修改代碼。如果有問題矾飞∫慌颍可以把kotlin Decompile成Java代碼自己去對比試試。如有需要洒沦,后期再說吧豹绪。
空指針問題
通常要快速的將 舊java代碼轉(zhuǎn)化成kotlin代碼,是拷貝java代碼粘貼到kotlin文件內(nèi)申眼,讓as自動轉(zhuǎn)化瞒津,但是這種方式,容易造成很多空指針問題括尸,有一些是很直白的報了編譯錯誤巷蚪,而有一些則是隱藏起來,等到程序運行時才會報錯濒翻。直接報錯的就不提了屁柏,下面演示隱藏的空指針問題:
Kotlin類:
class Student(name:String) {
var name: String = name
fun showName(tag: String) {
println("$tag : $name")
}
}
Java調(diào)用kt:
public class Main {
public static void main(String[] args) {
Student s = new Student("zhou");
s.showName(null);
}
}
此時,如果運行main函數(shù)有送,就會報出:
告訴我們參數(shù)tag不可為null淌喻。但是奇怪的是,在java代碼中娶眷,居然不會報編譯錯誤似嗤。賊特么詭異。
解決方案:
在方法參數(shù)后面加上問號届宠,變成這樣:
沒有基本數(shù)據(jù)類型
Kotlin之中沒有基本數(shù)據(jù)類型烁落,它只有:
Int,Short豌注,Long伤塌,F(xiàn)loat,Double轧铁,Byte 每聪,Char,Boolean
這樣的包裝類型。
為什么沒有药薯?沒有必要去糾結(jié)绑洛,但是只提供包裝類型有一個好處,那就是 方便擴展函數(shù)的定義童本。
我們可以很輕松地對 Int真屯,類型去擴展函數(shù)。
比如: Kotlin
自帶了很多擴展函數(shù):
這是系統(tǒng)定的穷娱,我們也可以自己來定義:
fun Int.plus100(): Int {//自定義擴展
return this + 100
}
fun main() {
val a: Int = 20
println("${a.plus100()}")
}
繼承
在用kt重構(gòu)部分模塊的過程中绑蔫,我發(fā)現(xiàn)頻繁出現(xiàn)下面的問題:
Kotlin基類:
abstract class Person(name: String) {
var name: String? = name
}
Java子類:
由于我是從基礎(chǔ)類開始重構(gòu),所以泵额,在原先的Java代碼中頻繁出現(xiàn)了類似這種 訪問權(quán)限不足的問題配深。一個一個去改成setName函數(shù),工作量巨大嫁盲。后來找到一個辦法:
在kotin中加入 @JvmField 變成這樣:
abstract class Person(name: String) {
@JvmField
var name: String? = name
}
@JvmField可以讓 kotlin的成員屬性變成公有的篓叶,kt轉(zhuǎn)化成java時,會是如下這樣:
public abstract class Person {
@JvmField
@Nullable
public String name;
public Person(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
兼容原先的Java代碼亡资。不用大面積修改了澜共。
默認支持可選命名參數(shù)
了解高級語言語法的同學(xué)肯定知道 可選命名參數(shù)和 可選位置參數(shù),經(jīng)測試: Kotlin的任何方法(包括構(gòu)造方法和普通和方法)锥腻,可以這么寫:
fun test001(s: String, s1: String) {
println("$s - $s1")
}
fun main() {
test001(s = "1111", s1 = "2222") //臥槽嗦董,Kotlin默認支持 可選命名參數(shù)
}
這種特性可以很好的避免了Java中出現(xiàn)的一個方法包含N個參數(shù) 把人眼睛看花的情況:
private void test(String s1, String s2, String s3, String s5, String s6, String s7, String s8, String s9, String s10, String s11, String s12) {
//...
}
比如如上面所示,一個方法瘦黑,有12個String參數(shù)京革,看多了會罵娘,誰特么寫的幸斥。然而匹摇,用kotlin:
fun test(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String, s7: String, s8: String, s9: String, s10: String, s11: String, s12: String) {}
fun main() {
test(s1 = "",s2 = "",s3 = "",s4 = "",s5 = "",s6 = "",s7 = "",s8 = "",s9 = "",s10 = "",s11 = "",s12 = "")
}
直覺上這種語法,融入了 建造者設(shè)計模式甲葬。讓同一個函數(shù)的多個參數(shù)不再混亂廊勃。當然如果你懷舊的話,你也可以用原始方法经窖,12個string依次擺下去坡垫。反正我不會這么做。
類画侣,成員方法 默認封閉
和Java相反冰悠,kotlin給類,成員方法 都采用了默認封閉原則配乱。具體體現(xiàn)為:類溉卓,默認不可繼承皮迟,成員方法默認不可重寫(繼承時)。如果要繼承類桑寨,或者重寫父類方法伏尼,必須在父類中手動加入 open
關(guān)鍵字,子類中重寫方法必須加上override
關(guān)鍵字 :
kotlin父類:
open class Student(name:String) {
var name: String = name
open fun showName(tag: String?) {
println("$tag : $name")
}
}
kotlin子類:
class StudentExt(name: String) : Student(name) {
override fun showName(tag: String?) {
super.showName(tag)
println("xxxxx")
}
}
Kotlin中方法和函數(shù)的區(qū)別
函數(shù)尉尾,是c/c++的詞匯烦粒,而方法,則是Java里面〈蓿現(xiàn)在kotlin中同時存在了方法和函數(shù),那么區(qū)別在哪里兽掰?
通常我們?nèi)藶椋涸贙otlin類內(nèi)部芭碍,稱為成員方法。而在類外部定義的孽尽,則成為全局函數(shù)(這里就不用去討論kotlin變成java之后長什么樣)窖壕。
應(yīng)用到具體場景,一句話解釋清楚:
A.kt 中有一個A類杉女,它有a()成員方法瞻讽。 同時我們可以在 B.kt中給A類擴展一個函數(shù)。創(chuàng)建一個A類對象之后熏挎,我們既可以調(diào)用a()成員方法速勇,又可以調(diào)用它的擴展函數(shù)。
A.kt
class A {
fun a() {}
}
B.kt
fun A.foo(){}// 擴展A的一個函數(shù)
fun main() {
val a = A()//創(chuàng)建對象
a.a() //調(diào)用成員方法
a.foo() //調(diào)用擴展函數(shù)
}
結(jié)語
Java轉(zhuǎn)kotlin坎拐,給我的感覺就是:
- kotlin對于一個function內(nèi)部的管理更加有條理烦磁,它引入了 scope 作用域的概念,利用基于lambda表達式的高階函數(shù)哼勇,把function內(nèi)部的代碼塊管理起來都伪,讓代碼可讀性更高
- kotlin的代碼行數(shù)會大大減少,因為kotlin設(shè)計了很多頂層函數(shù)积担,高階函數(shù)陨晶,使用大量的鏈式調(diào)用,把可以占用行數(shù)的代碼都濃縮在一行帝璧。這樣做的結(jié)果是先誉,一行代碼的信息量大大增加,對于新手是噩夢聋溜,但是對于kotlin熟手谆膳,會感覺很美妙。
- 關(guān)于協(xié)程撮躁,本文只做了最簡單的管中窺豹描述漱病,未曾詳細說到的東西還有很多买雾。但是可以肯定一點,協(xié)程的出現(xiàn)杨帽,顛覆了 android開發(fā)的異步編程思維漓穿,原本很多不敢想的,原本很多java實現(xiàn)起來要繞很多路的注盈,在kotlin上都可以很優(yōu)雅地實現(xiàn)晃危。
參考資料
Kotlin in Action 下載pdf書籍(https://www.7down.com/soft/209822.html)
菜鳥教程 https://www.runoob.com/kotlin/kotlin-tutorial.html
kotlin中文站 https://www.kotlincn.net