Kotlin

Posted by ooftf on April 14, 2021

kotlin关键字

  • abstract 抽象声明,被标注对象默认是open
  • annotation 注解声明
  • by 类委托、属性委托
  • class 声明类
  • companion 伴生对象声明
  • const 声明编译期常量
  • constructor 声明构造函数
  • data 数据类,声明的类默认实现equals()/hashCode()/toString/copy()/componentN()
  • enum 声明枚举类
  • field 属性的幕后字段
  • fun 声明函数
  • import 导入
  • in 修饰类型参数,使其逆变:只可以被消费而不可以被生产
  • init 初始化块;相当于主构造函数的方法体
  • inner 标记嵌套类,使其成为内部类:可访问外部类的成员
  • interface 声明接口
  • internal 可见性修饰符,相同模块内可见
  • lateinit 延迟初始化,避免空检查
  • object 对象表达式、对象声明
  • open 允许其它类继承;kotlin类默认都是final,禁止继承
  • out 修饰类型参数,使其协变:只可以被生产而不可以被消费
  • override 标注复写的方法、属性
  • package 包声明
  • private 可见性修饰符,文件内可见
  • protected 可见性声明,只修饰类成员,子类中可见
  • public kotlin默认的可见性修饰符,随处可见
  • super 访问超类的方法、属性
  • throw 抛异常
  • val 声明只读属性
  • var 声明可变属性
  • vararg 修饰函数参数:声明为可变数量参数
  • inline 声明内联函数
  • noinline 禁用内联,标记内联函数不需要内联的参数
  • crossinline 标记内联函数的lambda表达式参数,标识该lambda函数返回为非局部返回,不允许非局部控制流
  • operator 标记重载操作符的函数
  • suspend 声明挂起函数,该函数只能从协程和其他挂起函数中调用
  • reified 限定类型参数,需要配合inline关键字使用
  • sealed 声明密封类,功能类似枚举
  • typealias 声明类型别名

inline noinline crossinline

inline
  • inline 的工作原理就是将内联函数的函数体复制到调用处实现内联。
    1
    2
    3
    4
    5
    6
    7
    
      inline fun inlined(getString: () -> String?) = println(getString())
      fun notInlined(getString: () -> String?) = println(getString())
      fun test() {
        var testVar = "Test"
        notInlined { testVar }
        inlined { testVar }
      }
    

    反编译成Java代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
      public static final void test() {
          final ObjectRef testVar = new ObjectRef();
          testVar.element = "Test Variable";
        
          // notInlined:
          notInlined((Function0)(new Function0(0) {
              public Object invoke() {
                  return this.invoke();
              }
              @NotNull
              public final String invoke() {
                 return (String)testVar.element;
              }
          }));
        
          // inlined:
          String var3 = (String)testVar.element;
          System.out.println(var3);
      }
    
  • 不应该内联所有功能。而且官方也不建议这样做。Kotlin 贡献者的建议,原文是:「Functions should only be made inline when they use inline-only features like inlined lambda parameters or reified types.」意思就是说:inline 关键字应该只用在需要内联特性的函数中,比如高阶函数作为参数和具体化的类型参数时。

  • 为什么高阶函数为参数时推荐使用 inline 关键字,因为用 inline修饰的方法,不止本方法会内联,如果参数为函数也会内联。就相当于至少减少了两次方法数调用 ?
    noinline

    我们的内联函数中有多个lambda参数时,如果我们想要其中某一个lambda参数不进行内联,则可以使用noinline关键字。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      fun main() {
         myMethod(former = {
             println("former")
         }, latter = {
             println("latter")
         })
      }
        
      private inline fun myMethod(former: () -> Unit, noinline latter: () -> Unit) {
         former()
         latter()
      }
    

    在上述情况下 latter 会被编译成一个Function 而不是直接将实现代码copy到调用处

    crossinline

    在使用inline函数时,有一个注意点:小心地使用流程跳转。示例代码如下: ```kotlin fun main() { println(“start execution:”) sayHello { println(“in lambda”) return } println(“end execution”) }

private inline fun sayHello(block: () -> Unit) { println(“in sayHello”) block() } // 输出结果为: // start execution: // in sayHello // in lambda

1
2
3
4
5
6
可以看出,最后的"end execution"并没有输出在控制台,那是因为程序在执行sayHello时,由于inline函数,导致return语句直接结束了main方法。我们如果想在sayHello定义时,就不想以后出现这样的使用隐患,就可以使用crossinline关键字:
```kotlin
private inline fun sayHello(crossinline block: () -> Unit) {
   println("in sayHello")
   block()
}

在加上了 crossinline 关键字之后,return 语句那块就已经显示语法错误了。

