kotlin笔记

kotlin优势
简洁(data class自带get set equals hashCode toString copy
安全(区别可空和不可空,避免各类空指针)
与java互操作性

基础特性及用法

显式

Kotlin 通过细致的显式声明和约束,使代码的逻辑和结构都更加清晰,并减少错误。

  • 可变与只读
    var 声明可变变量
    val 声明制度变量
    只读集合 List Set Map
    可变集合 MutableList MutableSet MutableMap

  • 类型安全
    是静态隐式类型的,提供类型推断

  • 空引用安全
    区分可空与非空
    可空安全调用bob?.department?.head?.name
    Elvis操作符:如果 ?: 左边的表达式不为 null,则返回该表达式;否则返回 null

      val l = b?.length ?: ‐1
      // 等效于
      val l: Int = if (b != null) b.length else ‐1
    

集合
集合也可以根据是否允许容纳 null进行区分(List / List )。
可以使用 filterNotNull()从可为空类型的集合中过滤非空类型。

  • 修饰符
    可见性
    peivate: 类内可见
    protectedprivate+子类可见
    internal:同一模块(一起编译的一组kotlin源文件)
    public:可见

open:用于继承的类要显示的声明open

inner:内部类,默认内部类不能访问外部类
使用inner标记的类会携带外部类引用,可以访问外部类成员

  • 工程化

数据类解构声明
data class User(val name: String = "", val age: Int = 0)
数据类自动对每个属性生成对应的componentN()函数,用于解构声明: val jane = User("Jane", 35)
val (name, age) = jane

  • 扩展函数
    kotlin提供了对类扩展的功能,可以在不继承或使用设计模式(如装饰着)的情况下,为类添加新的功能

    下面为 MutableList 添加了一个 swap() 函数:
    fun MutableList.swap(index1: Int, index2: Int) {
     val tmp =    this[index1] // this 对应列表本身 this[index1] = this[index2]
     this[index2] = tmp
    }

同时声明以下两个函数回导致冲突:

fun String?.toDecimal(): BigDecimal {...}
fun toDecimal(value: String?): BigDecimal {...}  

扩展函数是静态解析的,其调用取决于声明类型而不是运行时的实际类型

open class C class D: C()
fun C.foo() = "c" fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D()) // c
  • 操作符重载

// todo

进阶特性及用法

kotlin中函数的地位

kotlin中函数是头等的。可以存储在变量与数据结构中、可以作为参数传递给高阶函数、也可以从高阶函数返回。

高阶函数

高阶函数是将函数用作参数或返回值的函数。

fun  lock(lock: Lock, body: () ‐> T): T {
    lock.lock()
    try {
     return body()
    }
    finally {
     lock.unlock()
   }
}

上述代码中,body就是函数类型,该函数没有输入参数,输出类型为T。 它被 lock 保护,在 try 块中调用,其结果由 lock() 函数返回。 lock() 的调用方法如下。

fun toBeSynchronized() = sharedResource.operation()
val result1 = lock(myLock, ::toBeSynchronized)
val result2 = lock(myLock, { sharedResource.operation() })

函数类型

Kotlin 使用类似 (Int) -> String 的一系列函数类型来处理函数的声明: val onClick: () -> Unit = ……。

这些类型具有与函数签名对应的特殊标示法,即他们的参数和返回值:

  • 参数放在()中,参数放在 ->左侧,返回值放在 ->右侧。参数列表可为空,返回类型不可省略,如:()->Unit

  • 参数类型可以有一个额外的接收者类型,他的表示法如:A.(B) -> C。它表示,参数A对象中有一个方法接收一个B类型的参数,并返回C。

  • 挂起函数 一种特殊类型的函数,有一个suspend修饰符。如suspend ()->Unit

  • 函数类型指定为可空需使用圆括号 ((Int,Int) -> Int)?

  • 函数类型可以使用圆括号结合(Int -> (Int -> Int))

  • 箭头表示法是右结合的 (Int -> (Int -> Unit)) 等价于(Int -> Int -> Unit)但不等价于((Int -> Int) -> Unit)

