[关闭]
@TryLoveCatch 2024-03-23T02:19:13.000000Z 字数 23237 阅读 220

Android知识体系之Kotlin

Android知识体系 kotlin


return跳转

  1. fun fun5() {
  2. listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
  3. if (value == 3) {
  4. return
  5. }
  6. println("value is $value")
  7. })
  8. println("function end")
  9. }
  10. fun fun6() {
  11. listOf(1, 2, 3, 4, 5).forEach {
  12. if (it == 3) {
  13. return
  14. }
  15. println("value is $it")
  16. }
  17. println("function end")
  18. }
  19. // 结果
  20. // =====fun5======
  21. // value is 1
  22. // value is 2
  23. // value is 4
  24. // value is 5
  25. // function end
  26. // =====fun6=====
  27. // value is 1
  28. // value is 2

数据类

  1. data class Person(val name: String, val age: Int)
  2. class Point(val x : Int, val y : Int) {
  3. operator fun component1() = x
  4. operator fun component2() = y
  5. }

对于数据类来说,其自动生成了 componentN() 函数,而对非数据类,为了使用解构声明,需要我们自己来手动声明函数

  1. var person1 = Person("xiaoming", 18)
  2. var person2 = Person(age = 18, name = "xiaoming")
  3. val (name, age) = person2
  4. println("$name : $age") // xiaoming:18
  5. val (age1, name1) = person2
  6. println("$name1 : $age1")// 18:xiaoming
  7. var(x, y) = Point(100, 200)
  8. println("$x , $y")// 100, 200

扩展函数

  1. class Student {
  2. //来自省份
  3. var province:String?= null
  4. //学生名字
  5. var name:String? = null
  6. init {
  7. name = "fish"
  8. province = "beijing"
  9. }
  10. fun printStudent() {
  11. println("$name")
  12. }
  13. }
  14. fun main(args: Array<String>) {
  15. var student = Student()
  16. student.printStudent()
  17. }

现在有个需求想要打印名字的同时还打印省份。
你可能会说:直接在printStudent()加入打印省份信息不就得了?
如果是第三方的文件呢?咱们没权限修改源文件,在Java 里我们一般通过包装Student类,再提供打印学生姓名和省份的方法。
而Koltin里更简洁,可以直接对这个类进行函数扩展。

  1. fun main(args: Array<String>) {
  2. var student = Student()
  3. student.printStudent1()
  4. }
  5. //扩展函数
  6. fun Student.printStudent1() {
  7. println("name:$name province:$province")
  8. }
  9. 反编译Java代码
  10. public static final void printStudent1(@NotNull Student $this$printStudent1) {
  11. Intrinsics.checkNotNullParameter($this$printStudent1, "$this$printStudent1");
  12. String var1 = "name:" + $this$printStudent1.getName() + " province:" + $this$printStudent1.getProvince();
  13. boolean var2 = false;
  14. System.out.println(var1);
  15. }

当扩展一个类的函数时,实际上传入了该类的对象,通过对象拿到属性/函数并操作。其本质上还是通过类的对象实例来组合各种操作。
假若现在将"province" 访问权限修改为"private",那么printStudent1 将无法访问到该属性。

属性委托 by

by lazy by viewModels by activityViewModes本质上都是属性委托。
lazy是函数,viewModels是函数,activityViewModels也是函数,他们返回了Delegate 对象,且这个对象必须要有 operator fun getValue operator fun setValue ,用val声明不需要 setValue.

Kotlin中属性在声明的同时也要求要被初始化,否则会报错。
例如以下代码:

  1. private var name0: String //报错
  2. private var name1: String = "xiaoming" //不报错
  3. private var name2: String? = null //不报错

可是有的时候,我并不想声明一个类型可空的对象,而且我也没办法在对象一声明的时候就为它初始化,那么这时就需要用到Kotlin提供的延迟初始化。

Kotlin中有两种延迟初始化的方式。一种是lateinit var,一种是by lazy。

  1. lateinit var
  2. private lateinit var name: String

lateinit var的作用也比较简单,就是让编译期在检查时不要因为属性变量未被初始化而报错。

  1. by lazy
  2. //用于属性延迟初始化
  3. val name: Int by lazy { 1 }
  4. //用于局部变量延迟初始化
  5. public fun foo() {
  6. val bar by lazy { "hello" }
  7. println(bar)
  8. }

总结一下,当一个属性name需要by lazy时,具体是怎么实现的:

  1. 生成一个该属性的附加属性:name?delegate;
  2. 在构造器中,将使用lazy(()->T)创建的Lazy实例对象赋值给name?delegate;
  3. 当该属性被调用,即其getter方法被调用时返回name?delegate.getVaule(),而name?delegate.getVaule()方法的返回结果是对象name?delegate内部的_value属性值,在getVaule()第一次被调用时会将_value进行初始化,往后都是直接将_value的值返回,从而实现属性值的唯一一次初始化。

内联

内联函数的本质是:把函数体复制粘贴到函数调用处

  1. inline fun test() {
  2. println("I'm a inline function")
  3. }
  4. fun run() {
  5. test()
  6. }

在 run() 函数中调用了内联函数 test()。反编译查看对应的 java 代码:

  1. public static final void test() {
  2. String var1 = "I'm a inline function";
  3. System.out.println(var1);
  4. }
  5. public static final void run() {
  6. String var1 = "I'm a inline function";
  7. System.out.println(var1);
  8. }