operator

  • 运算符重载就是对已有的运算符赋予他们新的含义。重载的修饰符是operator
  • 运算符重载实际上是函数重载,本质上是对运算符函数的调用,从运算符到对应函数的映射过程由编译器完成。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    data class Person(var name: String, var age: Int){
        operator fun plus(other:Person):Person{
             return Person(this.name+"+"+other.name,this.age+other.age)
        }
    }
    fun main() {
       
        var person1=Person("A",3)
        var person2=Person("B",4)
        var person3=person1+person2
        println("person3=$person3")
    }
     //输出结果
    person3=Person(name=A+B, age=7)
    
  • 我的理解:对 kotlin 具有特殊意义的一些函数进行函数重载需要添加 operator 比如 invoke 、 plus 等

reified

Kotlin-reified 官方文档
这个是为了满足inline特性而设计的语法糖,因为给函数使用内联之后,编译器会用其函数体来替换掉函数调用,而如果该函数里面有泛型就可能会出现编译器不懂该泛型的问题,所以引入reified,使该泛型被智能替换成对应的类型

sealed

声明密封类,功能类似枚举,是对 enum 类的一种扩展

实战举例

举个例子,假如在 Android 中我们有一个 view,我们现在想通过 when 语句设置针对 view 进行两种操作:显示和隐藏,那么就可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 sealed class UiOp {

    object Show: UiOp()
    
    object Hide: UiOp()

 }

 fun execute(view: View, op: UiOp) = when (op) {

    UiOp.Show -> view.visibility = View.VISIBLE
    
    UiOp.Hide -> view.visibility = View.GONE

 }

以上功能其实完全可以用枚举实现,但是如果我们现在想加两个操作:水平平移和纵向平移,并且还要携带一些数据,比如平移了多少距离,平移过程的动画类型等数据,用枚举显然就不太好办了,这时密封类的优势就可以发挥了,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 sealed class UiOp {

    object Show: UiOp()
    
    object Hide: UiOp()
    
    class TranslateX(val px: Float): UiOp()
    
    class TranslateY(val px: Float): UiOp()

 }

 fun execute(view: View, op: UiOp) = when (op) {

    UiOp.Show -> view.visibility = View.VISIBLE

    UiOp.Hide -> view.visibility = View.GONE

    is UiOp.TranslateX -> view.translationX = op.px // 这个 when 语句分支不仅告诉 view 要水平移动,还告诉 view 需要移动多少距离,这是枚举等 Java 传统思想不容易实现的

    is UiOp.TranslateY -> view.translationY = op.px
 }

typealias

  • typealias关键字的作用就是将一个类映射到另一个类上面,或者可以说是给一个类起个别名。
  • typealias关键字不会创建一个新的类型,只是给该类取了一个别名,编译运行的时候还会还原成原来的类型。
  • typealias的映射类只能定义在Kotlin文件内,而不能定义在类中或者方法中。
  • 对于Kotlin中映射类型,在Java中是无法调用的,我们只能调用它的原始类型来和映射类型进行交互。
实战举例
1
2
3
4
5
6
7
8
9
public typealias StringTypealias = String

val nameT: StringTypealias = "ZhangSan"
val nameS: String = "ZhangSan"
fun main() {
    println("nameT==nameS : ${nameT == nameS}")
}
//  输出结果:
//  nameT==nameS : true
应用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Classes and Interfaces (类和接口)
typealias RegularExpression = String
typealias IntentData = Parcelable
 
// Nullable types (可空类型)
typealias MaybeString = String?
 
// Generics with Type Parameters (类型参数泛型)
typealias MultivaluedMap<K, V> = HashMap<K, List<V>>
typealias Lookup<T> = HashMap<T, T>
 
// Generics with Concrete Type Arguments (混合类型参数泛型)
typealias Users = ArrayList<User>
 
// Type Projections (类型投影)
typealias Strings = Array<out String>
typealias OutArray<T> = Array<out T>
typealias AnyIterable = Iterable<*>
 
// Objects (including Companion Objects) (对象,包括伴生对象)
typealias RegexUtil = Regex.Companion
 
// Function Types (函数类型)
typealias ClickHandler = (View) -> Unit
 
// Lambda with Receiver (带接收者的Lambda)
typealias IntentInitializer = Intent.() -> Unit
 
// Nested Classes and Interfaces (嵌套类和接口)
typealias NotificationBuilder = NotificationCompat.Builder
typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback
 
// Enums (枚举类)
typealias Direction = kotlin.io.FileWalkDirection
// (but you cannot alias a single enum *entry*)
 
// Annotation (注解)
typealias Multifile = JvmMultifileClass

一些概念

  • 闭包
  • 高阶函数
  • DSL
  • 带接收者的lambda
  • init方法
  • apply等方法
  • invoke约定
  • 扩展函数
  • 函数库
  • 协变和逆变
  • 协程
  • Flow

