Kotlin快速进阶知识索引
Kotlin语法基础篇(五)
[TOC]
一、基本语法
1. 表达式的使用
//1、函数声明,可用表达式,自动推断返回类型之类的
fun sum(a:Int,b:Int):Int{
return a + b
}
//改为表达式,并自动推断返回类型
fun sum(a:Int,b:Int) = a+b
//2、结合条件判断的表达式
fun max(a:Int,b:Int):Int{
if(a>b){
return a
}else{
return b
}
}
//可简写为
fun max(a:Int,b:Int):Int{
return if(a>b) a else b
}
//进一步,表达式形式
fun max(a: Int, b: Int) = if (a > b) a else b
2. 可空值?
类型检测null
//可空类型声明,标准类型加上?,如Int对应可空的Int?
fun parseInt(str:String):Int?{
//因为str不一定就是数字的,所以返回可以为null,在调用处就需要处理
//return ...
}
//调用方判断非空
val x = parseInt("123")//返回类型为Int?
if(x!=null){
val c = x*2//如此才能正常使用
}
//空安全判断
var a:String?=null
a?.subString(5)?.length//因为a可空类型,所以使用?去操作,避免空指针,前面有一个null,后面都不会执行,而且不会抛出异常
a!!.subString(5).length//这里强判断!!非空,那么后面就认为不空,一旦a为null,就抛出异常了
//使用Elivs操作符,若a不空,则sa = a,空则赋值默认值
val sa = a?:return "default"//return 可以省略
val sssa = a?: throw Exception("null")
3. 类型检测is
、!is
//is 类型判读,会在is之后,就能使用它认定的类型,进行操作
val str:Any
if(str is String){
//在这里就认定为String类型了
println(str.length)
}
//或者
if(str !is String) return
println(str.length)//上面判断return后,这里也就认定为String了
//或者在同一个条件语句中,前面判断后,后面都可以认定类型
if(str is String &&str.length>0){
//......
}
4. for循环,使用in
、 或者..
、Range
//for循环
val array = (0..9).toList()//IntRange转化为list
for(i in array){
println(i)
}
for(i in "addgadsg"){
println(i)
}
for(i in 0..5){
//这里取值区间[0,5]
}
for(i in 0 until 5 step 1){
//这里取值区间[0,5)
}
//使用indices
for(i in array.indices){
println(array[i])
}
for(i in 9 downTo 0 step 2){
//递减,设置幅度的循环
}
5. When表达式
//类似于其他语言的switch case,但是更强大,结合表达式
fun picker(obj:Any) =
when(obj){
1,2,3->"Number"
"str"->"string"
is Long -> "oneLong"
!in 0..5 -> "no,no,no"
else -> "unknown"
}
//甚至必要时,可以不写表达式
when{
1 in 0..5 -> println()
2 !is String -> println()
}
6. 高阶函数及lambda
val fruits = lastOf("apple","banana","avocado","pear","peach")
//高级函数,lambda
fruits.filter{it.startWitch('a')}
.sortedBy{it}
.map{it.toUpperCase()}
.forEach{ println(it)}
7. isInitialized
lateinit var a:String
//在使用的时候,a未必就一定初始化了,可以两种方式处理
//1,将a声明为String?并=null赋值,那么用的时候,使用a?.去操作,
//2,使用`isInitialized`判断是否初始化了
if(a.isInitialized) //do something
8. typealias
类型别名
class PersonWithLongNameAndErrorSpell{}
//为了方便代码,可以声明类的别名
typealias Person = PersonWithLongNameAndErrorSpell
类的别名并不会创建新的类,只是一个外衣指针
9. infix
声明中缀函数
//中缀函数,作用于同类型的数据对象,而且函数声明在类之中
class DemoInt(value:Int){
var mInt = value
//中缀函数声明
infix fun sumInfix(sumTo:DemoInt){
return this.mInt+sumTo.mInt
}
}
//普通的sum函数是
fun sum(a:Int,b:Int) = a+b
//DemoInt使用中缀函数的求和
val a = DemoInt(2)
val b = DemoInt(3)
a sumInfix b//就会得到和一个求和,
10. operator
操作符函数重载
//类似中缀函数,在对应类中声明函数,
class DemoInt(value:Int){
var mInt = value
//操作符重载,这里是重载了 + 号
operator fun plus(demo:DemoInt){
println("$mInt , ${demo.mInt}")
}
}
//如此对于DemoInt对象就可以用+操作
val a = DemoInt(2)
val b = DemoInt(3)
a+b//这里就是调用了内部重载的plus函数
11. 函数式编程
- 1、函数声明为变量来使用
fun sum(a:Int,b:Int) = a+b
//定义一个不可变量的函数对象,f类型为一个函数表达式,也就是结果Int类型,然后=一个定义好的函数 sum,使用::前缀引用
val f:(Int,Int)->Int = ::sum//也可以使用var,但是val更为合理
//如此就可以在其他地方使用sum函数,用f表示
val result = f(2,3)
//可以结合lambda
val f2:(Int,Int)->Int={a,b->
a+b
}
//进一步
val f3 = {a:Int,b:Int->
a+b
}
- 2、类可以继承函数表达式
//类继承函数,就必须实现一个invoke函数,这时候被继承的表达式,类似于接口作用
class Div:(Int,Double)->Double{
override fun invoke(p1: Int, p2: Double): Double {
return (p1 / p2)
}
}
- 函数表达式作为其他函数的
形参
//定义一个函数,表达式(Int,Int)->Int
fun sum(a:Int,b:Int) = a+b
//使用函数形参,也就是理解为,传递来一种算法,
fun callSum(function:(Int,Int)->Int){
val result = function(2,3)//这里只知道使用function计算2,3,并不知道怎么计算,根据传参来决定算法
}
callSum(sum)//传递进一个求和算法,那么就会计算2+3,以此类推
//传参可以是匿名函数,//匿名的(Int,Int)->Int函数,
callSum({a,b->
a+b+20
}
)
这里要注意的,如果函数只有一个参数,可以不用a ->
在lambda
中,会有一个默认形参it
,而且调用处可不用()
val square:(Int)->Int = {it*it}
//匿名函数,求面积,一个参数
fun round(function:(Int)->Int){
println(function(3.14))//这里就是,接收3.14作为参数的fun函数计算
}
//匿名函数的调用方式,传递进去一种算法
round(fun(x:Int) = x*x)
//进一步,lambda 调用,这里传参的为匿名函数(Int)->Int,等效于上面的square
round{it*it}
二、中级语法
1. 接收者receiver
//有点类似于扩展函数,可以声明在对象类之外,其他任何需要的地方都行,然后就能用target的对象调用,像它自身的函数一样
class Person{
val name="固定的名字"
}
//声明receiver,这时候的lambda中,没有it参数,而是this,指代当前Person类,在调用时候,就会指引对象
val rec:Person.()->String = {"名字是 $name"}
//然后调用处,有Person的对象就行
val p = Person()
p.rec()
- 高级用法,在函数内声明
receiver
class Chicken(color:String){
val eggs = mutableListOf<Chicken>()
//function为定义的receiver函数类型,声明?可空,并=null为默认值
fun produce(color:String,function:(Chicken.()->Unit)?=null){
val c = Chicken("red")
eggs.add(c)
if(function!=null){
//调用传来的函数算法
c.function()
}
}
}
//调用处
val cc = Chicken("yellow")
val rec:Chicken.()->Unit = { "这里没有it参数,只有this,所以$color" }
//1、普通调用
cc.produce("green",rec)
//2、匿名函数
cc.produce("red",{"this就是这个对象$this"})
//3、lambda调用,因为是一个参数的表达式,且为最后一个形参
cc.produce("pink"){"这就是lambda的魅力 $this"}
2. 内联函数inline
//定义一个普通函数
fun flag(name:String){
println("插入标记点$name")
}
//普通函数调用上述函数形参
fun addFlag(name:String,action:()->Unit){
println("运行一些代码")
action()
println("插入标记点之后的逻辑")
}
//如上,那么我们需要在打点的位置,每次都要如下方式调用
addFlag("click",{"登录"})
//如此以来,因为形参是函数类型,所以在每次调用的时候,都会在内部生成响应的函数和对象,消耗性能和内存。
//使用inline标记为内联函数,那么形参函数就会被编译时固定化代码(我这么理解),可以复用
inline fun flag(name:String){
println("插入标记点$name")
}
函数就是对象,传递给形参的时候,使用inline
标记的函数,代码块就会放到了其他调用函数之内(编译时期),省去了运行时的调用函数的压栈出栈,跳转等消耗
3. run()
函数
//run函数是Any?的扩展函数,可以用于配合?.操作可null的对象
var str:String?=null
//调用方不确定str是否null,但是还有一些函数需要非null的str作为参数
fun callNoNull(str:String){
//do somethins
}
//此时调用可以用?避免NPE,也想非NUll时候运行callNoNull函数,结合run函数
str?.run{callNoNull(this)}
?.run{
callNoNull1(this)
callNoNull2(this)
}
//如此,str为null,则不会调用callNoNull,若非null,则可调用run内的函数,并且,整个表达式返回最后一个run内的函数的返回结果
4. let()
函数
//类似于run()函数,也可以过滤null对象,不同的是,它使用的是普通的函数形参,而run使用的是receiver函数形式
str?.let{callNoNull(it)}//let函数使用的it,而不是this,同上,可以多次调用,也可以内部多次调用其他函数。并返回最后运算的结果类型
5. with()
函数
//类似于run函数,但是内部不做非空判断,作用就是方便少写一些对象名称,可以用this代替,或者直接取到了对象的属性,函数
val result = with(paramStr){
callNoNull(this)//这里不保证非null,需要自行处理
println(length)//调用的就是paramStr的属性
}
6. apply()
函数
//顾名思义,就是将对象应用于某些操作和算法,这里内部的每个代码块都是拿到的同一个对象,而且不判断null
maybeNull?.apply {
firstFunction(this)
secondFunction(this)
memberPropertyA = memberPropertyB + memberFunctionA()
}?.memberFunctionB()
7. also()
函数
//类似于apply,只是在lambda内,使用it而不是this引用
8. thkeIf()
与thkeUnless()
//takeIf表示摘取仅仅满足条件的数据返回,可能为?,而takeUnless表示提取排除某些条件之外的数据,并返回,也可能为?,所以可以结合配置let函数
array.takeIf{it>12}?.let{it*it}
三、高阶语法
1. 委托delegate
//委托设计模式,例如
interface IAinterface{
val a :Int
fun abc()
}
//A class已经实现了接口
class A:IAinterface{
//重写val属性,只能get,不能set
override val a :Int
get()=10
override fun abc(){
//do something
}
}
//此时的class C如果也实现IAinterface接口,并且它有A的对象引用,那么就可以不必自己实现,通过by 来委托给 a
class C(a:A):IAinterface by a
2. 属性委托
//场景:使用ORM的数据对象时,每一行表格生成一个entity对象,调用方获取entity,并赋值给对应的自定义数据结构,这里就存在了读取和完全赋值,消耗性能与内存,因为并不是每个entity的字段都会用到,而且也不是在赋值给其他对象时候就立即用到,可以延迟加载。
abstract class DbModel(val entity: Entity)
class Person(val entity: Entity) : DbModel(entity) {
val name = entity.getString("name")
val age = entity.getLong("age")
}
//使用自定义的解析
class StringProperty(
private val model: DbModel,
private val fieldName: String,
private val defaultValue: String? = null
) {
private var _value: String? = defaultValue
private var loaded = false
val value: String?
get() {
// Warning: This is not thread-safe!
if (loaded) return _value
if (model.entity.contains(fieldName)) {
_value = model.entity.getString(fieldName)
}
loaded = true
return _value
}
}
//In Person调用时候,去解析db的entity对应字段到person
val name = StringProperty(this,"name","default value")
使用属性委托
class DelegatedStringProperty(
private val fieldName: String,
private val defaultValue: String? = null
) {
private var _value: String? = null
private var loaded = false
operator fun getValue(thisRef: DbModel, property: KProperty<*>): String? {
if (loaded) return _value
if (thisRef.entity.contains(fieldName)) {
_value = thisRef.entity.getString(fieldName)
}
loaded = true
return _value
}
}
//在person类中就可以
val name by DelegateStringProperty(this,"name","default value")
更为优雅的是构建时委托属性
val name:String? by lazy{
if (thisRef.entity.contains(fieldName)) {
thisRef.entity.getString(fieldName)
} else null
}
3. 泛型
- 作为参数
//类的参数
class Node<T>(val name:String,val value:T)
//函数形参
fun <T>getNode(name:String):T{
//do something
}
- 约束
interface IA{}
abstract class BB:IA{}
class C:BB(){}
//约定T泛型的上界,必须是实现BB的子类
class TestT<T:BB>
//或者,可以多个约束上界,但是要求上界之间存在交集
class TestT<T> where T:C,T:IA
- 型变/协变
4. 扩展函数
//可以在外部任何需要的地方,对一个类进行函数扩展,或者属性
val Int.showMyName():String{
return if(this>256)"HH"else "XX"
}
//上面就是对Int类扩展了一个函数,内部的this指向调用方,也就是一个Int对象
12.showMyName()
//扩展属性,因为Int中没有这个字段,所以只能用get这么写
val Int.name:String
get()="哈哈哈哈"
//调用
2.name
扩展函数只是个语法糖
,并不改变被扩展类的真实属性和函数,如果扩展属性和函数是被扩展类已有的,那么就扩展无效,调用的仍旧是自身的,哪怕这个函数是open
的。
5. 反射
- 待学习
6. 注解
- 待学习
7. 文件I/O
File("test.txt").forEachLine{println(it)}
//指定编码读取,默认utf-8
File("test.txt").forEachLine(Charsets.GBK){println(it)}
//流读取,使用字节缓存
val stream = File("test.txt").inputStream()
val bytes = ByteArray(1024)
stream.read(bytes)
stream.close()
println(bytes)
//写数据
File("data.txt").writeText("Hello world !")
8. use
函数
//处理异常时候,IO异常,可以用use,其内部已经有了try catch,可以自动关闭IO
File("/home/user/test.txt").inputStream().use{
val bytes = it.readBytes()
println(bytes.size)
}
9. 密封类
密封类,因此可以理解为,密封到一个文件内的同一类的 类
//1、密封类,必须声明在kt文件根节点,不要放在某个类内部,实现子类可以放在toplevel,或者密封类自身内部嵌套,哪怕在内部多层class内也行
sealed class Expr//限定某个函数的参数只能是expr的类型
{
//在密封类自身内部,可以声明子类
class cccc:Expr(){
//甚至在嵌套的内部,也可以,
object ggg:Expr()
}
}
//下面就是属于expr类型的不同数据,
data class Const(var name: Double) : Expr()
data class BigNumber(val bb: Double) : Expr()
private data class CommonNum(var a: Int) : Expr()
object NotNumber : Expr(){
//密封类的子类中,再声明 密封类的子类,是不行的
object ddjjjd:Expr()//这是不不对的
}
//虽在同一文件,但是再其他类中,也是不可以声明密封类的子类
object dddd{
//这是错的
object dddj:Expr()
}
//使用
//2、密封类,算是一个特别的数据类。不能open,也不要interface,abstract。用作限定数据类型,相比枚举类型,更为灵活。
// 枚举是一个类,多个数值,而密封类,是一个类,可以有很多种子类,大的属于一个类型就行。作为类型限制.
//todo 参照文件Expr声明,这样就能限定参数是expr类型,相比于枚举类型,较为灵活一些
private fun textExpr(e: Expr) {
when (e) {
is Const -> {
//这里就不些具体代码了,
}
is NotNumber -> {
//如果能确定所有类型,其实else可以不写
}
is BigNumber -> {
//密封类就主要用于替换枚举,用于一些要求宽泛的限定数据类型的地方
}
else -> {
//
}
}
}
Kotlin 相关语法的学习,可以参看https://github.com/zhiwei1990/android-jetpack-demo中kotlin
的模块,里面会有详细注释