可以看到 run() 函数中并没有直接调用 test() 函数,而是把 test() 函数的代码直接放入自己的函数体中。这就是 inline 的功效。
高阶函数里面会详细讲到inline的使用。

noinline

当一个 inline 函数中,有多个 lambda 作为参数时,可以在不想内联的 lambda 前使用 noinline 声明.

  1. inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit, noinline lambda2: (result: Int) -> Unit): Int {
  2. val r = a + b
  3. lambda.invoke(r)
  4. lambda2.invoke(r)
  5. return r
  6. }
  7. fun main(args: Array<String>) {
  8. sum(1, 2,
  9. { println("Result is: $it") },
  10. { println("Invoke lambda2: $it") }
  11. )
  12. }

反编译 Java:

  1. public static final int sum(int a, int b, @NotNull Function1 lambda, @NotNull Function1 lambda2) {
  2. int r = a + b;
  3. lambda.invoke(r);
  4. lambda2.invoke(r);
  5. return r;
  6. }
  7. public static final void main(@NotNull String[] args) {
  8. byte a$iv = 1;
  9. byte b$iv = 2;
  10. Function1 lambda2$iv = (Function1)null.INSTANCE;
  11. int r$iv = a$iv + b$iv;
  12. String var8 = "Result is: " + r$iv;
  13. System.out.println(var8);
  14. lambda2$iv.invoke(r$iv);
  15. }

第一个 lambda 内联到了调用处,而第二个使用 noinline 声明的 lambda 没有。

crossinline

我们先看一个例子:

  1. fun main() {
  2. println("1111")
  3. sayHello {
  4. println("2222")
  5. return
  6. }
  7. println("4444")
  8. }
  9. private inline fun sayHello(block: () -> Unit) {
  10. println("3333")
  11. block()
  12. }

你猜,结果是什么呢?

  1. 1111
  2. 3333
  3. 2222

4444并没有打印,为什么会这样啊?这其实不就是inline的特性吗?看反编译代码:

  1. public static final void main() {
  2. String var0 = "1111";
  3. System.out.println(var0);
  4. int $i$f$sayHello = false;
  5. String var1 = "3333";
  6. System.out.println(var1);
  7. int var2 = false;
  8. String var3 = "2222";
  9. System.out.println(var3);
  10. // 下面这三行是我添加的,其实并没有,因为在return后面,编译器直接就给忽略了
  11. //return;
  12. //String var4 = "4444";
  13. //System.out.println(var4);
  14. }
  15. // 没用
  16. private static final void sayHello(Function0 block) {
  17. int $i$f$sayHello = 0;
  18. String var2 = "3333";
  19. System.out.println(var2);
  20. block.invoke();
  21. }

声明一个 lambda 不能有 return 语句(可以有 return@label 语句)。这样可以避免使用 inline 时,lambda 中的 return 影响程序流程。

  1. inline fun sum(a: Int, b: Int, crossinline lambda: (result: Int) -> Unit): Int {
  2. val r = a + b
  3. lambda.invoke(r)
  4. return r
  5. }
  6. fun main(args: Array<String>) {
  7. sum(1, 2) {
  8. println("Result is: $it")
  9. return // 编译错误: return is not allowed here
  10. }
  11. }

小结

JvmField&JvmStatic

JvmStatic只能在object类或者伴生对象companion object中使用

Objec关键字

Kotlin 的 object 关键字有三种用法:

对象声明

对象声明,可以直接用来实现单例模式:

  1. object Singleton{
  2. fun xxx(){}
  3. }

直接 Decompile 看 Java 代码:

  1. public final class Singleton {
  2. @NotNull
  3. public static final Singleton INSTANCE;
  4. public final void xxx() {
  5. }
  6. private Singleton() {
  7. }
  8. static {
  9. Singleton var0 = new Singleton();
  10. INSTANCE = var0;
  11. }
  12. }

从 Java 代码中可以看出来,显然这是一个单例模式。

这在实际的业务场景是有一定限制的:对于需要携带参数的单例类,object 就有点力不从心了。
当然也不难解决,模仿 Java 的写法就行了,这里以 DCL 模式为例。

  1. class Singleton private constructor(private val param: Int) {
  2. companion object {
  3. @Volatile
  4. private var instance: Singleton? = null
  5. fun getInstance(property: Int) =
  6. instance ?: synchronized(this) {
  7. instance ?: Singleton(property).also { instance = it }
  8. }
  9. }
  10. }

伴生对象

伴生对象,顾名思义,就是伴随着类而存在的对象,在类加载的时候初始化。

  1. class Book {
  2. fun test() {
  3. setApp(this)
  4. }
  5. companion object {
  6. var a = 1
  7. var b = "xiaoming"
  8. lateinit var instance: Book
  9. private set
  10. fun setApp(app: Book) {
  11. instance = app
  12. }
  13. }
  14. }
  15. // 外面使用
  16. Book.setApp(xx);
  17. Book.a;

