您当前的位置: 首页 >  kotlin
  • 0浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Kotlin语法快速进阶知识索引

沙漠一只雕得儿得儿 发布时间:2020-12-27 22:06:14 ,浏览量:0

Kotlin语法快速进阶知识索引目录

一、基本语法

1. 表达式的使用

2. 可空值?类型检测null

3. 类型检测is、!is

4. for循环,使用in 、 或者.. 、Range

5. When表达式

6. 高阶函数及lambda

7. isInitialized

8. typealias类型别名

9. infix声明中缀函数

10. operator操作符函数重载

11. 函数式编程

二、中级语法

1. 接收者receiver

2. 内联函数inline

3. run()函数

4. let()函数

5. with()函数

6. apply()函数

7. also()函数

8. thkeIf()与thkeUnless()

三、高阶语法

1. 委托delegate

2. 属性委托

3. 泛型

4. 扩展函数

5. 反射

6. 注解

7. 文件I/O

8. use函数

9. 密封类

一、基本语法 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()
  //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(val name:String,val value:T)
//函数形参
fun getNode(name:String):T{
  //do something
}
  • 约束
interface IA{}
abstract class BB:IA{}
class C:BB(){}
//约定T泛型的上界,必须是实现BB的子类
class TestT
//或者,可以多个约束上界,但是要求上界之间存在交集
class TestT 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的模块,里面会有详细注释

转自:https://blog.csdn.net/zhiwei9001

关注
打赏
1657159701
查看更多评论
立即登录/注册

微信扫码登录

0.0444s