Skip to content

Kotlin 实际应用

声明

这个部分主要是说明 Kotlin 变量、方法、类的声明,同时包含一些关于 Kotlin 的类型。

变量

  1. var 为定义一个可以多次赋值的关键字;val 为定义只能赋值一次的关键字,相当于 java 中的 final。关键字后紧跟着变量名,其后是 : 再后面是类型,最后就是值。「Kotlin 可以进行类型推断」
val name: String = "text"
var text: String = ""
// 使用类型推断
val name = "text"
var text = ""
  1. 由于 Kotlin 的设计保证了其空安全的特性,保证了每个变量不能为空,可有些时候某个变量可能会为空怎么办?答案就是在声明的类型后面加上 ? ,就告诉编译器这个变量是可空变量。
var listener: ClickListener? = null

需要注意的是,如果定义一个变量为可空变量,那么在引用它时,也要加上空安全调用符,或你虽然定义的是可空,但某些时候你明确知道它不可能是空的,这时候要加上非空断言。

listener?.onClick() //空安全调用相当于在调用时加上了 if 为空的判断
listener!!.onClick() // 不做检查,如果为空,程序崩溃

在 Java 代码中被 @Nullable 所修饰的变量,在使用 Kotlin 调用时等同于调用 Kotlin 的可空变量,需要加上安全调用符号 ? 或非空断言 !!

  1. 在某些时刻,我们需要定义一个非空变量,同时我们也没有办法在定义的时候给出初始值,这时候就需要 lateinit 这个关键字了。先声明出这个变量,但延迟初始化,或者说把初始化变量的时机交给开发者。但如果没有进行初始化就进行调用,也是会报错的。
lateinit var view: View
override fun onCreate(...) {
    ...
    👇
    view = findViewById(R.id.tvContent)
}

方法「函数」

  1. 在 Kotlin 中声明方法,是通过 fun 关键字进行的,同时将返回值类型写在方法名的最后。具体语法如下
fun setOnClickListener(listener: ClickListener) {
// fun setOnClickListener(listener: ClickListener): Unit { 省略返回值
    this.listener = listener
}

fun setNumber(number: Int): Int {
  return number * 2
}

从这个例子中,可以看出来方法的声明和变量的声明有些类似。其中参数的声明也是几乎与声明变量等同的。在 Java 中如果一个方法的返回类型是 void,是需要声明出来的,在 Kotlin 中是可以省略的,不过也有一个关键字 Unit 与 void 相对应。

  1. 在 Kotlin 中,方法的参数是可以有默认值的。具体的写法就是在参数的类型后面直接赋值。在调用它时默认值可以不写。
fun setNumber(number: Int = 1): Int {
    return number * 2
}

setNumber()
  1. 当一个方法的参数有多个时,可以为多个参数分别设置默认值,或选择性的设置默认值。不过需要注意的时,如果没有默认值的参数不是在参数列表的前面,在调用时需要显性的声明出字段名。
// 没有默认值的参数不在前面
fun setNumber(number: Int = 1, scale: Int): Int {
    return number * scale
}

setNumber(scale = 2)


//没有默认值的参数在前面
fun setNumber(scale: Int, number: Int = 1): Int {
  return number * scale
}

setNumber(2)
  1. 与声明可空变量相同,在声明方法时,无论是参数还是返回值如果是可能为空,只要在类型后加上 ? 即可。
fun setInfo(avatar: String, name: String?): Info? {
    ...
}
  1. Kotlin 中对方法的声明还可以进一步的简化,具体做法就是用 = 将大括号及 return 关键字省略。而对于没有返回值的,也是用 = 连接实际执行方法的语句。
fun area(width: Int, height: Int): Int {
    return width * height
}
// 简写如下
fun area(width: Int, height: Int): Int = width * height

fun sayHi(name: String) {
    println("Hi " + name)
}
// 简写如下
fun sayHi(name: String) = println("Hi " + name)

类型

Kotlin 中也有类型之分,某些时候没有声明出来,是因为编译器能够自动推断。与 Java 类似,Kotlin 也有一些基本类型:

var number: Int = 1 // 类似的还有 Double Float Long Short Byte
var c: Char = 'c'
var b: Boolean = true
var array: IntArray = intArrayOf(1, 2) // 类似的还有 FloatArray DoubleArray CharArray 等
var str: String = "string"

但与 Java 又不同的是,Kotlin 中的基本类型都是对象,所以会有一定的装箱拆箱动作。而稍稍复杂的是,并不是所有情况下都是需要装箱拆箱,比如

var a: Int = 1 // unbox
var b: Int? = 2 // box
var list: List<Int> = listOf(1, 2) // box

Kotlin 在语言层面简化了 Java 中的 int 和 Integer,但是我们对是否装箱的场景还是要有一个概念,因为这个牵涉到程序运行时的性能开销。

因此在日常的使用中,对于 Int 这样的基本类型,尽量用不可空变量。

主要说明类、接口。

  1. 与 Java 相同,声明一个对象也是用 class 这个关键字。不过 Kotlin 中声明类时默认的访问权限就是 public,同时还是被 final 所修饰的。

注意:也不是所有的类都会被加上 final 关键字,抽象类就不会,因为抽象类是不能实例化的,是必须要被实现的。

class Person {

}
  1. 声明接口则跟 Java 完全相同。
interface Teacher{

}
  1. 在 Kotlin 中省去了 extends 和 implements 两个关键字,取而代之的是 : 如果需要继承或实现某个接只需要在 :写出响应的类名、接口名即可,如果存在多个使用 , 进行分割。
class Student : Person(), Child {

}

open class Person {

}

interface Child {

}

由于默认的 Kotlin 声明的类是被 final 所修饰的,所以如果想继承某个类,需要在那个类声明的 class 关键字前加上 open 关键字。

构造函数

与 Java 相同,Kotlin 也有构造函数或者说叫构造器的概念,不同的是,Kotlin 把构造函数分为两类,主构造函数和次级构造函数。

在上面的例子,我省去了类的主构造器的声明,就像 Java 中可以无参构造函数是一样的。

class Person constructor(){

}

当然这样写,编译器会提示把 constructor 省略。

特性「特殊之处」

  • 声明变量
  • get\set
  • 集合的可修改和不可修改

  • Class 特性

  • Object 单例
  • data 数据类 toString 等

高阶函数

一个函数的参数或是返回值类型是函数类型的,那么这个函数就是高阶函数。

同时,在定义时可以设置这个函数为扩展函数或是 suspend 函数。

函数类型作为参数

fun doSome(number: Int, action: (n: Int) -> Int): Int {
    return action.invoke(number + 2)
}

@Test
fun test_run_param() {
  val doSome = doSome(10, fun(n: Int): Int {
    return n * n
  })
  println("do some result: $doSome")

  val doSome1 = doSome(10) { it * it }
  println("do some result: $doSome1")
}

应用场景:需要对稍候执行的内容进行自定义的场景下,比如:listener 的回调方法就可以使用高阶函数进行传递。

函数类型作为返回值

fun getCostCaculator(delivery: Delivery): (Order) -> Double {
    if (delivery == Delivery.standard) {
        return { order -> order.goodCount * 10.4 + 10 }
    }
    return fun(order: Order): Double { return order.goodCount * 25.5 + 20 }
}

@Test
fun test_run_return() {
  val costCaculator = getCostCaculator(Delivery.standard)
  val invoke = costCaculator.invoke(Order(10))
}

通过将返回值设置为函数类型并返回一个函数,这种函数也是高阶函数。if 判断内的 return 为简写。

应用场景:不需要立即执行 invoke 方法时可以使用这个函数。当需要立即调用返回的函数时不如返回一个包含这个函数的对象。

问题:既然我能返回一个函数类型,而且这个函数类型还没有被执行 invoke 方法,为什么我不返回 这个函数类型的 invoke 方法后返回 invoke 的返回值?而且返回一个 invoke 方法最终也是会被调用的,如果不被调用,那么这个返回的函数就是无意义的。

内联扩展函数