转换成Java代码,我们看下

  1. public final class Book {
  2. private static int a = 1;
  3. @NotNull
  4. private static String b = "xiaoming";
  5. private static Book instance;
  6. @NotNull
  7. public static final Book.Companion Companion = new Book.Companion((DefaultConstructorMarker)null);
  8. public final void test() {
  9. Companion.setApp(this);
  10. }
  11. public static final class Companion {
  12. public final int getA() {
  13. return Book.a;
  14. }
  15. public final void setA(int var1) {
  16. Book.a = var1;
  17. }
  18. @NotNull
  19. public final String getB() {
  20. return Book.b;
  21. }
  22. public final void setB(@NotNull String var1) {
  23. Intrinsics.checkNotNullParameter(var1, "<set-?>");
  24. Book.b = var1;
  25. }
  26. @NotNull
  27. public final Book getInstance() {
  28. Book var10000 = Book.instance;
  29. if (var10000 == null) {
  30. Intrinsics.throwUninitializedPropertyAccessException("instance");
  31. }
  32. return var10000;
  33. }
  34. private final void setInstance(Book var1) {
  35. Book.instance = var1;
  36. }
  37. public final void setApp(Book app) {
  38. ((Book.Companion)this).setInstance(app);
  39. }
  40. private Companion() {
  41. }
  42. // $FF: synthetic method
  43. public Companion(DefaultConstructorMarker $constructor_marker) {
  44. this();
  45. }
  46. }
  47. }
  48. // 外面使用
  49. Book.Companion.setApp(xx);
  50. Book.Companion.a;

可以看出来:

了解了伴生对象的本质之后,再来说两个它的其他用法。

创建静态工厂方法

  1. enum class CarType {
  2. AUDI,BMW
  3. }
  4. interface Car {
  5. val brand: String
  6. companion object {
  7. operator fun invoke(type: CarType): Car {
  8. return when (type) {
  9. CarType.AUDI -> Audi()
  10. CarType.BMW -> BMW()
  11. }
  12. }
  13. }
  14. }
  15. class Audi : Car {
  16. override val brand: String = "audi"
  17. }
  18. class BMW : Car {
  19. override val brand: String = "bmw"
  20. }
  21. // 使用
  22. fun testCar() {
  23. Car(CarType.BMW)
  24. }

这里重载了 invoke() 方法,调用时直接 Car(CarType.BMW) 即可。
反编译之后,我们看下:

  1. // Car.java
  2. package com.arcfox.n61manual;
  3. import kotlin.Metadata;
  4. import org.jetbrains.annotations.NotNull;
  5. public interface Car {
  6. @NotNull
  7. Car.Companion Companion = Car.Companion.$$INSTANCE;
  8. @NotNull
  9. String getBrand();
  10. public static final class Companion {
  11. // $FF: synthetic field
  12. static final Car.Companion $$INSTANCE;
  13. @NotNull
  14. public final Car invoke(@NotNull CarType type) {
  15. // $FF: Couldn't be decompiled
  16. }
  17. private Companion() {
  18. }
  19. static {
  20. Car.Companion var0 = new Car.Companion();
  21. $$INSTANCE = var0;
  22. }
  23. }
  24. }
  25. // TestKt.java
  26. package com.arcfox.n61manual;
  27. import kotlin.Metadata;
  28. public final class TestKt {
  29. public static final void testCar() {
  30. Car.Companion.invoke(CarType.BMW);
  31. }
  32. }

伴生对象扩展方法

伴生对象也是支持扩展方法的。还是以上面的 Car 为例,定义一个根据汽车品牌获取汽车类型的扩展方法。

  1. fun Car.Companion.getCarType(brand:String) :CarType { ...... }

虽然是在 Car.Companion 上定义的扩展函数,但实际上相当于给 Car 增加了一个静态方法,使用方式如下:

  1. Car.getCarType("BMW")

对象表达式

对象表达式最经典的用法就是用来 代替 Java 的匿名内部类 。例如常见的点击事件:

  1. xxx.setOnClickListener(object : View.OnClickListener{
  2. override fun onClick(v: View) {
  3. }
  4. })

这和 Java 的匿名内部类是等价的。只不过像上面的单方法接口,我们很少用 object 写,而是用 lambda 代替,显得更加简洁。

  1. xxx.setOnClickListener { view -> ...... }

当匿名对象需要重写多个方法时,就只能选择对象表达式了。
和 Java 的匿名内部类一样,对象声明中也可以访问外部变量。
对象表达式应该是 object 最朴实无华的使用方式了。

匿名函数

  1. fun a(funParam: (Int) -> String): String {
  2. return funParam(1)
  3. }

要传一个函数类型的参数,或者把一个函数类型的对象赋值给变量,除了用双冒号来拿现成的函数使用,你还可以直接把这个函数挪过来写:

  1. a(fun b(param: Int): String {
  2. return param.toString()
  3. });
  4. val d = fun b(param: Int): String {
  5. return param.toString()
  6. }

另外,这种写法的话,函数的名字其实就没用了,所以你可以把它省掉:

  1. a(fun(param: Int): String {
  2. return param.toString()
  3. });
  4. val d = fun(param: Int): String {
  5. return param.toString()
  6. }

这种写法叫做匿名函数。为什么叫匿名函数?很简单,因为它没有名字呗,对吧。等号左边的不是函数的名字啊,它是变量的名字。这个变量的类型是一种函数类型,具体到我们的示例代码来说是一种只有一个参数、参数类型是 Int、并且返回值类型为 String 的函数类型。
另外呢,其实刚才那种左边右边都有名字的写法,Kotlin 是不允许的。右边的函数既然要名字也没有用,Kotlin 干脆就不许它有名字了。

