Kotlin的一些语法和数据结构

本篇将讲述Kotlin的一些语法糖和Lambda的使用

Kotlin的一些语法和数据结构

本篇已Kotlin为主要语言

数组

  • 数组

    • 创建指定大小的空数组

      1
      var dp = IntArray(n)
    • 创建数组

      1
      var dp = arrayOf(1,2,3)//相当于 int[1,2,3]
    • 创建指定长度的空数组(相当于java中的new int[n])

      1
      val nullArray = arrayofNulls<Int>(6)
    • 数组特点

      • 一个数组只能由同种数据类型组成
      • 数组一旦初始化,长度就固定
      • 数组中元素的地址是连续的
      • 数组本身是没有增删改查的方法

链表

  • 链表通常有单链表和双向链表

  • 单链表

    • 单链表是链表中的一种,它的方向是单向的,对链表的访问要从头部(head)开始,然后依次通过next去访问下一个结点

    • 单链表的数据结构是分为两部分,第一个是元素,第二个是指针。元素用来存储任意数据类型,指针则是指向下一个结点的引用

    • 创建单链表使用的结点

      1
      2
      3
      class Node(var value : Int) {
      var next : Node? = null
      }
    • 创建单链表

      1
      2
      3
      4
      5
      6
      class LinkedNode{
      //头结点
      var head : Node? = null
      //尾结点
      var last : Node?= null
      }
  • 双向链表

    • 双向链表也是链表中的一种,它与单链表不同的是,它的每个结点有两个指针。第一个指针是指向它的前驱结点(也就是指向它的上一个),第二个则是指向它的后继结点(也就是指向它的下一个)。所以双向链表可以很方便的去访问它的前驱结点和后继结点

    • 双向链表的数据结构是由3部分组成,第一部分为prev指针(前驱指针),第二部分为元素,第三部分为next指针(后继指针)。prev指针是指向上一个结点的引用,元素则可以为任意数据类型,next指针则是指向下一个结点的引用

    • 创建双向链表使用的结点

      1
      2
      3
      4
      class BNode(var value : Int) {
      var next : BNode? = null
      var prev : BNode? = null
      }
    • 创建双向链表

      1
      2
      3
      4
      class BothwayNode{
      var head : BNode? = null
      var last : BNode? = null
      }
  • 单向循环链表

    • 单向循环链表,它只是在单链表的基础上使得最后一个结点(尾结点)的指针不在指向空,而是指向头部。使其成为一个循环

    • 单向循环链表的数据结构和单链表是一致的

  • 双向循环链表

    • 双向循环链表,它也只是在双链表的基础上使得最后一个结点的后继指针(next)指向头节点,头结点的前驱指针指(prev)向最后一个结点

    • 双向循环链表的数据结构也是与双向链表一致

  • 链表的基础操作

    • 单链表插入元素

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class LinkedNode {
      var head : Node? = null
      var last : Node? = null

      fun insert(value : Int){
      val node = Node(value)//先创建结点
      if(head == null){//两种情况,第一种为单链表为空
      head = node//则将元素传入,并当前元素即是头也是尾
      last = node
      }else{
      last!!.next = node//如果不为空,则将尾指针指向它,并将尾指针向后移
      last = node
      }
      }
      }
    • 单链表删除元素

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      class LinkedNode {
      var head : Node? = null
      var last : Node? = null

      fun delete(value : Int){
      if (head==null){
      return
      }
      if(head!!.value == value){//如果头就是要删除的元素
      head = head!!.next//将头向后移即可
      return
      }
      var iterator = head//创建一个用来遍历的结点
      while(iterator!!.next!=null){//从头到尾循环
      if(iterator.next!!.value == value){//当下一个结点就是要被删除的元素时,退出
      break
      }
      //每次都向后移
      iterator = iterator.next
      }
      //当退出循环就代表下一个元素是要被删除,则将指针指向下一个
      iterator.next = iterator.next!!.next
      }
      }
    • 双向链表添加元素

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      class BothwayNode {
      var head : BNode? = null
      var last : BNode? = null

      fun insert(value : Int){
      val node = BNode(value)
      if (head == null){//这里都与单链表相似
      head = node
      last = node
      }else{
      //因为双向链表是有前驱和后继指针的
      //所以需要将last(尾指针)指向它,在将插入元素的前驱指向last,最后在将last(尾指针)后移
      last!!.next = node
      node.prev = last
      last = node
      }
      }
      }
    • 双向链表删除元素

      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
      class BothwayNode {
      var head : BNode? = null
      var last : BNode? = null

      fun delete(value : Int){
      if (head == null){
      return
      }
      if(head!!.value == value){
      //当要删除的元素为头时,将头后移
      head = head!!.next
      return
      }
      var iterator = head
      while (iterator!!.next != null){
      if (iterator.next!!.value == value){
      break
      }
      iterator = iterator.next
      }
      if (iterator.next!!.next != null){
      //判断要删除的元素是否为最后一个
      //将要删除元素的后一个的前驱指向当前元素
      iterator.next!!.next!!.prev = iterator
      }else{
      //为最后一个,则只需要将前驱设置为空即可
      iterator.next!!.prev = null
      }
      //完成了前驱,还需要将后继继续指向后一位
      iterator.next = iterator.next!!.next
      }
      }

For

  • 对一个数字进行循环

    • 循环到那个数字(不包括)

      1
      for(i in 1 until n)//n就是输入的数字
    • 循环到那个数字(包括)

      1
      for(i in 1..n)//n就是输入的数字

