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