所以,如果你在 Java 里设计一个回调的时候是这么设计的:

  1. public interface OnClickListener {
  2. void onClick(View v);
  3. }
  4. public void setOnClickListener(OnClickListener listener) {
  5. this.listener = listener;
  6. }

使用的时候是这么用的:

  1. view.setOnClickListener(new OnClickListener() {
  2. @Override
  3. void onClick(View v) {
  4. switchToNextPage();
  5. }
  6. });

到了 Kotlin 里就可以改成这么写了:

  1. fun setOnClickListener(onClick: (View) -> Unit) {
  2. this.onClick = onClick
  3. }
  4. view.setOnClickListener(fun(v: View): Unit) {
  5. switchToNextPage()
  6. })

简单一点哈?另外大多数(几乎所有)情况下,匿名函数还能更简化一点,写成 Lambda 表达式的形式:

  1. view.setOnClickListener({ v: View ->
  2. switchToNextPage()
  3. })
  4. // 当然可以继续简化
  5. view.setOnClickListener { v: View ->
  6. switchToNextPage()
  7. }
  8. // 继续简化
  9. view.setOnClickListener {
  10. switchToNextPage()
  11. }

终于讲到 Lambda 了。

Lambda

Lambda 表达式本质上就是可以传递给其它函数的一小段代码,通过 Lambda 表达式可以把通用的代码结构抽取成库函数,也可以把 Lambda 表达式存储在一个变量中,把这个变量当做普通函数对待
//由于存在类型推导,所以以下三种声明方式都是完全相同的

  1. val plus1: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
  2. val plus2: (Int, Int) -> Int = { x, y -> x + y }
  3. val plus3 = { x: Int, y: Int -> x + y }
  4. println(plus3(1, 2))
  1. 一个 Lambda 表达式始终用花括号包围,通过箭头把实参列表和函数体分开
  2. 如果 Lambda 声明了函数类型,那么就可以省略函数体的类型声明
  3. 如果 Lambda 声明了参数类型,且返回值支持类型推导,那么就可以省略函数类型声明

Lambda 表达式最常见的用途就是和集合一起工作,看以下例子
要从一个人员列表中取出年龄最大的一位

  1. data class Person(val name: String, val age: Int)
  2. fun main() {
  3. val people = listOf(Person("leavesC", 24), Person("Ye", 22))
  4. println(people.maxBy { it.age }) //Person(name=leavesC, age=24)
  5. }

当中,库函数 maxBy 可以在任何集合上调用,其需要一个实参:一个函数,用于指定要用来进行比较的函数。花括号中的代码 { it.age } 就是实现了这个逻辑的 Lambda 表达式

上述 maxBy 函数的实参是简化后的写法,这里来看下 maxBy 函数的简化过程

  1. - 最原始的语法声明应该是这样的,用括号包裹着 Lambda 表达式
  2. println(people.maxBy({ p: Person -> p.age }))
  3. - kotlin 有一种语法约定,如果 Lambda 表达式是函数调用的最后一个实参,可以将之放到括号的外边
  4. println(people.maxBy() { p: Person -> p.age })
  5. - Lamdba 表达式是函数唯一的实参时,可以去掉调用代码中的空括号对
  6. println(people.maxBy { p: Person -> p.age })
  7. - Lambda 表达式的参数类型是可以被推导出来时就可以省略声明参数类型
  8. println(people.maxBy { p -> p.age })
  9. - 如果当前上下文期待的是只有一个参数的 Lambda 表达式且参数类型可以被推断出来,就会为该参数生成一个默认名称:it
  10. println(people.maxBy { it.age })

匿名函数和Lambda的本质

泛型

https://juejin.cn/post/6935322686943920159

  1. //泛型类
  2. class A<T> {}
  3. class Plate<T : Fruit>(val t: T)
  4. class Plate<T>(val t: T) where T : Fruit, T : Soft
  5. //泛型接口
  6. interface B<T>{}
  7. //泛型方法
  8. fun <T> pick(a : T) {}

Java 的类型通配符 ? 对应 Kotlin 中的概念就是 *

- 协变 逆变 不变
Kotlin <out T>,只能作为消费者,只能读取不能添加 <in T>,只能作为生产者,只能添加,读取出的值只能当做 Any 类型 <T>,既可以添加也可以读取
Java <? extends T>,只能作为消费者,只能读取不能添加 <? super T>,只能作为生产者,只能添加,读取出的值只能当做 Object 类型 <T>,既可以添加也可以读取

高阶函数

定义

首先来看一下高阶函数的定义。如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。
这个定义可能有点不太好理解,一个函数怎么能接收另一个函数作为参数呢?这就涉及另外一个概念了:函数类型。我们知道,编程语言中有整型、布尔型等字段类型,而 Kotlin 又增加了一个函数类型的概念。如果我们将这种函数类型添加到一个函数的参数声明或者返回值声明当中,那么这就是一个高阶函数了。

函数类型的语法规则如下:

  1. (String, Int) -> Unit
  1. fun example(func: (String, Int) -> Unit) {
  2. func("xiaoming", 18)
  3. }
  4. fun main() {
  5. example({name: String, age: Int ->
  6. println("=====")
  7. println("name: $name, age: $age")
  8. })
  9. example { name, age ->
  10. println("=====")
  11. println("name: $name, age: $age")
  12. }
  13. }

