Kotlin快速进阶知识索引(一)

Kotlin快速进阶知识索引

相关链接:

github

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-demokotlin的模块,里面会有详细注释

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249