本文是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!"//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}
字符串判等
==判断内容是否相等
===判断引用是否相等