使用

那高阶函数具体有什么用途呢?由于高阶函数的用途实在是太广泛了,这里如果要让我简单概括一下的话,那就是高阶函数允许让函数类型的参数来决定函数的执行逻辑。
即使是同一个高阶函数,只要传入不同的函数类型参数,那么它的执行逻辑和最终的返回结果就可能是完全不同的。
咱们再看一个例子

  1. fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
  2. return operation(num1, num2)
  3. }

定义了一个函数,三个参数,两个int类型,一个函数类型(参数为两个int,返回值为int);
对传入的两个整型参数进行某种运算,并返回最终的运算结果,但是具体进行什么运算是由传入的函数类型参数决定的。

  1. fun plus(num1: Int, num2: Int): Int {
  2. return num1 + num2
  3. }
  4. fun minus(num1: Int, num2: Int): Int {
  5. return num1 - num2
  6. }
  7. fun main() {
  8. val num1 = 100
  9. val num2 = 80
  10. val result1 = num1AndNum2(num1, num2, ::plus)
  11. val result2 = num1AndNum2(num1, num2, ::minus)
  12. println("result1 is $result1")
  13. println("result2 is $result2")
  14. }

::plus 和 ::minus 这种写法,是函数引用方式的写法,表示将 plus() 和 minus() 函数作为参数传递给 num1AndNum2() 函数。

使用这种函数引用的写法虽然能够正常工作,但是如果每次调用任何高阶函数的时候都还得先定义一个与其函数类型参数相匹配的函数,这是不是有些太复杂了?
没错,因此 Kotlin 还支持其他多种方式来调用高阶函数,比如 Lambda 表达式、匿名函数、成员引用等。其中,Lambda 表达式是最常见也是最普遍的高阶函数调用方式,也是我们接下来要重点学习的内容。
上述代码如果使用 Lambda 表达式的写法来实现的话,代码如下所示:

  1. fun main() {
  2. println(num1AndNum2(10, 20, ::plus))
  3. val result = num1AndNum2(10, 6) { num1, num2 ->
  4. num1 - num2
  5. }
  6. println(result)
  7. }

为什么系统的高阶函数都需要加上inline?

kotlin在Standard.kt标准库中提供了一些便捷的内置高阶函数( let、also、with、run、apply ),可以帮助我们写出更简洁优雅的 Kotlin 代码,提高开发效率,但是我们看这些高阶函数的时候,会发现他们都被inline修饰。

  1. public inline fun <R> run(block: () -> R): R {
  2. contract {
  3. callsInPlace(block, InvocationKind.EXACTLY_ONCE)
  4. }
  5. return block()
  6. }
  7. @kotlin.internal.InlineOnly
  8. public inline fun <T, R> T.run(block: T.() -> R): R {
  9. contract {
  10. callsInPlace(block, InvocationKind.EXACTLY_ONCE)
  11. }
  12. return block()
  13. }

为什么这样呢?咱们还以上面的例子来说明:

  1. fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
  2. val result = operation(num1, num2)
  3. return result
  4. }
  5. fun main() {
  6. val num1 = 100
  7. val num2 = 80
  8. val result = num1AndNum2(num1, num2) { n1, n2 ->
  9. n1 + n2
  10. }
  11. }

转换为java代码:

  1. public static int num1AndNum2(int num1, int num2, Function operation) {
  2. int result = (int) operation.invoke(num1, num2);
  3. return result;
  4. }
  5. public static void main() {
  6. int num1 = 100;
  7. int num2 = 80;
  8. int result = num1AndNum2(num1, num2, new Function() {
  9. @Override
  10. public Integer invoke(Integer n1, Integer n2) {
  11. return n1 + n2;
  12. }
  13. });
  14. }

在这里 num1AndNum2() 函数的第三个参数变成了一个 Function 接口,这是一种 Kotlin 内置的接口,里面有一个待实现的 invoke() 函数。而 num1AndNum2() 函数其实就是调用了 Function 接口的 invoke() 函数,并把 num1 和 num2 参数传了进去。

这就是 Kotlin 高阶函数背后的实现原理。你会发现,原来我们一直使用的 Lambda 表达式在底层被转换成了匿名类的实现方式。这就表明,我们每调用一次 Lambda 表达式,都会创建一个新的匿名类实例,当然也会造成额外的内存和性能开销。

而为了解决这个问题,Kotlin 提供了内联函数的功能,它可以将使用 Lambda 表达式带来的运行时开销完全消除。
内联函数的用法非常简单,只需要在定义高阶函数时加上 inline 关键字的声明即可,如下所示:

  1. inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
  2. val result = operation(num1, num2)
  3. return result
  4. }

那么内联函数的工作原理又是什么呢?其实并不复杂,就是 Kotlin 编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。

我们用图例来说明一下:
下图是原始写法,我们继续看看编译器都做了什么
[图片]

第一步,将 上图中Lambda 表达式中的代码替换到函数类型参数调用的地方,变成了下图所示的样子
[图片]
第二步,将内联函数中的全部代码替换到函数调用的地方,也就变成了下图的样子
[图片]

也正是如此,内联函数才能完全消除 Lambda 表达式所带来的运行时开销。

