Kotlin 笔记

函数

方法是一种特殊的函数,它必须通过类的实例调用,也就是说每个方法可以在方法内部拿到这个方法的实例。这是方法和函数的不同之处。
方法和函数几乎一模一样,唯一的区别就是方法必须声明在类里面。

返回 Nothing

如果一个函数不会返回(也就是说只要调用这个函数,那么在它返回之前程序肯定退出了(比如一定会抛出异常的函数)), 因此你也不知道返回值该写啥,那么你可以让它返回 Nothing。

返回 Unit

如果一个函数不返回东西,你可以不写返回值。也可以让它显示返回 Unit

1
2
fun square(int: Int) = int * int
val square = { int: Int -> int * int}

fun xxx() = Unit 表示这是一个空函数。

内部函数

函数体内定义的函数。

1
2
3
4
5
6
7
fun myfun(){
val name = "VanceKing"
fun printlnName(){
println(name)
}
printlnName()
}

中缀表达式

其实是 Kotlin 方法的一种语法糖,一个方法如果在声明时有一个 infix 修饰符,那么它可以使用中缀语法调用。

所谓中缀语法就是不需要点和括号的方法调用

1
2
3
4
5
6
7
8
9
10
11
12
class B

class A{
infix fun infixFunction(b: B) {
}
}

fun main(){
val a = A()
a.infixFunction(B())
a infixFunction B()
}

操作符重载

Kotlin 的操作符重载的规则是

  1. 该方法使用 operator修饰符
  2. 该方法的方法名必须被声明为特定的名称,以将对应的操作符映射为这个函数的调用
  3. 参数必须符合该操作符的规定,比如+的重载就不能有多于一个(不含)的参数,也不能为空参数。

eg:

1
2
3
4
5
6
7
class A {
operator fun plus(a: A) {
println("invoking plus")
}
}

A() + A()

函数的柯里化

高阶函数

  • 高阶函数:以另一个函数作为参数或者返回值的函数
  • 函数类型:参数类型->返回类型,Unit 不能省略

内联函数

内联函数最好的好处就是直接内联 Lambda,不产生匿名内部类对象。

扩展函数

使用一些语法糖来假装给一些类添加方法,并像真正的方法一样调用它。

扩展 Lambda

泛型扩展

Lambda 表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Lambda对象有缺省的invoke函数的实现
fun main() {
{ println("AA") }.invoke()
}

//操作符重载
{ println("AA") }()
//有一个参数
{ str: String -> println(str) }("123")
//有多个参数
{ name: String, age: Int -> println("$name, $age") }("Vance", 18)

val value = { name: String, age: Int -> age }("Vance", 18)
//使用“_”代替不需要命名的参数
val value = { _: String, age: Int -> age }("Vance", 18)

Lambda 经常作为函数参数使用。

一些约定

  • 如果 lambda 表达式作为函数最后一个实参,可以将 lambda 表达式放到括号外面;
  • 当 lambda 作为函数唯一的一个实参时,可以将函数括号直接省略;
  • 当有多个实参时候,即可以选择把 lambda 留在括号内强调它是一个实参,也可以放到括号外边;