Lambda

  • 内联扩展函数

    • let

      1
      2
      3
      4
      object.let{
      //默认是it来代表,可以通过it来访问其公有的属性和方法
      it.todo()
      }

      使用场景

      场景一: 最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。

      场景二: 然后就是需要去明确一个变量所处特定的作用域范围内可以使用

      1
      2
      3
      4
      5
      object?.let {
      it.todo()
      it.todo1()
      it.todo2()
      }
    • with

      是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式。

      1
      2
      3
      with(object){
      //todo
      }

      例子展示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      override fun onBindViewHolder(holder: ViewHolder, position: Int){
      val item = getItem(position)?: return

      with(item){
      holder.tvNewsTitle.text = titleEn//这里省略了this.(原本为this.titleEn)
      holder.tvNewsSummary.text = summary
      holder.tvExtraInf.text = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews"
      ...
      }
      }

      可以直接调用with(object)的公有属性

    • run

      1
      2
      3
      object.run{
      //todo
      }

      run可以说是let和with的结合体,它既可以方便调用,又以最后一行为返回值(或指定的return值)

      例子展示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      override fun onBindViewHolder(holder: ViewHolder, position: Int){

      getItem(position)?.run{
      holder.tvNewsTitle.text = titleEn
      holder.tvNewsSummary.text = summary
      ...
      }

      }

      这里是对上面的with进行改变

    • apply

      1
      2
      3
      object.apply{
      //todo
      }

      applyrun很像,run是返回最后一行或者指定返回结果。而apply则是对本身进行修改,返回自己

    • 例子展示

      1
      2
      3
      4
      5
      6
      user.apply{
      //这些name都是user中的公有属性
      name = "xxx"
      age = 10
      ...
      }
    • also

      1
      2
      3
      object.also{
      //todo
      }

      alsolet很像,also是返回它本身,let是返回最后一行

      例子展示

      1
      2
      3
      4
      val adapter = DetailAdapter().also {
      binding.userRandomList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
      binding.userRandomList.adapter = it
      }

高阶函数

顶层函数

​ 在Kotlin中,如果将一个函数写在文件源代码的最上方(也就是不属于任何一个类),那么这个函数就是顶层函数

  • 代码展示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.laboratory.anyrandom.util

    fun toast(msg: String) {
    runOnMainThread {
    Toast.makeText(App.context, msg, Toast.LENGTH_SHORT).show()
    }
    }

    fun runOnMainThread(runnable: Runnable) {
    Handler(Looper.getMainLooper()).post(runnable)
    }

    在上述代码中,toastrunOnMainThread就是顶层函数

  • 使用

    在Kotlin中,顶层函数属于包内成员,包内可以直接使用,包外只需要import该顶层函数,即可使用。

    1
    toast("我是顶层函数")

    在同一个包下可以直接访问

顶层属性

​ 既然有顶层方法,应该也有顶层属性。和顶层函数一样,属性也可以放在文件的顶层,不附属与任何一个类。这种属性叫顶层属性。

  • 代码展示

    1
    2
    3
    package com.laboratory.anyrandom.util

    val topAttributes :Int = 100
  • 和顶层函数相同,想要在Java中进行访问,那么就必须要在路径上方写上(@file:JvmName(“代表名字”))

    1
    2
    3
    4
    @file:JvmName("TopUtils")
    package com.laboratory.anyrandom.util

    val topAttributes :Int = 100

    这样在Java中也可以直接访问

    1
    TopUtils.getTopAttributes();

    当然在Java是通过访问器进行访问的(也就是Get() / Set()方法)

扩展函数

​ Kotlin可以在无需继承的情况下去扩展一个类,调用则像是内部函数一样调用

  • 代码展示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 判断是否是中文字符
    */
    fun Char.isChinese(): Boolean {
    val unicodeBlock = Character.UnicodeBlock.of(this)
    if (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
    || unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
    ) // 中日韩象形文字
    {
    return true
    }
    return false
    }

    声明一个扩展函数,想要一个被扩展的目标。以上面代码为例,这是对Char的扩展,扩展的方法名为isChinese,返回值为Boolean类型

  • 扩展函数只能对Public对象进行扩展,对于Private的成员是无法访问的

  • 扩展函数可以与顶层函数一样,与顶层函数写在同个位置

    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
    @file:JvmName("TopUtils")
    package com.laboratory.anyrandom.util

    import android.os.Handler
    import android.os.Looper
    import android.widget.Toast
    import com.laboratory.anyrandom.App

    //顶层函数
    fun toast(msg: String) {
    runOnMainThread {
    Toast.makeText(App.context, msg, Toast.LENGTH_SHORT).show()
    }
    }

    fun runOnMainThread(runnable: Runnable) {
    Handler(Looper.getMainLooper()).post(runnable)
    }

    //扩展函数
    /**
    * 判断是否是中文字符
    */
    fun Char.isChinese(): Boolean {
    val unicodeBlock = Character.UnicodeBlock.of(this)
    if (unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
    || unicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
    ) // 中日韩象形文字
    {
    return true
    }
    return false
    }

Lazy

  • lateinit var: lateinit只用于可变变量(var xxx)

  • by lazy: lazy只用于不可变变量(val xxx)

    1
    2
    3
    4
    val lazyValue: String by lazy {
    println("computed!")
    "Hello"
    }

    输出结果

    1
    2
    3
    4
    5
      //第一次调用
    computed!
    Hello
    //第二次
    Hello

    第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。