Standard.kt

为了方便解释,我们先定义一个Student类

  1. class StudentInfo {
  2. //姓名
  3. var name:String? = "Fish"
  4. var alias:String ? = "小鱼人"
  5. //省份
  6. var province:String? = "北京"
  7. //年龄
  8. var age:Int ? = 18
  9. //性别
  10. var isBoy:Boolean = true
  11. //分数
  12. var score:Float = 88f
  13. }

let

  1. public inline fun <T, R> T.let(block: (T) -> R): R {
  2. //内联函数,扩展函数,定义了泛型,接收一个函数类型参数
  3. //调用函数,返回执行结果
  4. return block(this)
  5. }

定义了泛型T,let 作为T的扩展函数,let 的参数为函数类型,接收T对象,返回R,返回值也可以为Unit。

  1. // 不使用let
  2. fun testLet1(studentInfo: StudentInfo) {
  3. studentInfo?.isBoy = false
  4. studentInfo?.name = "小鱼人"
  5. studentInfo?.age = 14
  6. }
  7. // 使用let
  8. fun testLet2(studentInfo: StudentInfo) {
  9. var letRet = studentInfo?.let {
  10. it.isBoy = false
  11. it.name = "小鱼人"
  12. it.age = 14
  13. //Lambda结果作为let 返回值
  14. "111"
  15. }
  16. println("$letRet, ${studentInfo.name}")// 111,小鱼人
  17. }
  18. // 也可以没有返回值,就是Unit
  19. fun testLet2(studentInfo: StudentInfo) {
  20. var letRet = studentInfo?.let {
  21. it.isBoy = false
  22. it.name = "小鱼人"
  23. it.age = 14
  24. }
  25. println("$letRet, ${studentInfo.name}")// Kotlin.Unit,小鱼人
  26. }

作用:

also

  1. public inline fun <T> T.also(block: (T) -> Unit): T {
  2. //扩展函数,函数类型入参为T对象,返回T对象
  3. block(this)
  4. //返回调用者本身
  5. return this
  6. }

与let 类似,只是返回值有点差异。

  1. // 不使用also
  2. fun testAlso1(studentInfo: StudentInfo) {
  3. studentInfo?.isBoy = false
  4. studentInfo?.name = "小鱼人"
  5. studentInfo?.age = 14
  6. }
  7. // 使用also
  8. fun testAlso2(studentInfo: StudentInfo) {
  9. var letRet = studentInfo?.also {
  10. it.isBoy = false
  11. it.name = "小鱼人"
  12. it.age = 14
  13. //Lambda结果未被使用
  14. "Fish"
  15. }
  16. println("alsoRet:${letRet.name}")
  17. }

also 返回值为调用者本身,也就是studentInfo,因此我们可以继续使用studentInfo进行操作。

  1. fun testAlso3(studentInfo: StudentInfo) {
  2. studentInfo?.also {
  3. it.isBoy = false
  4. it?.name = "小鱼人"
  5. it?.age = 14
  6. //Lambda结果作为let 返回值
  7. "Fish"
  8. }.let {
  9. //继续调用
  10. it.score = 99f
  11. }
  12. }

作用:
also 原理、作用与let 类似,因为其返回对象本身,因此可以用在链式调用的场景。

with

  1. public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
  2. //内联函数,带两个参数,一个是接收者,另一个函数类型
  3. //T.() 泛型T的扩展函数,函数无参数
  4. //返回接收者调用结果,也就是Lambda返回值
  5. return receiver.block()
  6. }

可以理解为,给receiver增加了一个扩展函数block,并且执行了它。
需要注意的是:with 并不是扩展函数,因此无需使用对象访问它。
with 反编译结果:

  1. public static final Object with(Object receiver, @NotNull Function1 block) {
  2. //传入接收者对象
  3. return block.invoke(receiver);
  4. }

T.() 表示T的扩展函数,可以表示为:block:T.()->R,可以使用receiver.block() 访问,最终实现block的函数体(Lambda)里持有T的对象,因此内部可以使用this访问T的属性和函数。

  1. // 不使用with
  2. fun testWith1(studentInfo: StudentInfo) {
  3. studentInfo?.isBoy = false
  4. studentInfo?.name = "小鱼人"
  5. studentInfo?.age = 14
  6. }
  7. // 使用with
  8. fun testWith2(studentInfo: StudentInfo) {
  9. var withRet = with(studentInfo) {
  10. isBoy = false
  11. name = "小鱼人"
  12. age = 14
  13. "Fish"
  14. }
  15. println("withRet:$withRet")
  16. }

可以看出,使用with时只需要传入要操作的对象,而Lambda表达式里即可直接操作属性和函数。
with 本质上是通过扩展接收者函数,内部就可以访问属性和函数(隐藏了this),通常用在需要多次书写对象调用的场景,比如ViewHolder 访问View。
with 有个弊端:
因为不是扩展函数,因此无法像let/also 一样在调用时通过"?"判空。

run

  1. public inline fun <T, R> T.run(block: T.() -> R): R {
  2. //扩展函数,函数类型也扩展了T
  3. return block()
  4. }