还可以通过类型别名给函数类型起一个别称:

 typealias ClickHandler = (Button,ClickEvent) ->Unit

函数类型实例调用

函数类型的值可以通过invoke(...)调用:f.invoke(x)或者直接f(x)

如果该值具有接收者类型,那么应该将接收者作为第一个参数传递。调用接收者的函数值类型的另一个方式是在其前面加上接收者对象,就好比该值是一个扩展函数:

val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus

println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!")) 

println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // 类扩展调用
  • Lamda表达式
    没有经过声明而直接作为表达式传递的函数。
    编写规则:
    1.Lamda表达式要被{}包围
    2.参数(参数类型可以省略)在->左边
    3.函数体在-> 右边
    4.最后一个表达式作为函数的返回值
    val sum1: (Int, Int) ‐> Int = { x, y ‐> x + y }
    val sum2 = { x: Int, y: Int ‐> x + y }
    sum1(1, 2)
    sum2(1, 2)

如果函数的最后一个参数为表达式,且你传递的是Lamda表达式,那么kotlin的惯例是把Lamda表达式写在括号外面:

lock(myLock, { sharedResource.operation() })
// 等效于
lock (myLock) {
 sharedResource.operation()
}
  • 闭包

Lambda表达式和匿名函数可以访问其闭包,也就是外部作用域声明的变量。不同的是,java可以修改从闭包中获取的变量。

  • 匿名函数

      val isOdd: (Int) -> Boolean = fun(value: Int): Boolean {
          return value % 2 == 1 
      }
    
      val isOdd = fun(value: Int): Boolean {
          return value % 2 == 1 
      }
    
      val isOdd = fun(value: Int): Boolean = value % 2 == 1
    

使用

    val values = listOf(1, 2, 3, 4, 5)
    val filtered = filter(values, isOdd)
  • 可调用引用
    1.引用顶层、局部、成员、扩展函数:::isOdd String::toInt
    2.引用顶层、成员、扩展属性: List::size
    3.引用构造器: ::Regex

      val filtered = filter(values, ::isOdd)
    
  • 实现了函数类型接口的类。使用方法同普通类一样,定义方法如下:

      class OddChecker: (Int) -> Boolean {
          override fun invoke(value: Int): Boolean {
             return value % 2 == 1 
          }  
       }
    
  • Lambda语法糖

如果Lambda只有一个参数,且编译器可以推断其类型,则可以省略参数,此时该参数会被隐式的声明为it

val isOdd: (Int) -> Boolean = { value -> value % 2 == 1 } val isOdd: (Int) -> Boolean = { it % 2 == 1 }
val isOdd: (Int) -> Boolean = { it % 2 == 1 }

如果省略回影响编译器的类型推断,则不可省略

val isOdd = { value: Int -> value % 2 == 1 }

如果函数只返回一个表达式,则可以省略函数体的大括号(和返回类型),使用 = 连接函数体

fun double(x: Int): Int { return x * 2 } 
fun double(x: Int): Int = x * 2
fun double(x: Int) = x * 2
  • 纯函数
    1.对于同样的输入,返回同样的输出
    2.调用函数不会产生副作用(不会修改程序状态)
  • 内置函数

    let T的扩展函数,将block中的操作应用在T上,并返回block的值,在block中使用it引用T的实例
    also T的扩展函数,将block中的操作应用在T上,并返回T,在block中使用it引用T的实例
    withblock中的操作应用在T上,并返回block的值,不是扩展函数,block中使用this引用T的实例
    run T的扩展函数,将block中的操作应用在T上,并返回block的值,在block中使用this引用T的实例
    apply T的扩展函数,将block中的操作应用在T上,并返回T,在block中使用this引用T的实例

  • 集合的操作

      var records: List = ...
      val models: List =
      records // List
      .map { it.transform() } // List .filter { it.units.isNotEmpty() } // List .map { food ->
      food.units.map { unit -> food.deepCopy(listOf(unit)) } } //List> .flatten() // List
      
      val foodMap: Map> = models.groupBy { it.foodCategoryName }
    
  • 参数默认值和命名参数

      fun reformat(str: String,normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') {
       ... 
      }
    

