本文是Kotlin核心編程(2021年6月第一版第5次印刷)的讀書筆記。
感覺適合有一定了解java的Kotlin初學(xué)者,內(nèi)容講了Kotlin通用的使用場景、方法,原理性內(nèi)容不是很難。
這里會根據(jù)書中順序,把個人感覺比較重要的內(nèi)容做下記錄。
第一章、認(rèn)識Kotlin
1.3Kotlin---改良的Java
1在很大程度上實(shí)現(xiàn)了類型推導(dǎo),而java在se10才支持了局部變量的推導(dǎo);
2放棄了static關(guān)鍵字,但又引入了object,可以直接用它來聲明一個單例。
3引入了java中沒有的特殊類,比如Data Class(數(shù)據(jù)類)、Sealed Classes(密封類)。
4新增了java中沒有的語法糖(Smart Casts)。
//java寫法:
if(parentView instanceOf ViewGroup){
((ViewGroup)parentView).addView(childView)
}
//kotlin寫法:
if(parentView is ViewGroup){
parentView.addView(childView)
}
兼容了java,Kotlin可以與java 6一起工作,這也是在Android 上流行的原因之一。
第二章、基礎(chǔ)語法
2.1不一樣的類型聲明:
類型名放變量名后面
//String a = "I am Kotlin";
val a :String = "I am Kotlin"
增強(qiáng)的類型推導(dǎo):編譯器可以在不顯示聲明類型的情況下,自動推導(dǎo)出他所需要的類型。
val string = "I am Kotlin" //java.lang.String
val int = 11234 //int
val long = 1234L //long
...
聲明函數(shù)返回值類型,類型信息放在函數(shù)名后面
fun sum(x:Int,y:Int):Int{return x+y}
如果沒有聲明返回值類型,函數(shù)默認(rèn)被當(dāng)做返回Unit類型,然而實(shí)際上返回的是Int,所以編譯器會報錯。這種情況下必須顯示聲明返回值類型。
fun sum(x:Int,y:Int){return x+y}//!
Type mismatch: inferred type is Int but Unit was expected
*可以暫時把Unit當(dāng)做java中的void。Unit是一個類型,void只是一個關(guān)鍵字。
kotlin進(jìn)一步增強(qiáng)了函數(shù)的語法,可以把{}去掉,用等號定義一個函數(shù)。
fun sum(x:Int,y:Int)=x+y
這種用單行表達(dá)式與等號的語法來定義函數(shù),叫做表達(dá)式函數(shù)體,作為區(qū)分,普通的函數(shù)聲明則可以叫做代碼塊函數(shù)體。使用表達(dá)式函數(shù)體我們可以不聲明返回值類型。但是kotlin并不能針對遞歸情況進(jìn)行全局類型推導(dǎo)。
fun foo(n:Int) = if(n == 0) 1 else n*foo(n-1)//!
Type checking has run into a recursive problem.
Easiest workaround: specify types of your declarations explicitly
2.2val和var的使用規(guī)則
var代表了變量,val聲明的變量具有java中final關(guān)鍵字的效果,也就是引用不可變。
val x = intArrayOf(1,2,3)
x = intArrayOf(2,3,4)//!
Val cannot be reassigned
//因為引用不可變,
//所以x不能指向另一個數(shù)組,
//但我們可以修改x指向數(shù)組的值
x[0] = 2
println(x[0])
2
更加直觀的例子
class Book (var name:String){//var生命的參數(shù)name引用可以被改變
fun printlnName(){
println(this.name)
}
}
fun main() {
val book = Book("Think in java")//book對象的引用不可變
book.name = "kotlin"
book.printlnName() //"kotlin"
}
2.3高階函數(shù)和Lambda
我們可以不經(jīng)像類一樣在頂層直接定義一個函數(shù),也可以在一個函數(shù)內(nèi)部定義一個局部函數(shù),還可以將函數(shù)像普通變量一樣傳遞給另一個函數(shù),或在其他函數(shù)內(nèi)部被返回。
高階函數(shù):接收一個或多個過程作為參數(shù);或者把一個過程作為返回結(jié)果。
接下來用一個例子來說明:有一個國家數(shù)據(jù)庫,設(shè)計了一個CountryApp對國家數(shù)據(jù)進(jìn)行操作,現(xiàn)在要獲取所有的歐洲國家
data class Country(
val name:String,
val continient:String,
val population:Int)
class CountryApp{
fun filterCountry(countries:List<Country>):List<Country>{
val res = mutableListOf<Country>()
for(c in countries){
if(c.continient == "EU"){
res.add(c)
}
}
return res
}
}
后來要找其他洲,改進(jìn)了上述方法,加了一個參數(shù)
fun filterCountry(countries:List<Country>,continient:String):List<Country>{
val res = mutableListOf<Country>()
for(c in countries){
if(c.continient == continient){
res.add(c)
}
}
return res
}
現(xiàn)在要增加人口的條件,又增加了一個參數(shù),如下:
fun filterCountry(countries:List<Country>,continient:String,population:Int):List<Country>{
val res = mutableListOf<Country>()
for(c in countries){
if(c.continient == continient&& c.population>population){
res.add(c)
}
}
return res
}
如果更多的篩選條件會作為方法參數(shù)不斷累加,業(yè)務(wù)邏輯也高度耦合。需要把所有的篩選邏輯行為都抽象成一個參數(shù)---把篩選邏輯作為一個方法傳入。
class CountryTest{
fun isBigCountry(country :Country):Boolean{
return country.continient == "EU" && country.population>10000
}
}
Kotlin中函數(shù)類型格式非常簡單,有以下幾個特點(diǎn):
通過->來組織參數(shù)類型和返回值類型;
用一個括號包裹參數(shù)類型;
返回值即使是Unit也必須顯示聲明。
*如果是無參函數(shù)類型,參數(shù)部分用()代替;多個參數(shù)用逗號分隔
(Int)->Unit
()->Unit
(Int,String)->Unit
(errCode:Int,errMsg:String)->Unit
(errCode:Int,errMsg:String?)->Unit
((errCode:Int,errMsg:String?)->Unit)?
(Int)->((Int)->Unit)
最終上面的方法修改為:
fun filterCountry(
countries:List<Country>,
test:(Country)->Boolean
):List<Country>{
val res = mutableListOf<Country>()
for(c in countries){
if(test(c)){
res.add(c)
}
}
return res
}
然后我們需要把isBigCountry方法傳遞給filterCountry,需要一個方法引用表達(dá)式通過::實(shí)現(xiàn)對于某個類的方法進(jìn)行引用。
fun main() {
val countryApp = CountryApp()
val countryTest = CountryTest()
val countries = ......
countryApp.filterCountry(countries,countryTest::isBigCountry)
}
上面仍不算一個好的方案,每次都要在類中專門寫一個新增的篩選方法。用匿名函數(shù)進(jìn)行進(jìn)一步優(yōu)化。
countryApp.filterCountry(
countries,
fun(country:Country):Boolean{
return country.continient == "EU"
&& country.population >10000
}
)
還有另一種Lambda表達(dá)式讓代碼更簡潔,可以理解為簡化表達(dá)式后的匿名函數(shù)
countryApp.filterCountry(
countries,
{
country->
country.continient == "EU" && country.population >10000
}
)
Lambda語法:
一個lambda表達(dá)式必須通過{}來包裹;
如果lambda聲明了參數(shù)部分類型,且返回值類型支持類型推導(dǎo),那么lambda變量就可以省略函數(shù)類型聲明;
如果lambda變量聲明了函數(shù)類型,那么lambda的參數(shù)部分類型就可以省略。
此外,如果lambda表達(dá)式返回的不是Unit,則默認(rèn)最后一行表達(dá)式的值類型就是返回值類型。
lambda里的it是kotlin簡化后的一種語法糖,叫做單個參數(shù)的隱式名稱。代表這個lambda接收的單個參數(shù)。
擴(kuò)展函數(shù)--kotlin允許我們在不修改已有類的前提下,給他新增方法:
fun View.invisible(){
this.visibility = View.INVISIBLE
}
類型View被稱為接收者類型,this對應(yīng)的是這個類型所創(chuàng)建的接收者對象,可以被省略。
2.4面向表達(dá)式編程
*kotlin里的try catch finally(經(jīng)過測試和java一致)
在下述4種特殊情況時,finally塊都不會被執(zhí)行:
1)在finally語句塊中發(fā)生了異常。
2)在前面的代碼中用了System.exit()退出程序。
3)程序所在的線程死亡。
4)關(guān)閉CPU。
fun main() {
println(test())
}
fun test ():Int{
try{
println("try ----")
var i = 1/0
return 0;
}catch(e:Exception){
println("catch ----")
return 1;
}finally{
println("finally ----")
return 2;
}
}
//輸出:
try ----
catch ----
finally ----
2
fun main() {
println(test())
}
fun test ():Int{
try{
println("try ----")
var i = 1/0
return 0;
}catch(e:Exception){
println("catch ----")
return 1;
}finally{
println("finally ----")
//return 2; 沒有return后
}
}
//輸出:
try ----
catch ----
finally ----
1
假如我們不在 finally中 return,結(jié)果會怎樣
fun main() {
println(test())
}
fun test ():Int{
var i = 999;
try{
println("try ----")
var i = 1/0
return 0;
}catch(e:Exception){
println("catch ----")
i = 100;
return i;
}finally{
println("finally ----")
i = 200;
return i;
}
}
//輸出:
try ----
catch ----
finally ----
200
fun main() {
println(test())
}
fun test ():Int{
var i = 999;
try{
println("try ----")
var i = 1/0
return 0;
}catch(e:Exception){
println("catch ----")
i = 100;
return i;
}finally{
println("finally ----")
i = 200;
//return i;沒有return后
}
}
//輸出:
try ----
catch ----
finally ----
100
在 return的時候會把返回值壓入棧,并把返回值賦值給棧中的局部變量, 最后把棧頂?shù)淖兞恐底鳛楹瘮?shù)返回值。所以在 finally中的返回值就會覆蓋 try/catch中的返回值,如果 finally中不執(zhí)行 return語句,在 finally中修改返回變量的值,不會影響返回結(jié)果。
Kotlin中的“?:”被叫做Elvis運(yùn)算符,表示一種類型的可空性
Kotlin中的when表達(dá)式:
由when開始,{}包含多個分支,每個分支用->連接,不需要switch 和 break,由上到下,依次匹配否則執(zhí)行else;
最終整個when表達(dá)式的返回類型就是所有分支相同的返回類型,或者公共類型。
fun foo(a:Int) = when(a){
1->1
2->2
else ->0
}
或者:
fun foo(a:Int) = when{
a==1->1
a==2->2
else ->0
}
kotlin中的for循環(huán)
for (i in 1..10)println(i)
或者
for (i:Int in 1..10)println(i)
范圍表達(dá)式range通過rangeTo函數(shù)實(shí)現(xiàn)的,通過..操作符與某種類型對象組成。除了整形的基本類型之外,該類型需要實(shí)現(xiàn)java.lang.Comparable接口
字符串的大小根據(jù)首字母在字母表中的排序比較,相同則從左到右一次比較
step函數(shù)來定義迭代步長
for (i in 1..10 step 2)println(i)
downTo實(shí)現(xiàn)倒序
for (i in 1..10 downTo 1 step 2)println(i)//通過downTo 而不是10..1
//108642
util實(shí)現(xiàn)半開區(qū)間
for (i in 1 util10)println(i)
//123456789
用in來檢查成員關(guān)系
"a" in listOf("a","b","c")
通過withIndex提供一個鍵值元組
for((index,value) in array.withIndex()){
println("the element at $index is $value")
}
in、step、downTo、until是通過中綴表達(dá)式實(shí)現(xiàn)的
2.5字符串的定義和操作
val str = "hello world!"
str.length//12
str.substring(0,5)//hello
str+"hello kotlin!"http://hello world!hello kotlin!
str[0]//h
str.first()//h
"".isEmpty()//t
"".isBlank()//t
用三個引號定義的字符創(chuàng),最終的打印格式和在代碼里的格式一致,而且不會解釋轉(zhuǎn)化轉(zhuǎn)義字符
val html = """<html>
<body>
<p>hello</p>
</body>
</html>
"""
字符串模板${}提升緊湊型和可讀性
Hi ${name},welcome to ${lang}
字符串判等
==判斷內(nèi)容是否相等
===判斷引用是否相等