run与with 类似,同样的是扩展T.(),因此在block里能够访问属性和函数

  1. // 不使用run
  2. fun testRun1(studentInfo: StudentInfo) {
  3. studentInfo?.isBoy = false
  4. studentInfo?.name = "小鱼人"
  5. studentInfo?.age = 14
  6. }
  7. // 使用run
  8. fun testRun2(studentInfo: StudentInfo) {
  9. var withRet = studentInfo?.run {
  10. isBoy = false
  11. name = "小鱼人"
  12. age = 14
  13. "Fish"
  14. }
  15. println("withRet:$withRet")
  16. }

可以看出,run 比 with 多了可以判空的功能,并且比let 多了可以省略it访问的功能,因此:
run 结合了let 与 with 的功能,它俩能做的run 也能做。

apply

  1. public inline fun <T> T.apply(block: T.() -> Unit): T {
  2. //扩展函数
  3. block()
  4. //返回调用者本身
  5. return this
  6. }

和run 相似,只是apply 返回值为对象本身,并且block无返回值。

  1. // 不使用apply
  2. fun testApply1() {
  3. var studentInfo = StudentInfo()
  4. studentInfo.isBoy = false
  5. studentInfo.name = "小鱼人"
  6. studentInfo.age = 14
  7. }
  8. // 使用apply
  9. fun testApply2() {
  10. var applyRet = StudentInfo().apply {
  11. isBoy = false
  12. name = "小鱼人"
  13. age = 14
  14. "Fish"
  15. }
  16. println("withRet:${applyRet.name}")
  17. }

apply 返回对象本身,因此我们可以在Lambda里做一些初始化操作。
当Lambda 执行完毕后,返回的对象已经初始化完毕。
apply 多用于对象初始化过程以及链式调用。

repeat

  1. public inline fun repeat(times: Int, action: (Int) -> Unit) {
  2. //循环调用action,传入参数为当前次数
  3. for (index in 0 until times) {
  4. action(index)
  5. }
  6. }

repeat 不是扩展函数。

  1. fun testRepeat2() {
  2. var list = mutableListOf<StudentInfo>()
  3. repeat(10) {
  4. //重复这个动作10次
  5. list.add(StudentInfo())
  6. println("第 $it 个")
  7. }
  8. }

::

函数类型

我们讲高阶函数的时候,其实用过,如下面例子:

  1. fun plus(num1: Int, num2: Int): Int {
  2. return num1 + num2
  3. }
  4. fun minus(num1: Int, num2: Int): Int {
  5. return num1 - num2
  6. }
  7. fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
  8. return operation(num1, num2)
  9. }
  10. fun main() {
  11. val num1 = 100
  12. val num2 = 80
  13. val result1 = num1AndNum2(num1, num2, ::plus)
  14. val result2 = num1AndNum2(num1, num2, ::minus)
  15. println("result1 is $result1")
  16. println("result2 is $result2")
  17. }

表示将 plus() 和 minus() 函数作为参数传递给 num1AndNum2() 函数。
想把一个函数赋值给一个变量,也得使用::,如下所示:

  1. var fun1 = ::plus
  2. val result = fun1(10,20)// 30

加了两个冒号,这个函数才变成了一个对象。

databinding

比如我定义了响应点击事件的函数:

  1. //注意保持参数类型的一致
  2. fun onClick(v: View) {
  3. ...
  4. }

在 xml 中绑定该事件,以往我们的做法一般是:

  1. <Button...onClick="@{(v) -> data.onClick(v)}"/>

使用 :: 来绑定可以更加简洁:

  1. <Button...onClick="@{data::onClick}"/>

反射

直接使用泛型 T:: 即可反射获得其内部属性,如 class,constructor 等。

  1. // kotlin
  2. Student::class
  3. // java
  4. Student.class

reified

https://juejin.cn/post/6844903833596854279
https://www.jianshu.com/p/f64c8de76375

reified:使抽象的东西更加具体或真实,非常推荐 Android 开发使用这个关键字。
用关键字 inline 标记的函数就称为内联函数,再用 reified 关键字修饰内联函数中的泛型形参,编译器在进行编译的时候便会将内联函数的字节码插入到每一个调用的地方,当中就包括泛型的类型实参。而内联函数的类型形参能够被实化,就意味着我们可以在运行时引用实际的类型实参了。

不再需要传参数 clazz

大部分的文章讲解 reified 的使用,都有提到这个点,比如我们定义实现一个扩展函数启动 Activity,一般都需要传 Class 参数:

  1. // Function
  2. private fun <T : Activity> Activity.startActivity(context: Context, clazz: Class<T>) {
  3. startActivity(Intent(context, clazz))
  4. }
  5. // Caller
  6. startActivity(context, NewActivity::class.java)

reified 方式

使用 reified,通过添加类型传递简化泛型参数

  1. // Function
  2. inline fun <reified T : Activity> Activity.startActivity(context: Context) {
  3. startActivity(Intent(context, T::class.java))
  4. }
  5. // Caller
  6. startActivity<NewActivity>(context)

不安全的转换

Kotlin 中, 使用安全转换操作符 as?,它可以在失败时返回 null。实现如下函数,我们认为会安全地获取数据或返回 null

  1. // Function
  2. fun <T> Bundle.getDataOrNull(): T? {
  3. return getSerializable(DATA_KEY) as? T
  4. }
  5. // Caller
  6. val bundle: Bundle? = Bundle()
  7. bundle?.putSerializable(DATA_KEY, "Testing")
  8. val strData: String? = bundle?.getDataOrNull()
  9. val intData: Int? = bundle?.getDataOrNull() // Crash