默认参数调用

reformat(str)

指定参数调用

reformat(str, true, true, false, '_')

使用命名参数,提高代码可读性

reformat(str,normalizeCase = true,upperCaseFirstLetter = true,divideByCamelHumps = false,wordSeparator = '_')
  • 委托属性

使用如下形式声明委托属性

class Example {
  var p: String by Delegate() 
}

实现委托

 class Delegate {
     operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!" }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
     }
  }

使用

val e = Example()
e.p = "HelloWorld" // HelloWorld has been assigned to 'p' in    Example@17f052a3. println(e.p) // Example@17f052a3, thank you for delegating 'p' to me!
  • 标准委托 lazy

      val lazyValue: String by lazy { println("computed!")
           "Hello"
       }
        
      fun main(args: Array) { println("Running in main")    println(lazyValue) println(lazyValue)
      }
    

输出

 Running in main
 computed!
 Hello
 Hello
  • observable
    Delegates.observable接收两个参数:初始值和属性变化时的Handler

      class User {
        var name: String by Delegates.observable("") {
        prop, old, new ->
        println("$old -> $new")
        }
      }
    
      fun main(args: Array) { 
          val user = User()
          user.name = "first"
          user.name = "second"
      }
    

输出

   -> first
  first -> second
  • map

      class User(val map: MutableMap) { 
            var name: String by map
            var age: Int by map 
      }
    
      val user = User(mapOf( "name" to "John Doe","age" to 25 ))
      println(user.name) // John Doe println(user.age) // 25
      user.name = "Mary Sue"
      user.age = 26
      println(user.map) // {name=Mary Sue, age=26}
    
  • 内联函数和具体化类型参数
    使用高阶函数会创建额外的函数对象并捕获闭包,带来额外开销。通过inline标注为内联函数,可以避免这些开销。
    inline会同时对函数本身和作为函数参数的lambda表达式生效

    inline fun  lock(lock: Lock, body: () -> T): T { lock.lock()
        try {
           return body()
        } finally {
           lock.unlock()
        }
    }

    foo()
    lock(l) { foo() }
    bar()

相当于

    foo()
    l.lock() 
    try {
       foo()
    } finally {
       l.unlock() 
    }
    bar()

在内联函数中使用 reified 将范型标记为具体化类型参数,在内联函数中,可以像访问参数一样访问具体化类型参数。

  inline fun  fromJson(json: String): T =
  gson.fromJson(json, T::class.java) 
  val user = fromJson(json)
  val user: User = fromJson(json)
  • 支持字段
    在类或累的主构造器中使用varval声明类的属性,不能再类中声明Filed,Kotlin会自动在需要的时候为属性添加一个支持字段。
    class Person {
          var age: Int = 10
              get() {
                  return field 
              }
              set(value) {
                  field = value
              }
    }
  • 具有支持字段的属性

    如果属性使用了至少一个默认的访问器(get/set),或者自定义访问器中使用了filed标识符访问了支持字段,则会生成对应的支持字段。

    class Person(
      val name: String,
      var age: Int){
      val isAdult: Boolean = age >= 18
    

    }
    这里的isAdult相当于java的

    public final boolean isAdult = age >= 18
    val person = Person("John", 17)
    println(person.isAdult) \ false p
    erson.age++
    println(person.isAdult) \ false

  • 没有支持字段的属性

      class Person(
         val name: String,
         var age: Int
      ){
           val isAdult: Boolean
                get() {
                 return age >= 18 
                }
      //   等同于get() = age >= 18
      }
    

这里的isAdult相当于java的

  public boolean isAdult() {
        return age >= 18; 
  }

  val person = Person("John", 17) 
  println(person.isAdult) \\ false 
  person.age++ 
  println(person.isAdult) \\ true

你可能感兴趣的:(kotlin笔记)