常见的符号

  1. $:字符串模板

  2. ?:表示这个对象可能为空

  3. ?::Elvis 操作符

    1
    val length = name?.length ?: -1
  4. !!:表示该对象不为 null,否则调用时会抛出 NPE

  5. ==:比较对象的值;===:比较对象的地址;

  6. ..:表示区间

    1
    2
    if (i in 1..10) // [1, 10]
    for (i in 1..4 step 2)// "13"
  7. _:不需要指定名称的变量,类似占位符

  8. ::

    1. 表示把一个方法当做一个参数,传递到另一个方法中进行使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      fun main() {
      val user = User()
      // result is {a , b}
      println(user.lock("a", "b", user::getResult))
      }

      class User {
      fun getResult(str1: String, str2: String): String = "result is {$str1 , $str2}"

      fun lock(p1: String, p2: String, lambda: (str1: String, str2: String) -> String): String {
      return lambda(p1, p2)
      }
      }

      listOf("Hello", "World").forEach(::println)
      ```

    2. 得到 Class 对象,如:String::class.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // package kotlin.jvm
      public val <T> KClass<T>.java: Class<T>
      @JvmName("getJavaClass")
      get() = (this as ClassBasedDeclarationContainer).jClass as Class<T>

      // reified 实化范型参数
      inline fun <reified T : Activity> openActivity() {
      startActivity(Intent(this, T::class.java))
      }
    3. 判断 lateinit 变量是否初始化

      1
      2
      3
      4
      private lateinit var name: String
      if (::name.isInitialized) {
      //...
      }
  9. @

    1. 限定 this 的类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      class User {
      inner class State {
      fun getUser(): User {
      return this@User
      }

      fun getState(): State {
      return this@State
      }
      }
      }
    2. 作为标签

    1
    2
    3
    4
    5
    6
    7
    loop@ for (i in list) {
    for (j in list2) {
    if (1 == j) {
    break@loop
    }
    }
    }

关键字

  1. lateinit

    延迟初始化变量。

    在编译层面上,kotlin 的编译器不会做这种检查。如果你将变量声明为 lateinit,它就认为你肯定会初始化,至于你是怎么初始化它的,它就不管了。
    如果一个变量声明为 lateinit,但是没有初始化,而又被使用了的话,会抛出一个异常 UninitializedPropertyAccessException。

    在访问的变量的那个地方,插入 ifnonnull 字节码指令,检测是否为 null。

    类似 by lazy:

    1
    2
    3
    val name: String by lazy {
    "Vance"
    }

    by lazy 和 lateinit 的区别:

    1. by lazy 修饰 val 的变量
    2. lateinit 修饰 var 的变量,且变量是非空的类型
  2. object

    可以用来声明单例对象、伴生对象,匿名对象

    生对象

    • 每个类可以最多有一个半生对象
    • 伴生对象的成员类似于 Java 的静态成员
    • 使用 const 关键字修饰常量,类似于 Java 中的 static final 修饰
    • 可以使用 @JvmField 和 @JvmStatic 类似于 Java 中调用静态属性和静态方法
    • 伴生对象可以扩展属性和扩展方法
  3. lateinit
    修饰变量,表示该对象延迟初始化,不用判 null。

  4. sealed
    密封类。

  5. operator
    运算符重载。

  6. internal
    饰类和方法 限制不同 module 的访问。

  7. inner
    只能用来修饰内部类。

  8. inline
    内联函数。

  9. noinline

  10. crossinline
    让无法使用内联函数的方法使用内联函数。

  11. infix
    中缀表达式。

    1. 必须是成员函数或扩展函数;
    2. 必须只有一个参数;
    3. 其参数不得接受可变数量的参数且不能有默认值。
    1
    2
    3
    4
    5
    6
    7
    infix fun String.begin(prefix: String): Boolean = startsWith(prefix)
    val result = "Hello" begin "A"

    public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY
    return this .. (to - 1).toInt()
    }
  12. by
    类或属性委托。

注解

  1. @JvmOverloads

    生成重载,如果你写一个有默认参数值的 Kotlin 函数,在 Java 中只会有一个所有参数都存在的完整参数签名的方法可见,如果希望向 Java 调用者暴露多个重载,可以使用 @JvmOverloads 注解。

  2. @JvmField

    不会为属性生成 getters/setters 方法且,变量不能用 private 修饰。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class User(id: String) {
    @JvmField
    val ID = id
    }

    // 编译成字节码后,反编译成 Java 代码
    public final class User {
    @JvmField
    @NotNull
    public final String ID;

    public User(@NotNull String id) {
    Intrinsics.checkNotNullParameter(id, "id");
    super();
    this.ID = id;
    }
    }
    // Java
    class JavaClient {
    public String getID(User user) {
    return user.ID;
    }
    }
  3. @JvmStatic
    生成静态的 setter/getter 方法。

  4. @JvmName
    指定生成的类名或方法名称。如果作用在顶级作用域(文件中),则会改变生成对应 Java 类的名称。如果作用在方法上,则会改变生成对应 Java 方法的名称。eg: @file:JvmName(“FooKt”),@JvmName(“foo1”)

  5. @JvmMultifileClass
    配合 @JvmName 使用,指定生成到同一个文件中。

  6. @JvmOverloads
    生成重载方法,供 Java 调用。

  7. @Throws
    由于 Kotlin 语言不支持 CE(Checked Exception),而 Java 语言通过 throws 关键字在方法上声明 CE。

  8. @Synchronized  @Volatile  @Transient
    分别对应 Java 中的 Synchronized volatile transient 关键字。

  9. @JvmWildcard
    用于处理泛型参数。

  10. @JvmSuppressWildcards
    用来抑制通配符泛型参数的生成,即在不需要型变泛型参数的情况下,我们可以通过添加这个注解来避免生成型变泛型参数。

  11. @JvmDefault
    用于在接口中处理默认实现的方法。