然而,如果获得的数据不是它期望的类型,这个函数会出现 crash。 因此为了安全获取数据,修改之前的函数如下:

  1. // Function
  2. fun <T> Bundle.getDataOrNull(clazz: Class<T>): T? {
  3. val data = getSerializable(DATA_KEY)
  4. return if (clazz.isInstance(data)) {
  5. data as T
  6. } else {
  7. null
  8. }
  9. }
  10. // Caller
  11. val bundle: Bundle? = Bundle()
  12. bundle?.putSerializable(DATA_KEY, "Testing")
  13. val strData: String? = bundle?.getDataOrNull(String::class.java)
  14. val intData: Int? = bundle?.getDataOrNull(String::class.java) // Null

这种写法不太友好,不仅在实现函数的方式上,而且还需要传递额外的 clazz 参数。

reified 方式

使用 reified,简化泛型参数和保证 as? 类型转换安全性

  1. // Function
  2. private inline fun <reified T> Bundle.getDataOrNull(): T? {
  3. return getSerializable(DATA_KEY) as? T
  4. }
  5. // Caller
  6. val bundle: Bundle? = Bundle()
  7. bundle?.putSerializable(DATA_KEY, "Testing")
  8. val strData: String? = bundle?.getDataOrNull()
  9. val intData: Int? = bundle?.getDataOrNull() // Null

不同的返回类型函数重载

实现一个函数计算 DP 到像素,并返回一个 Int 或 Float。这种情况就会想到函数重载,如下所示:

  1. fun Resources.dpToPx(value: Int): Float {
  2. return TypedValue.applyDimension(
  3. TypedValue.COMPLEX_UNIT_DIP,
  4. value.toFloat(), displayMetrics)
  5. }
  6. fun Resources.dpToPx(value: Int): Int {
  7. val floatValue: Float = dpToPx(value)
  8. return floatValue.toInt()
  9. }

但是,这将导致编译时出错。原因是,函数重载方式只能根据参数计数和类型不同,而不能根据返回类型。

reified 方式

使用 reified,可以实现不同的返回类型函数重载

  1. inline fun <reified T> Resources.dpToPx(value: Int): T {
  2. val result = TypedValue.applyDimension(
  3. TypedValue.COMPLEX_UNIT_DIP,
  4. value.toFloat(), displayMetrics)
  5. return when (T::class) {
  6. Float::class -> result as TInt::class -> result.toInt() as T
  7. else -> throw IllegalStateException("Type not supported")
  8. }
  9. }
  10. // Caller
  11. val intValue: Int = resource.dpToPx(64)
  12. val floatValue: Float = resource.dpToPx(64)

协程

https://juejin.cn/post/6908271959381901325
https://rengwuxian.com/kotlin-coroutines-2/
https://juejin.cn/post/6987724340775108622
Kotlin Jetpack 实战 | 09. 图解协程原理
Kotlin:玩转协程

查询用户信息 --> 查找该用户的好友列表 -->拿到好友列表后,查找该好友的动态

  1. getUserInfo(new CallBack() {
  2. @Override
  3. public void onSuccess(String user) {
  4. if (user != null) {
  5. System.out.println(user);
  6. getFriendList(user, new CallBack() {
  7. @Override
  8. public void onSuccess(String friendList) {
  9. if (friendList != null) {
  10. System.out.println(friendList);
  11. getFeedList(friendList, new CallBack() {
  12. @Override
  13. public void onSuccess(String feed) {
  14. if (feed != null) {
  15. System.out.println(feed);
  16. }
  17. }
  18. });
  19. }
  20. }
  21. });
  22. }
  23. }
  24. });

有点恶心了,是不是?这还是仅包含 onSuccess 的情况,实际情况会更复杂,因为我们还要处理异常,处理重试,处理线程调度,甚至还可能涉及多线程同步。
上面的代码用协程应该写?很简单,核心就是三行代码:

  1. val user = getUserInfo()
  2. val friendList = getFriendList(user)
  3. val feedList = getFeedList(friendList)

是不是简洁到了极致?这就是 Kotlin 协程的魅力:以同步的方式完成异步任务。

https://blog.csdn.net/sange77/article/details/102536792

DSL

https://juejin.cn/post/6844903861610627079

retrofit

https://juejin.cn/post/6844904148521992205
https://juejin.cn/post/6844903861610610696

工具

Kotlin转Java

这个很重要,通过这个Android studio 自带的工具,我们可以轻松掌握 Kotlin 各种语法的本质。
[图片]
[图片]
[图片]

参考

https://developer.android.com/kotlin/learn?hl=zh-cn
https://juejin.cn/post/6880602489297895438
https://juejin.cn/post/6844904201353429006
https://juejin.cn/post/6844904184911757325
https://juejin.cn/post/6844904179505299469
https://juejin.cn/post/6844904038589267982
https://juejin.cn/post/6844903607817469966
https://segmentfault.com/a/1190000040402706
https://blog.csdn.net/wekajava/article/details/124917198
https://juejin.cn/post/6844903506420187149
https://rengwuxian.com/kotlin-lambda/
https://rengwuxian.com/kotlin-source-noinline-crossinline/
https://rengwuxian.com/kotlin-coroutines-2/

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注