Kotlin-语言

概述

  • Kotlin是面向对象的静态类型语言;
  • 在Kotlin中,所有东西都是对象,在这个意义上可以在任意变量上调用成员函数与属性;
  • Kotlin优点
    • 简洁
      • 提供了大量的语法;
      • 减少样板代码;
    • 安全
      • 减少空指针判断;
      • 类型支持可空;
    • 互操性
    • 协程
  • 来源于 Kotlin1.3.50 官方文档

基础

    • 包的声明位于源文件顶部;
    • 目录与包的结构可以无需匹配;
    • 源文件的所有声明(包括类,函数,变量)都包含在包的声明中;
    • 以下包会默认导入Kotlin文件
      • kotlin.*
      • kotlin.annotation.*
      • kotlin.collections.*
      • kotlin.comparisons.*
      • kotlin.io.*
      • kotlin.ranges.*
      • kotlin.sequences.*
      • kotlin.text.*
    • 根据目标平台会导入额外的包
      • JVM
        • java.lang.*
        • kotlin.jvm.*
      • JS
        • kotlin.js.*
    • import
      • 可以导入指定Kotlin文件;用来导入类,顶层函数和属性,在对象声明中声明的函数和属性,枚举常量;
      • 如果名字冲突,可以用 as 起个别名;
  • 基本类型
    • Kotlin中基本类型有:数字,字符,布尔,数组与字符串;
    • 数字
      • 对于整数有四类:Byte Short Int Long,对应字节长度为 8 16 32 64;不显式指定类型时,未超过 Int 最大值的整数被推断为 Int 类型,否则推断为 Long,除非后缀 L 显式指定 Long 类型;
      • 无符号整数类型:UByte UShort UInt ULong;字面量整数后缀 u U 表示无符号类型;
      • 对于浮点数有两类:Float Double,对应字节长度为 32 64;默认推断为 Double ,除非后缀 f / F 显式指定 Float 类型;
      • 在Java平台,数字是物理存储为JVM中的原生类型;
      • 两个数字对象保留了相等性(==),但不保留同一性(===);
      • 数字之间没有隐式转换(小类型隐式转换为大类型);
      • + - * / % 可用于数字间运算;
      • 位运算
        • 没有特殊字符用于位运算;只可用中缀方式调用具名函数;只能用于 Int Long
        • shl(bits) - 有符号左移
        • shr(bits) - 有符号右移
        • ushl(bits) - 无符号左移
        • ushr(bits) - 无符号右移
        • and(bits) - 位与
        • or(bits) - 位或
        • xor(bits) - 位异或
        • inv() -位非
      • 逻辑运算
        • == ===
        • < <= > >=
        • in a...b !in a...b
    • 字符
      • Char 表示,字面常量用单引号括起来,支持转义字符和Unicode转义序列;
    • 布尔
      • Boolean 表示,有两个值 true false
      • 逻辑运算有 || && !
    • 数组
      • 数组用 Array 表示,定义了 operator fun get(index: Int): T operator fun set(index: Int, value: T): Unit 函数;
      • 数组类型不是协变的,不同于Java中的数组类型;
      • 有专门的原生数组类型:ByteArray IntArray 等等;无符号原生数组类 UByteArray UShortArray UIntArray ULongArray
    • 字符串
      • 字符串用 String 表示,字符串的元素--字符可以用索引表示 s[1]
      • 字符串模版
  • 变量
    • val 用来定义只读变量,只能为其赋值一次;var 用来定义可修改变量;
  • 控制流
    • if
      • if 也可以当作表达式;当 if 当作表达式(返回一个值)而不是语句时,需要 else 分支;
    • when
      when (x) {
       1 -> print("x == 1")
       2 -> print("x == 2") 
        else -> {
           // 注意这个块
           print("x is neither 1 nor 2") 
        } 
      }
      
      when (x) {
       0, 1 -> print("x == 0 or x == 1")
       else -> print("otherwise") 
      }
      
      when (x) {
       in 1..10 -> print("x is in the range") 
       in validNumbers -> print("x is valid") 
       !in 10..20 -> print("x is outside the range")
       else -> print("none of the above") 
      }
      
      • when 将它的参数与所有的分支条件顺序比较,直到某个分支满足条件;when 既可以当作语句,也可以当作表达式;作为表达式,必须有 else 分支;
      fun Request.getBody() = 
          when (val response = executeRequest()) { 
            is Success -> response.body 
            is HttpError -> throw HttpException(response.status) 
        }
      
      • 自 Kotlin 1.3 起,可以使用以下语法将 when 的主语(subject,译注:指 when 所判断的表达式)捕获到变量中;在 when 主语中引入的变量的作用域仅限于 when 主体;
    • for
      for (item in items) { ... }
      for (index in 1...100) { ... }
      
      • 可以使用 in 关键词;
    • while
      • while do while 和其他语言一样;
    • return break continue
      • return 默认从直接包围的函数或者匿名函数返回;支持返回标签函数;
      • break 默认从直接包围循环跳出;支持跳出标签循环;
      • continue 默认进入直接包围循环的下一次迭代;支持进入标签循环的下一次迭代;
  • 表达式
  • 函数
    • Kotlin中函数是头等公民;这表明函数可以存储在变量和数据结构中,作为参数传递给高阶函数或者从高阶函数中返回;
    • 函数声明
      fun sum(a: Int, b: Int): Int { return a + b }
      
      • 定义了函数名为 sum,参数 aInt 类型 bInt 类型,返回类型为 Int 的函数;
      • 如果返回类型为无意义的值,用 Unit 表示,可省略返回类型;return 语句可推断返回类型时,也可省略显式声明的返回类型;
      fun sum(a: Int, b: Int) = a + b
      
      • 用表达式表示函数体;
    • 函数参数
      • 函数参数可以有默认值;调用时可以不传递有默认值的参数;
      • 最后一个参数可以是可变数量参数,用 vararg 前缀;
    • 函数作用域
      • 函数可以在顶部声明,也可以在类中声明,还可以在函数中嵌套声明;
      • 局部函数是在一个函数内声明的函数,可以访问外部函数(闭包)的局部变量;
    • 扩展函数
    • 中缀函数
      • 中缀函数用 infix 表示;
      • 中缀函数必须只有一个参数,不能有默认值,不能是可变数量参数;
    • 作用域函数
      • Kotlin 标准库包含几个函数,它们的唯一目的是在对象的上下文中执行代码块;当对一个对象 调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域;在此作用域中,可 以访问该对象而无需其名称,这些函数称为作用域函数;包括 let run with apply also;这些函数基本上做了同样的事情:在一个对象上执行一个代码块;不同的是这个对象在块中 如何使用,以及整个表达式的结果是什么;
      • 每个作用域函数之间 有两个主要区别:
        • 上下文对象 this it
          • 在作用域函数的 lambda 表达式里,上下文对象可以不使用其实际名称而是使用一个更简短的引用来访问;
          • 每个作用域函数都使用以下两种方式之一来访问上下文对象:作为 lambda 表达式的接收者( this )或者作为 lambda 表达式的参数( it );run with apply 中上下文对象为 thislet also 中上下文对象为 it
        • 返回值
          apply also 返回上下文对象;let run with 返回lambda表达式结果;
    • 高阶函数
      • 高阶函数是将函数用作参数或者返回值的函数;
      • 函数类型
        • 函数类型声明
          (A, B, C,...) -> R
          A.(B, C,...) -> R
          
          • 以上表示一个函数类型,其中A,B,C表示函数的参数类型,R表示返回类型;
          • 函数类型可以有一个额外的接受者;
        • 函数类型实例化
          • 使用函数字面值的代码块
            • lambda表达式
            • 匿名函数
          • 使用已有声明的可调用引用
            • 顶层,局部,成员,扩展函数:::isOdd() String::toInt
            • 顶层,成员,扩展属性:List<Int>::size
            • 构造函数:::Regex
          • 使用实现函数型接口的自定义类的实例
        • 函数类型实例调用
          • 函数类型的值可以直接调通过操作符 invoke(...) 调用:f.invoke(x) 或者 f(x)
      • lambda表达式
        • lambda表达式与匿名函数是 函数字面值,即未声明函数,但是可以立即作为表达式传递;
        val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
        
        • -> 前面是函数参数,后面是函数体;
        • 如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外;也叫尾式 lambda 表达式;如果函数只有一个函数作为参数,圆括号可以省略;
        • 如果 lambda 表达式只有一个参数,可以省略 参数声明,直接用 it 表示;
        • lambda 缺少返回类型(虽然可以推断出来),但是匿名函数可以显式指定返回类型;
        • lambda 表达式或者匿名函数,以及局部函数和对象表达式,可以访问闭包,即在外部作用域中声明的变量;
        • 带有接受者的函数字面值;
    • 内联函数
      • 使用高阶函数,会带来运行时的效率损失:每个函数都是一个对象,并且会捕获一个闭包,内存分配(对于函数对象和类)和虚拟调用都会引入运行时开销;
  • 类与对象
      • 类用 class 表示;类声明由类名,类头(类型参数,主构造函数)和类体构成,类头和类体是可选的;
      • 类的成员包括
        • 构造函数与初始化块
        • 函数
        • 属性
        • 嵌套类与内部类
        • 对象声明
    • 主构造函数
      class Person constructor(firstName: String) { /*……*/ }
      
      • 主构造函数是类头的一部分;如果主构造函数没有任何注解或者可见性修饰符,则可以省略 constructor 关键字;主构造函数不能包含任何的代码;
      • 初始化代码可以放在 init 开头的代码块中;在实例初始化期间,初始化块按照在类体中出现的顺序执行,与属性初始化器交织在一起;主构造器的参数可以在初始化块和属性初始化器中使用;
    • 次构造函数
      • 如果有主构造函数,那么次构造函数必须委托给主构造函数或者另一个次构造函数(:this());
      • 委托给主构造函数会作为次构造函数的第一条语句,及时没有主构造函数,初始化块和属性初始化器也会隐式委托;
    • 实例
      • 没有 new 关键字;
    • 属性
      var <propertyName>[: <PropertyType>] [= <property_initializer>] 
        [<getter>] 
        [<setter>]
      
      • 初始化器(initializergetter setter 都是可选的;如果可以从初始化器中推断出类型,属性类型也可以省略;
      • val 不允许 setter
      • 当一个属性需要幕后字段时,会自动提供,可以使用 field 在访问器中使用;如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会 为该属性生成一个幕后字段;
      • 幕后属性
      • 如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量;
      • 延迟初始化
        • 属性声明为非空类型,必须在构造函数中初始化;但是可以通过 lateinit 延迟初始化;并且不能是原生类型;
        • 如果在未初始化时访问一个 lateinit 属性,会抛出异常;
      • 继承
        • 单继承,根类为 Any ,有三个方法: hashCode() toString() equals()
        • 覆盖方法
          • 基类中可覆盖方法用 open 修饰;对 final 类用 open 则无效;
          • 子类中覆盖方法用 override 修饰;override 本身是 open 的,如果不想再被子类覆盖,需要用 final 修饰;
        • 覆盖属性
          • 与覆盖方法类似,必须具有兼容的类型;可以用 var 属性覆盖 val 属性,反之不行;
          • 可以在主构造函数中用 override 覆盖属性(可以理解为构造函数的参数);
      • 扩展
        • Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式;
        • 扩展函数
          fun MutableList<Int>.swap(index1: Int, index2: Int) {
           val tmp = this[index1]
           // “this”对应该列表 
           this[index1] = this[index2] 
           this[index2] = tmp 
          }
          
          • 声明一个扩展函数,我们需要用一个 接收者类型也就是被扩展的类型来作为他的前缀;
          • this 表示扩展函数的接受者对象;
          • 为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数;
          • 扩展是静态解析的;扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成 员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数;
        • 扩展属性
        • 伴生对象的扩展
          • 可以为伴生对象扩展函数与属性;
      • 委托
        • Java中的委托模式
          class Derived(b: Base) : Base by b
          
          • Derived 类可以通过将其所有公有成员都委托给指定对象(by b);
        • 委托属性
          val/var <属性名>: <类型> by <表达式>
          
          • 延迟属性 Lazy
            val lazyValue: String by lazy { 
              println("computed!") 
              "Hello" 
            }
            
            • 用于 val 属性;
          • 可观察属性 Observable
            var name: String by Delegates.observable("<no name>") { 
              prop, old, new -> 
              println("$old -> $new") 
            }
            
      • 数据类
        • 数据类用 data class 表示;
        • 主构造函数的参数必须用 var val 声明;
      • 密封类
        • 密封类用 sealed class 表示;
        • 密封类的子类必须在同一个文件中,其他文件中无法访问;密封类是抽象的;构造函数是 private
      • 枚举类
        • 枚举类用 enum class 表示;
      • 内部类
        • 内部类用 inner 表示,内部类有外部实例;
      • is 运算符监测一个表达式是否某类型的一个实例;
    • 对象
      • object 表示对象声明,类似于Java中的单例对象;
      • 类体中 companion object 表示伴生对象,类似于Java中的静态变量;
  • 集合
    • 基础
      • 集合包括 List Set Map
      • 只读集合是协变的 ;可变集合不是协变的;
    • 区间
      1...10
      1 until 10
      
      • 第一个表示闭区间 [1, 10],第二个表示开区间 [1, 10)
    • 数列
      • 可以在区间上加 step
    • 集合操作
      • 集合操作一般定义为成员函数和扩展函数;
      • 操作不会影响原始集合,生成一个新的集合;
      • 转换
        • map
          • 参数为元素
          • 结果顺序与原始顺序相同;
        • mapIndexed
          • 参数多了索引;
        • zip
          • 双路合并是根据两个集合中具有相同位置的元素构建配对;
          • 返回 Pair 对象的列表;
        • associateWith
          • 关联允许从集合元素与与其关联的某些值构建Map;
          • 以原始集合元素作为Map的键;
        • associateBy
          • 以原始集合元素作为Map的值;
        • associate
          • Map的键和值都是生成的,lambda返回 Pair,对应Map的Key和Value;
        • flatten
          • 可以在一个集合的集合上调用它;
        • flatMap
          • 需要一个函数,将一个集合元素映射到另一个集合上;
      • 过滤
        • filter
        • filterIndex
        • partition
        • 返回一个 List的Pair ,第一个List包含lambda符合条件的元素,第二个包含不符合lambda的元素;
      • 检验谓词
        • any:至少一个匹配成功,返回true;
        • none:没有元素匹配成功,返回true;
        • all:所有元素都匹配成功,返回true;空集合永远都返回true;
      • 分组
        • groupBy
          • 返回Map,key是lambda表达式的值,value为对应元素的list;
      • 取集合的一部分
        • slice
          • 返回给定索引的集合元素列表;
        • take takeLast
          • 从头部/尾部返回指定数量元素的集合;当数量大于集合大小时,返回整个集合;
        • drop dropLast
          • 从头部/尾部去掉给定数量的元素;
        • chunked
          • 将集合分解为给定大小的“块”;
        • windowed
      • 取单个元素
        • elementAt
        • first
        • last
        • random
      • 排序
      • 聚合
        • min
        • max
        • average
        • sum
        • count

协程

  • 协程后面会单独讲;

习惯用法

  • 数据类
    data class Customer(val name: String, val email: String)
    
    • 会为 Customer 提供以下功能:
      • 所有属性的 getters (对于 var 还会提供 setters);
      • equals()
      • hashCode()
      • toString()
      • copy()
      • 所有属性的 component1() component2() 等等;
  • 单例
    object Resource { val name = "Name" }
    
  • 函数参数的默认值
    fun foo(a: Int = 0, b: String = "") { …… }  
    
    • 在定义函数时,可以设置参数的默认值;
  • 扩展函数
    fun String.spaceToCamelCase() { …… } 
    "Convert this to camelcase".spaceToCamelCase()
    
  • 单表达式函数
    fun theAnswer() = 42
    
  • 一个对象的多个方法调用
    class Turtle {
     fun penDown()
     fun penUp()
     fun turn(degrees: Double)
     fun forward(pixels: Double)
    }
    val myTurtle = Turtle()
    with(myTurtle) { // 画一个 100 像素的正方形
     penDown()
     for(i in 1..4) {
       forward(100.0)
       turn(90.0)
     }
    penUp() }
    
  • 配置对象的属性
    val myRectangle = Rectangle().apply {
     length = 4
     breadth = 5
     color = 0xFAFAFA
    }
    
  • 字符串模版
    println("Name $name")
    
  • 使用区间
    for (i in 1..100) { …… } // 闭区间:包含 100 
    for (i in 1 until 100) { …… } // 半开区间:不包含 100 
    for (x in 2..10 step 2) { …… } 
    for (x in 10 downTo 1) { …… } 
    if (x in 1..10) { …… }
    
  • 延迟属性
    val p: String by lazy { // 计算该字符串 }
    

编码规范

  • 目录结构
    • 包结构与源文件目录保持一致;
  • 源文件名称
    • 如果源文件只包含单个类(以及可能相关的顶层声明),那么文件名应该和类名一致,并后缀 .kt ;如果源文件包含多个类或只包含顶层声明,那么选择一个和该文件内容相关的名称,并以此命名,使用大驼峰风格;
  • 源文件布局
    • 鼓励多个声明(类,顶层函数或者属性)放在一个Kotlin文件中,只要这些声明在语义上是相关联的;
  • 类布局
    • 类内容应该按照以下顺序排列
      • 属性声明与初始化块
      • 次构造函数
      • 方法声明
      • 伴生对象
      • 嵌套类(主要为了外部使用)
    • 不要按可见性或者字母顺序对方法进行排序,也不要将常规方法与扩展方法分开;而是把相关方法放在一起,包括相关联的嵌套类;
    • 重载方法放在一起;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容