闭包

  • 参考文章
  • 如何在外部调取局部的变量呢?答案就是——闭包。闭包就是能够读取其他函数内部变量的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
        //这是一个返回值为一个函数的高阶函数
        fun makeFun():()->Unit{
            var conut = 0
            return fun(){   //返回一个匿名函数,这个函数持有count的状态
                println(++conut)
            }
        }
      
        fun main() {
            val returnFun = makeFun() //函数调用,返回一个函数
            returnFun()       //调用这个返回的函数,此时makeFun持有makeFun()内部变量的状态
            returnFun()
            returnFun()
        }
    

    运行结果:
    1
    2
    3

  • 广义上来说,在Kotlin语言之中,函数、条件语句、控制流语句、花括号逻辑块、Lambda表达式都可以称之为闭包,但通常情况下,我们所指的闭包都是在说Lambda表达式。
  • 函数可以作为变量的经典示例
    1. args.forEach(::println)
    2. 双冒号获取函数对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        class MyLogger(val tag: String) {
          fun print(i: Int) {
              println("$tag  $i")
          }
        }
        fun main(args: Array<String>) {
            val arr = intArrayOf(1, 2, 4, 6)
            arr.forEach(MyLogger("TAG")::print)
        }
      
    3. 声明一个函数并赋值
      1
      2
      3
      
        var oneFun: () -> Unit = fun() {
                 
        }
      
    4. 闭包
      1
      2
      3
      4
      5
      6
      
        fun aaa(): () -> (Int) {
            var current = 10
            return fun(): Int {
                return current++
            }
        }
      

高阶函数

  • 定义:将函数用作一个函数的参数或者返回值的函数。
  • 示例
    1
    2
    3
    
    public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
        for (element in this) action(element)
    }
    

    分析:forEach的参数为一个函数,所以forEach为一个高阶函数

常用高阶函数

  • run
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
       T.run {
              // 入参为this
              // 任何类型可以作为返回值
              R
          }
      kotlin.run {
          // 没有入参
          // 任何类型可以作为返回值
          R
      }
    
  • with
    1
    2
    3
    4
    5
    
      with(Any()) {
          // 入参为this
          // 任何类型可以作为返回值
          R
      }
    
  • apply
    1
    2
    3
    4
    
       Any.apply {
              // 入参为this
              // 返回值和入参一致
          }
    
  • also
    1
    2
    3
    4
    
       Any.also {
              // 入参为 it
              // 返回值和入参一致
          }
    
  • let
    1
    2
    3
    4
    5
    
       Any.let {
              // 入参为 it
              // 任何类型可以作为返回值
              R
          }
    
  • takeIf
    1
    
    "abc".takeIf { it == "sss" }?.length
    

    只有为 true 才会返回调用对象,否则返回 null

  • takeUnless
    1
    
    "abc".takeIf { it == "sss" }?.length
    

    判断条件与 takeIf 相反,只有为 false 才会返回调用对象,否则返回 null

  • repeat
    1
    2
    3
    
    repeat(5){
        println("repeat")
    }
    

    重复执行 闭包 指定次数

DSL

带接收者的lambda

声明一个 lambda 表达式时,参数后面添加 .() 例如 String.() -> Unit ,那么函数实例作用域内的 this 为 String 的实例对象

  • 经典示例apply public inline fun T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }

kotlin跳出循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//跳出本次循环,功能类似于continue
(0..10).forEachIndexed { index, it ->
        println("-- forEach -- ${index} --")
    	if (it > 5) return@forEachIndexed
    	println(it)
  	}
//跳出整个循环,功能类似于break
run outside@{
    (0..10).forEachIndexed { index, it ->
        println("-- forEach -- ${index} --")
        if (it > 5) return@outside
        println(it)
    }
}

be careful

  • 在kotlin项目中 注解处理器要使用kapt代替annotationProcessor,annotationProcessor(可能会失效?)
    添加 apply plugin: ‘kotlin-kapt’
  • databinding 在布局中条用 kotlin 伴生类的方法,调用不到(使用@JvmStatic)
  • kotlin ARouter在autowired 的变量要添加@JvmFiled

创建数组迭代器

(0..10) 表示[0,10]
(0 until 10)表示[0,10)

init{} 执行时机

是在kotlin constructor构造方法之前调用,翻译成Java代码就是插入到构造方法的第一行然后才执行kotlin构造方法

1
2
3
4
5
6
7
8
9
10
11
12
class Person() {
    private var gender: Boolean = true
    constructor(name: String,gender: Boolean):this() {
        println("constructor")
    }
    init {
        println("Person init 2,gender:${gender}")
    }
    init {
        println("Person init 1")
    }
}

执行结果:
Person init 2,gender:true
Person init 1
constructor