From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
阅读原文时间:2023年07月10日阅读:5

From Java To Kotlin, 空安全、扩展、函数、Lambda

概述(Summarize)

  • • Kotlin 是什么?

  • • 可以做什么?

  • • Android 官方开发语言从Java变为Kotlin,Java 有哪些问题?

  • • Kotlin的优点

  • • Kotlin 特性(Features)


Kotlin 出自于捷克一家软件研发公司 JetBrains ,这家公司开发出很多优秀的 IDE,如 IntelliJ IDEA、DataGrip 等都是它的杰作,包括 Google 官方的 Android IDE -- Android Studio ,也是 IntelliJ IDEA 的插件版。

Kotlin 源于 JetBrains 的圣彼得堡团队,名称取自圣彼得堡附近的一个小岛 ( Kotlin Island ) ,和 Java一样用岛屿命名,JetBrains 在 2010 年首次推出 Kotlin 编程语言,并在次年将之开源。

  • • Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift。

  • • Kotlin 可以编译成Java字节码。也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。

  • • 在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言,替代 Java 语言


Kotlin 代码会被编译成Java字节码,所以和 Java 兼容 


  • • Android

  • • Server-side

  • • Multiplatform Mobile

    Kotlin Multiplatform Mobile is in Beta!

  • • Multiplatform libraries

    Create a multiPlatform library for JVM, JS, and Native platforms.

     可以做很多方向的开发!


  • • 空引用(Null references):Java 中的 null 值是经常导致程序运行出错的原因之一,因为 Java 不支持空安全。

  • • 更少的函数式编程特性:Java 语言在函数式编程方面的支持相对较弱,虽然 Java 8 引入了 Lambda 表达式和 Stream API,但是 Kotlin 语言在这方面的支持更加全面和友好。

  • • 不够灵活,缺乏扩展能力:我们不能给 第三方 SDK 中的classes 或者 interfaces 增加新的方法。。

  • • 语法繁琐,不够简洁:Java 语言比 Kotlin 语言更为冗长,需要写更多的代码来完成相同的任务,这可能会降低开发效率。

Modern, concise and safe programming language

  • • 简约:使用一行代码创建一个包含 getters、 setters、 equals()、 hashCode()、 toString() 以及 copy() 的 POJO:

  • • 安全:彻底告别那些烦人的 NullPointerException

  • • 互操作性: Kotlin 可以与 Java 混合编程,Kotlin 和 Java 可以相互调用,目标是 100% 兼容。


  • • 空安全(Null safety)

  • • 类型推断(Type inference)

  • • 数据类 (Data classes)

  • • 扩展函数 (Extension functions)

  • • 智能转换(Smart casts)

  • • 字符串模板(String templates)

  • • 单例(Singletons)

  • • 函数类型 (Function Type )

  • • Lambda 表达式

  • • 高阶函数(Primary constructors)

  • • 函数字面量和内联函数(Function literals & inline functions)

  • • 类委托(Class delegation)

  • • 等等……


基本语法 (Basic Syntax )

  • • 变量(Variables)

  • • 基本数据类型( Basic Data Type )

  • • 空安全(Null Safety )

  • • 函数声明( Define Function )

  • • 让函数更好的调用( Making functions easier to call )

  • • 命名参数/具名参数 (Named arguments)

  • • 参数默认值(Default arguments)


在 Java/C 当中,如果我们要声明变量,我们必须要声明它的类型,后面跟着变量的名称和对应的值,然后以分号结尾。就像这样:

Integer price = 100;

而 Kotlin 则不一样,我们要使用val或者是var这样的关键字作为开头,后面跟“变量名称”,接着是“变量类型”和“赋值语句”,最后是分号结尾。就像这样:

/*
关键字     变量类型
 ↓          ↓           */
var price: Int = 100;   /*
     ↑            ↑
   变量名        变量值   */

在 Kotlin 里面,代码末尾的分号省略不写,就像这样:

var price = 100 // 默认推导类型为: Int

另外,由于 Kotlin 支持类型推导,大部分情况下,我们的变量类型可以省略不写,就像这样:

var price = 100 // 默认推导类型为: Int

var 声明的变量,我们叫做可变变量,它对应 Java 里的普通变量。

val 声明的变量,我们叫做只读变量,它相当于 Java 里面的 final 变量。

var price = 100
price = 101

val num = 1
num = 2 // 编译器报错

var, val 反编译成 Java :


我们已经知道了 val 属性只有 getter,只能保证引用不变,不能保证内容不变。例如,下面的代码:

class PersonZ {
    var name = "zhang"
    var age = 30
    val nickname: String
        get() {
            return if (age > 30) "laozhang" else "xiaozhang"
        }
    fun grow() {
        age += 1
    }

属性 nickname 的值并非不可变,当调用 grow() 方法时,它的值会从 "xiaozhang" 变为 "laozhang",

不过因为没有 setter,所以无法直接给 nickname 赋值

编译时常量

const 只能修饰没有自定义 getter 的 val 属性,而且它的值必须在编译时确定

val time = System.currentTimeMillis()
// 这种会报错
const val constTime = System.currentTimeMillis()

Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。

类型

位宽度

备注

Double

64

Kotlin 没有 double

Float

32

Kotlin 没有 float

Long

64

Kotlin 没有 long

Int

32

Kotlin 没有 int/Intege

Short

16

Kotlin 没有 short

Byte

8

Kotlin 没有 byte

在 Kotlin 语言体系当中,是没有原始类型这个概念的。这也就意味着,在 Kotlin 里,一切都是对象。


既然 Kotlin 中的一切都是对象,那么对象就有可能为空。如果我写这样的代码:

val i: Double = null // 编译器报错

以上的代码并不能通过 Kotlin 编译。 

这是因为 Kotlin 强制要求开发者在定义变量的时候,指定这个变量是否可能为 null

对于可能为 null 的变量,我们需要在声明的时候,在变量类型后面加一个问号“?”:

val i: Double = null // 编译器报错
val j: Double? = null // 编译通过

并且由于 Kotlin 对可能为空的变量类型做了强制区分,这就意味着,“可能为空的变量”无法直接赋值给“不可为空的变量”,反过来 “不可为空的变量” 可以赋值给“可能为空的变量” 。

var i: Double = 1.0
var j: Double? = null

i = j  // 编译器报错
j = i  // 编译通过

这么设计的原因是,从集合逻辑上:可能为空 包含 不可为空

而如果我们实在有这样的需求,也不难实现,只要做个判断即可:

var i: Double = 1.0
val j: Double? = null

if (j != null) {
    i = j  // 编译通过
}






在 Kotlin 当中,函数的声明与 Java 不太一样。 Java:

   public String helloFunction(@NotNull String name) {
      return "Hello " + name + " !";
   }

Kotlin :

/*
关键字    函数名          参数类型   返回值类型
 ↓        ↓                ↓       ↓      */
fun helloFunction(name: String): String {
    return "Hello $name !"
}/*   ↑
   花括号内为:函数体
*/
  • • 使用了 fun 关键字来定义函数;

  • • 返回值类型,紧跟在参数的后面,这点和 Java 不一样。


如果函数体中只有一行代码,可以简写

  • • return可以省略

  • • { } 花括号可以省略

  • • 直接用 = 连接,变成一种类似 变量赋值的 函数形式

    fun helloFunton(name:String):String = "Hello $name !"

我们称之为单表达式函数

由于Kotlin支持类型推导,返回值类型可以省略:

fun helloFunton(name:String):= "Hello $name !"

这样看起来就更简洁了。


命名参数/具名参数 (Named arguments)

以前面的函数为例子,我们调用它:

helloFunction("Kotlin")

和 Java 一样。

不过,Kotlin 提供了一些新的特性,如命名函数参数 举个例子,现在有一个函数:

fun createUser(
    name: String,
    age: Int,
    gender: Int,
    friendCount: Int,
    feedCount: Int,
    likeCount: Long,
    commentCount: Int
) {
    //..
}

如果像 Java 那样调用:

createUser("Tom", 30, 1, 78, 2093, 10937, 3285)

就要严格按照参数顺序传参:

  • • 参数顺序调换,参数就传错了,不好维护

  • • 当参数是一堆数字,很难知道数字对应的形参,可读性不高

Kotlin 参数调用:

createUser(
    name = "Tom",
    age = 30,
    gender = 1,
    friendCount = 78,
    feedCount = 2093,
    likeCount = 10937,
    commentCount = 3285
)

我们把函数的形参加了进来,形参和实参用 = 连接,建立了两者的对应关系。这样可读性更强。

如果想修改某个参数例如feedCount也可以很方便的定位到参数。 这样易维护


参数默认值(Default arguments)

fun createUser(
    name: String,
    age: Int,
    gender: Int = 1,
    friendCount: Int = 0,
    feedCount: Int = 0,
    likeCount: Long = 0L,
    commentCount: Int = 0
) {
    //..
}

gender、likeCount 等参数被赋予了默认值,当我们调用时,有些有默认值的参数就可以不传参,Kotlin编译器自动帮我们填上默认值。

createUser(
    name = "Tom",
    age = 30,
    friendCount = 50
)

在 Java 当中要实现类似的逻辑,我们就必须手动定义新的“3 个参数的 createUser 函数”,或者是使用 Builder 设计模式。



Classes and Objects

  • • 类 (Class)

  • • 抽象类 (Abstract Class)

  • • 继承(Extend)

  • • 接口和实现 (Interface and implements)

  • • 嵌套类和内部类( Nested and Inner Classes )

  • • 数据类(Data Class )

  • • object 关键字

  • • object:匿名内部类

  • • object:单例模式

  • • object:伴生对象

  • • 扩展 (Extension)

  • • 什么是扩展函数和扩展属性?

  • • 扩展函数在 Android 中的案例


Java

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 属性 name 没有 setter
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Kotlin

class Person(val name: String, var age: Int)

Kotlin 定义类,同样使用 class 关键字。

Kotlin 定义的类在默认情况下是 public 的。

编译器会帮我们生成“构造函数”,

对于类当中的属性,Kotlin 编译器也会根据实际情况,自动生成 getter 和 setter。

和Java相比 Kotlin 定义一个类足够简洁。


抽象类 (Abstract Class)

abstract class Person(val name: String) {
    abstract fun walk()
    // 省略
}

继承(Extend)

//                      Java 的继承
//                           ↓
public class MainActivity extends Activity {
    @Override
    void onCreate(){ ... }
}

//              Kotlin 的继承

//                 ↓

class MainActivity : AppCompatActivity() {

    override fun onCreate() { … }

}

* * *

接口和实现 (Interface and implements)
--------------------------------

Kotlin 当中的接口(interface),和 Java 也是大同小异的,它们都是通过 interface 这个关键字来定义的。

interface Behavior {

    fun walk()

}

class Person(val name: String): Behavior {

    override fun walk() {

        // walk

    }

    // …

}

可以看到在以上的代码中,我们定义了一个新的接口 Behavior,它里面有一个需要被实现的方法 walk,然后我们在 Person 类当中实现了这个接口。

**Kotlin 的继承和接口实现语法基本上是一样的。**

* * *

Kotlin 的接口,跟 Java 最大的差异就在于,接口的方法可以有默认实现,同时,它也可以有属性。

interface Behavior {

    // 接口内的可以有属性

    val canWalk: Boolean

    // 接口方法的默认实现

    fun walk() {

        if (canWalk) {

            // do something

        }

    }

}

class Person(val name: String): Behavior {

    // 重写接口的属性

    override val canWalk: Boolean

        get() = true

}

我们在接口方法当中,为 walk() 方法提供了默认实现,如果 canWalk 为 true,才执行 walk 内部的具体行为。

Kotlin 当中的接口,被设计得更加强大了。

在 Java 1.8 版本当中,Java接口也引入了类似的特性。

* * *

嵌套类和内部类( Nested and Inner Classes )
-----------------------------------

Java 当中,最常见的嵌套类分为两种:**非静态内部类**、**静态内部类**。Kotlin 当中也有一样的概念。

class A {

    class B {

    }

}

以上代码中,B 类,就是 A 类里面的嵌套类。

**注意:** 无法在 B 类当中访问 A 类的属性和成员方法。

因为Kotlin 默认嵌套类(B类)是一个静态内部类

Kotlin 嵌套类反编译成 Java 代码:

![](https://article.cdnof.com/2307/6d7a6a62-0eaf-4bcd-a219-271c5aea7184.jpg)

* * *

public class JavaOuterInnerClass2 {

   // 内部类

    public  class InnerClass {

    }

    // 静态内部类

    public  static  final   class  StaticInnerClass{

    }

}

通过 javac 命令 编译成 class 文件后:

*   • InnerClass

    ![](https://article.cdnof.com/2307/2b2a7017-2f22-4e81-9c67-b1a24630c8cb.jpg)

*   • StaticInnerClass

    ![](https://article.cdnof.com/2307/59210336-d438-4afe-a532-01e2f1d33c40.jpg)

通过.class 可以发现,

`$InnerClass` 持有外部类的引用。

`$StaticInnerClass` 不持有外部类的引用。

Java 当中的嵌套类,默认情况下,没有 **static关键字** 时,它就是一个**内部类**,这样的内部类是会**持有外部类的引用的**。 所以,这样的设计在 Java 当中会非常容易出现**内存泄漏!** 而我们之所以会犯这样的错误,往往只是因为忘记加`static`关键字。

Kotlin 则恰好**相反**,在默认情况下,**嵌套类变成了静态内部类**,而这种情况下的嵌套类是**不会持有外部类引用的**。只有当我们真正需要访问外部类成员的时候,我们才会加上 **inner 关键字**。这样一来,默认情况下,开发者是不会犯错的,只有手动加上 `inner` 关键字之后,才可能会出现内存泄漏,而当我们加上 inner 之后,其实往往也就能够意识到内存泄漏的风险了。

* * *

数据类(Data Class )
----------------

Koltin 数据类 ,就是用于存放数据的类,等价于 **POJO** (Plain Ordinary Java Object)。要定义一个数据类,我们只需要在普通的类前面加上一个关键字 `data`,就可以把它变成一个"数据类"。

// 数据类当中,最少要有一个属性

                   ↓

data class Person(val name: String, val age: Int)

编译器会为数据类自动生成一些 POJO 常用的方法

*   • getter()

*   • setter()

*   • equals();

*   • hashCode();

*   • toString();

*   • componentN() 函数;

*   • copy()。

* * *

Koltin 数据类反编译成 Java代码:

![](https://article.cdnof.com/2307/abe8a74b-43ed-464e-97b2-a1cd87c09070.jpg)

* * *

object 关键字
----------

`fun` 关键字代表了定义函数,`class` 关键字代表了定义类,这些都是固定的,`object` 关键字,却有三种迥然不同的语义,分别可以定义:

*   • 匿名内部类;

*   • 单例模式;

*   • 伴生对象。

之所以会出现这样的情况,是因为 Kotlin 的设计者认为:

这三种语义**本质**上都是在**定义一个类的同时还创建了对象**。

在这样的情况下,与其分别定义三种不同的关键字,还不如将它们统一成 object 关键字。

* * *

### object:匿名内部类

在 Java 开发当中,我们经常需要写类似这样的代码:

public interface Runnable {

      void run();

  }

  public static void main(String[] args) {

      // 创建Runnable对象并使用匿名内部类重写run方法

      Runnable runnable = new Runnable() {

          public void run() {

              System.out.println("Runnable is running");

          }

      };

      // 创建Thread对象并将Runnable作为参数传入

      Thread thread = new Thread(runnable);

      // 启动线程

      thread.start();

  }

这是典型的匿名内部类写法。

在 Kotlin 当中,我们会使用 `object` 关键字来创建匿名内部类。

interface Runnable {

        fun run()

    }

    

    @JvmStatic

    fun main(args: Array) {

        // 创建Runnable对象并使用匿名内部类重写run方法

        val runnable: Runnable = object : Runnable {

            override fun run() {

                println("Runnable is running")

            }

        }

        // 创建Thread对象并将Runnable作为参数传入

        val thread: Thread = Thread(runnable)

        // 启动线程

        thread.start()

    }

* * *

### object:单例模式

在 Kotlin 当中,要实现单例模式其实非常简单,我们直接用 object 修饰类即可:

object UserManager {

    fun login() {}

}

可以看出,Kotlin 生成单例,代码量非常少

反编译后的 Java 代码:

public final class UserManager {

public static final UserManager INSTANCE;

static {

      UserManager var0 = new UserManager();

      INSTANCE = var0;

   }

private UserManager() {}

public final void login() {}

}

Kotlin 编译器会将其**转换成静态代码块的单例模式**。

虽然具有简洁的优点,但同时也存在两个缺点。

*   • 不支持懒加载。

*   • 不支持传参构造单例。

### object:伴生对象

Kotlin 当中**没有** static 关键字,所以我们没有办法直接定义静态方法和静态变量。不过,Kotlin 还是为我们提供了伴生对象,来帮助实现静态方法和变量。

Kotlin 伴生:

companion object {

        const val LEARNING_FRAGMENT_INDEX = 0

       

        fun jumpToMe(context: Context, index: Int) {

            context.startActivity(Intent(context, TrainingHomeActivity::class.java).apply {

                putExtra(FRAGMENT_INDEX, index)

            })

        }

    }

反编译后的 Java 代码:

private Companion() { }

   public static final Companion Companion = new Companion((DefaultConstructorMarker)null);

   

   public static final int LEARNING_FRAGMENT_INDEX = 0;

  

   public static final class Companion {

      public final void jumpToMe(@NotNull Context context, int index) {

      

      }

 }

可以看到jumpToMe()并不是静态方法,它实际上是通过调用单例 Companion 的实例上的方法实现的。

* * *

扩展 (Extension)
--------------

Kotlin 的扩展(Extension),主要分为两种语法:

第一个是**扩展函数**,

第二个是**扩展属性**。

从语法上看,扩展**看起来**就像是我们从类的外部为它扩展了新的成员。

场景:假如我们想修改 JDK 当中的 String,想在它的基础上增加一个方法“lastElement()”来获取末尾元素,如果使用 Java,我们是无法通过常规手段实现的,因为我们没办法修改 JDK 的源代码。**任何第三方提供的 SDK,我们都无权修改**。

不过,借助 Kotlin 的扩展函数,我们就完全可以在**语义层面**,来为第三方 SDK 的类**扩展**新的成员方法和成员属性。

### 扩展函数

扩展函数,就是从类的外部扩展出来的一个函数,这个函数看起来就像是类的成员函数一样

Extension.kt

 /*

 ①    ②      ③            ④

 ↓     ↓       ↓            ↓   */     

fun String.lastElement(): Char? {

    //   ⑤

    //   ↓

    if (this.isEmpty()) {

        return null

    }

return this[length - 1]

}

// 使用扩展函数

fun main() {

    val msg = "Hello Wolrd"

    // lastElement就像String的成员方法一样可以直接调用

    val last = msg.lastElement() // last = d

}

*   • 注释①,fun关键字,代表我们要定义一个函数。也就是说,不管是定义普通 Kotlin 函数,还是定义扩展函数,我们都需要 fun 关键字。

*   • 注释②,“String.”,代表我们的扩展函数是为 String 这个类定义的。在 Kotlin 当中,它有一个名字,叫做接收者(Receiver),也就是扩展函数的接收方。

*   • 注释③,lastElement(),是我们定义的扩展函数的名称。

*   • 注释④,“Char?”,代表扩展函数的返回值是可能为空的 Char 类型。

*   • 注释⑤,“this.”,代表“具体的 String 对象”,当我们调用 msg.lastElement() 的时候,this 就代表了 msg。

* * *

扩展函数反编译成 Java 代码:

public final class StringExtKt {

   @Nullable

   public static final Character lastElement(@NotNull String \(this\)lastElement) {

      // 省略

   }

}

而如果我们将上面的 StringExtKt 修改成 StringUtils,它就变成了典型的 Java 工具类

public final class StringUtils {

public static final Character lastElement(String $this) {

     // 省略

   }

}

public static final void main() {

  Character last = StringUtils.lastElement(msg);

}

所以 Kotlin 扩展函数 **本质** 上和 Java静态方法 是一样的。

只是编译器帮我们做了很多事情, 让代码写起来更简洁。

* * *

### 扩展属性

而扩展属性,则是在类的外部为它定义一个新的成员属性。

// 接收者类型

//     ↓

val String.lastElement: Char?

    get() = if (isEmpty()) {

            null

        } else {

            get(length - 1)

        }

fun main() {

    val msg = "Hello Wolrd"

    // lastElement就像String的成员属性一样可以直接调用

    val last = msg.lastElement // last = d

}

* * *

扩展函数/扩展属性对比

![](https://article.cdnof.com/2307/a018786a-1b7a-412f-b1eb-8c9b424ad2ba.jpg)

转换成Java代码后,扩展函数和扩展属性代码一致,

和 `StringUtils.lastElement(msg); }` 用法是一样的。

扩展最主要的用途,就是用来取代 Java 当中的各种工具类,比如StringUtils、DateUtils 等等。

* * *

### 扩展函数在 Android 中的案例

**用扩展函数简化Toast的用法:**

这是Toast的标准用法,在界面上弹出一段文字提示,代码很长。

Toast.makeText(context, "This is Toast",Toast.LENGTH_SHORT).show()

还容易忘记调show()函数,造成Toast 没有弹出。

**用扩展函数改写后:**

fun String.showToast(context: Context) {   

    Toast.makeText(context, this, Toast.LENGTH_SHORT).show() 

}

调用时,只需要在要展示的内容后面调一下showToast(),这样就简洁了很多。

"This is Toast".showToast(context)

* * *

函数与 Lambda 表达式
==============

*   • 函数类型(Function Type)

*   • 函数引用 (Function reference)

*   • 高阶函数(Higher-order function)

*   • 匿名函数 (Anonymous function)

*   • Lambda Expressions

*   • 函数式(SAM)接口

*   • SAM 转换

*   • 高阶函数应用

* * *

函数类型(Function Type)
-------------------

函数类型(Function Type)就是**函数的**

**类型**, 在 Kotlin 的世界里,函数是一等公民 既然变量可以有类型,函数也可以有类型。

//         (Int,  Int) ->Float 这就是 add 函数的类型

//           ↑     ↑      ↑

fun add(a: Int, b: Int): Float { return (a+b).toFloat() }

将第三行代码里的“ **Int** **Int** **Float**”抽出来,就可以确定该函数的类型。

将函数的“参数类型”和“返回值类型”抽象出来后,加上`()`,`->` 符号加工后,就得到了“函数类型”。

`(Int, Int) ->Float` 就代表了参数类型是两个 Int,返回值类型为 Float 的函数类型。

* * *

函数引用(Function reference)
------------------------

普通的变量有引用的概念,我们可以将一个变量赋值给另一个变量,这一点,在函数上也是同样适用的,函数也有引用,并且也可以赋值给变量。

前面定义的 add 函数,赋值给另一个函数变量时,不能直接用的,

![](https://article.cdnof.com/2307/46529722-dd59-4b2f-a22a-add6cc104d04.jpg)

需要使用::操作符 , 后跟要引用的函数名,获得函数引用后才可以去赋值。

fun add(a: Int, b: Int): Float { return (a+b).toFloat() }

//   变量     函数类型               函数引用        

//    ↑         ↑                     ↑

val function: (Int, Int) -> Float = ::add

 println(function(2, 3)) // 输出 5

加了双冒号:: , 这个函数才变成了一个**对象**,只有对象才能被赋值给变量。

* * *

* * *

fun add(a: Int, b: Int): Float { return (a+b).toFloat() } 

   

   fun testGaojie() {

     println( ::add )

     println( (::add)(2, 3) )// 输出 5.0

    }

通过反编译成 Java 代码,可以看出。

`::add` 等价于 `Function2 var1 = new Function2(...)` ,

是一个FunctionN 类型的对象。

反编译成 Java代码:

public final void testGaojie() {

 //  println( ::add )

      Function2 var1 = new Function2((GaojieFunTest)this) {

         public Object invoke(Object var1, Object var2) {

            return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue());

         }

         public final float invoke(int p1, int p2) {

            return ((GaojieFunTest)this.receiver).add(p1, p2);

         }

      };

      System.out.println(var1);

//  println( (::add)(2, 3) )

      float var2 = ((Number)((Function2)(new Function2((GaojieFunTest)this) {

         public Object invoke(Object var1, Object var2) {

            return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue());

         }

         public final float invoke(int p1, int p2) {

            return ((GaojieFunTest)this.receiver).add(p1, p2);

         }

      })).invoke(2, 3)).floatValue();

      System.out.println(var2);

   }

* * *

* * *

fun add(a: Int, b: Int): Float { return (a+b).toFloat() } 

   

   fun testGaojie() {

     println(  add(2, 3)  )// 输出 5.0

     val function: (Int, Int) -> Float = ::add

     println( function(2, 3) ) // 输出 5.0

     println(  function.invoke(2, 3)  )  // 输出 5.0

    }

将 testGaojie()转换成 Java 代码。可以看到在 Java 里,&nbsp;**函数类型**被声明为**普通的接口**:一个函数类型的变量是FunctionN接口的一个实现。Kotlin标准库定义了一系列的**接口**,这些接口对应于**不同参数数量**的**函数**:`Function0<R>`(没有参数的函数)、`Function2<P1,P2,R>`(2个参数的函数)...`Function22<P1,P2 ... R>`。每个接口定义了一个`invoke()`方法,调用这个方法就会执行函数。一个**函数类型的变量**就是实现了对应的FunctionN接口的**实现类**的**实例**。实现类的`invoke()`方法包含了&nbsp;**函数引用**对应的**函数**的**函数体**

反编译成 Java代码:

public final void testGaojie() {

 // println(  add(2, 3)  )

      float var1 = this.add(2, 3);

      System.out.println(var1);

//  val function: (Int, Int) -> Float = ::add     

      Function2 function = (Function2)(new Function2((GaojieFunTest)this) {

         // \(FF: synthetic method
         // \)FF: bridge method

         public Object invoke(Object var1, Object var2) {

            return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue());

         }

public final float invoke(int p1, int p2) {

            return ((GaojieFunTest)this.receiver).add(p1, p2);

         }

      });

// println( function(2, 3) ) // 输出 5.0      

      float var2 = ((Number)function.invoke(2, 3)).floatValue();

      System.out.println(var2);

//  println(  function.invoke(2, 3)  )  // 输出 5.0     

      var2 = ((Number)function.invoke(2, 3)).floatValue();

      System.out.println(var2);

   }

* * *

高阶函数 (Higher-order function)
----------------------------

高阶函数的定义:高阶函数是将函数用作**参数**或者**返回值**的函数。

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

或者说,如果一个函数的**参数**或者**返回值**,其中有一个是**函数**,那么这个函数就是高阶函数。

//                            函数类型的变量   函数类型

    //                                 ↓            ↓

    fun  higherOrderAdd( a:Int,b: Int,block: (Int, Int) -> Float):Float{

//                   函数类型的变量

//                       ↓

        var  result = block.invoke(a,b) 

//                   函数类型的变量

//                       ↓

        var  result2 = block(a,b)

        println("result:$result")

        return result

    }

higherOrderAdd 有一个参数是函数类型,所以它是高阶函数

* * *

匿名函数
----

匿名函数看起来跟普通函数很相似,除了它的**名字**和**参数类型**被省略了外。 匿名函数示例如下:

fun(a :Int, b :Int) = a + b

上面的匿名函数是没法直接调用的,赋值给变量后才可以调用

val anonymousFunction = fun(a :Int, b :Int) = a + b

&nbsp;&nbsp;fun&nbsp;anonymousFunctionTest()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;higherOrderAdd(2,2,::add)&nbsp;//&nbsp;函数引用
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;higherOrderAdd(2,2,anonymousFunction)&nbsp;//&nbsp;函数变量
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;higherOrderAdd(2,2,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fun&nbsp;(a:Int,b:Int):Float{&nbsp;return&nbsp;(a+b).toFloat()})&nbsp;//&nbsp;匿名函数
&nbsp;&nbsp;&nbsp;&nbsp;}
```

匿名函数**本质**上也是函数类型的对象,所以可以赋值给变量。

* * *

* * *

匿名函数不能单独声明在 ()外面,因为匿名函数是(**函数的声明**与**函数引用**合二为一)

![](https://article.cdnof.com/2307/00d6ba9a-17d1-4484-8aee-38423390fdd5.jpg)

// 具名函数不能直接赋值给变量,因为它不是对象

![](https://article.cdnof.com/2307/b44f1244-62fd-4c42-bd63-d5820b9beaab.jpg)

// 函数()内不能直接 声明 具名函数,因为它不是对象

![](https://article.cdnof.com/2307/eecc673d-ee0b-4088-b717-cad756f4cf99.jpg)

这几个个报错是因为,匿名函数是把**函数的声明**与**函数引用**合二为一了,所以在需要匿名函数的地方,声明一个具名函数是报错的,正确的做法是改用**具名函数引用**&nbsp;例如:

```
&nbsp;&nbsp;higherOrderAdd(2,2,::add)&nbsp;//&nbsp;函数引用
```

* * *

* * *

Lambda
------

Java 在 Java8中引入的Lambda。

Java Lambda 的基本语法是

```
(parameters)&nbsp;->&nbsp;expression&nbsp;
```

或(请注意语句的花括号)

```
&nbsp;&nbsp;(parameters)&nbsp;->&nbsp;{&nbsp;statements;&nbsp;}
```

Kotlin 语言的是可以用 Lambda 表达式作为函数参数的,Lambda就是**一小段**可以作为**参数**传递的**代码**,那么到底多少代码才算一小段代码呢?Kotlin对此并没有进行限制,但是通常不建议在Lambda 表达式中编写太长的代码,否则可能会影响代码的**可读性**。

Lambda也可以理解为是**匿名函数**的**简写**。

我们来看一下Lambda表达式的语法结构:

```
{参数名1:&nbsp;参数类型,&nbsp;参数名2:&nbsp;参数类型&nbsp;->&nbsp;函数体}
```

首先最外层是一对花括号{ },如果有参数传入到Lambda表达式中的话,我们还需要声明**参数列表**,参数列表的结尾使用一个 '->' 符号 ,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,并且**最后一行代码**会自动作为Lambda表达式的**返回值**。

* * *

```
&nbsp;&nbsp;&nbsp;&nbsp;fun&nbsp;&nbsp;higherOrderAdd(&nbsp;a:Int,b:&nbsp;Int,block:&nbsp;(Int,&nbsp;Int)&nbsp;->&nbsp;Float):Float{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;&nbsp;result&nbsp;=&nbsp;block(a,b)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;println("result:$result")
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;result
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Test
&nbsp;&nbsp;&nbsp;&nbsp;fun&nbsp;anonymousFunctionTest()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;higherOrderAdd(2,2,::add)&nbsp;//&nbsp;函数引用
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;higherOrderAdd(3,3,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fun&nbsp;(a:Int,b:Int):Float{&nbsp;return&nbsp;(a+b).toFloat()})&nbsp;//&nbsp;匿名函数
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;higherOrderAdd(4,4,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;a:Int,b:Int&nbsp;->&nbsp;&nbsp;(a+b).toFloat()})&nbsp;//&nbsp;&nbsp;&nbsp;&nbsp;Lambda表达式
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;println(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fun&nbsp;(a:Int,b:Int):Float{&nbsp;return&nbsp;(a+b).toFloat()}(5,5)&nbsp;)&nbsp;//&nbsp;匿名函数直接调用
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;println(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;a:Int,b:Int&nbsp;->&nbsp;&nbsp;(a+b).toFloat()}(5,5))&nbsp;//&nbsp;Lambda表达式调用
&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;

```

相比匿名函数,lambda 表达式定义与引用函数更&nbsp;**简洁**&nbsp;。

* * *

函数式(SAM)接口
----------

SAM 是 Single Abstract Method 的缩写,只有一个抽象方法的接口称为**函数式接口**或&nbsp;**SAM(单一抽象方法)接口**。函数式接口可以有多个非抽象成员,但**只能有一个抽象成员**。

在Java 中可以用注解@FunctionalInterface 声明一个函数式接口:

```
@FunctionalInterface
public&nbsp;interface&nbsp;Runnable&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;run();
}
```

在 Kotlin 中可以用 fun 修饰符在 Kotlin 中声明一个函数式接口:

```
//&nbsp;注意&nbsp;interface&nbsp;前的&nbsp;fun
fun&nbsp;interface&nbsp;KRunnable&nbsp;{
&nbsp;&nbsp;&nbsp;fun&nbsp;invoke()
}
```

* * *

* * *

SAM 转换
------

对于函数式接口,可以通过 lambda 表达式实现 SAM 转换,从而使代码更简洁、更有可读性。

使用 lambda 表达式可以替代手动创建 实现函数式接口的类。 通过 SAM 转换, Kotlin 可以将 签名与接口的单个抽象方法的**签名匹配**的任何&nbsp;**lambda 表达式**,转换成实现该接口的类的**实例**。

```
//&nbsp;注意需用fun&nbsp;关键字声明
fun&nbsp;&nbsp;interface&nbsp;&nbsp;Action{
&nbsp;&nbsp;&nbsp;&nbsp;fun&nbsp;run(str:String)
}
fun&nbsp;&nbsp;runAction(action:&nbsp;Action){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;action.run("this&nbsp;&nbsp;run")
}

fun main() {

//      创建一个 实现函数式接口 的类 的实例(匿名内部类)

    val action = object :Action{

        override fun run(str: String) {

            println(str)

        }

    }

    //   传入实例,不使用 SAM 转换

    runAction(action)

//    利用 Kotlin 的 SAM 转换,可以改为以下等效代码:

//    使用 Lambda表达式替代手动创建 实现函数式接口的类

    runAction({

            str-> println(str)

    })

}

* * *

* * *

fun  interface  InterfaceApi{

    fun run(str:String)

}

fun  runInterface(interfaceApi: InterfaceApi){

    interfaceApi.run("this  run")

}

//  函数类型替代接口定义

fun  factionTypeReplaceInterface(block:(String)->Unit){

     block("this block run")

}

//===Test

// 普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的

fun  testFactionTypeReplaceInterface(){

    val function:(String)->Unit = { println(it) }

    runInterface(function) //普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的

    factionTypeReplaceInterface(function)

}

// 高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。

fun  testInterface(){

    val interfaceApi:InterfaceApi = object :InterfaceApi{

        override fun run(str: String) {

            println(str)

        }

    }

    runInterface(interfaceApi)

    factionTypeReplaceInterface(interfaceApi)// 高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。

}

![](https://article.cdnof.com/2307/0d0e061f-70cd-4fc5-9508-410229a1a7d3.jpg)

普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的

反过来不可以:

高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。

前面说的都是函数传不同的参数类型。

![](https://article.cdnof.com/2307/a191ca0e-6a4d-4150-b483-d17388ba0b1f.jpg)

这张图中的三处报错都是,**类型不匹配**。

**说明:**

作为函数实参时, 函数类型对象 单向代替 函数式接口对象。

但是在创建对象时, 函数类型、函数式接口两种类型是泾渭分明的。

高阶函数应用
------

在Android开发时,我们经常会遇到给自定义View绑定点击事件的场景。以往通常的做法如下:

// CustomView.java

// 成员变量

private OnContextClickListener mOnContextClickListener;

// 监听手指点击内容事件

public void setOnContextClickListener(OnContextClickListener l) {

    mOnContextClickListener = l;

}

// 为传递这个点击事件,专门定义了一个接口

public interface OnContextClickListener {

    void onContextClick(View v);

}

### \>

// 设置手指点击事件

customView.setOnContextClickListener(new View.OnContextClickListener() {

    @Override

    public void onContextClick(View v) {

        gotoPreview();

    }

});

看完了这两段代码之后,你有没有觉得这样的代码会很啰嗦?因为,真正逻辑只有一行代码:gotoPreview(),而实际上我们却写了 6 行代码。

* * *

### 用 Kotlin 高阶函数 改写后

//View.kt

//                     (View) -> Unit 就是「函数类型 」

//                       ↑        ↑ 

var mOnContextClickListener: ((View) -> Unit)? = null

// 高阶函数

fun setOnContextClickListener(l: (View) -> Unit) {

    mOnClickListener = l;

}

如果我们将前面Java写的例子的核心逻辑提取出来,会发现这样才是最简单明了的:

//                      { gotoPreview() } 就是 Lambda

//                             ↑

customView.setOnContextClickListener({ gotoPreview() })

Kotlin 语言的设计者是怎么做的呢?实际上他们是分成了两个部分:

*   •&nbsp;用函数类型替代接口定义;

*   •&nbsp;用 Lambda 表达式作为函数参数。

* * *

Kotlin 中引入高阶函数会带来几个好处:一个是针对**定义方**,代码中**减少**了接口类的定义;另一个是对于**调用方**来说,代码也会更加**简洁**。这样一来,就大大减少了代码量,提高了代码可读性,并通过减少类的数量,提高了代码的性能。

|
 | 不使用高阶函数 | 使用高阶函数 |
| --- | --- | --- |
| 定义方 | 需要额外定义接口 | 不需要额外定义接口 |
| 调用方 | 代码繁琐 | 代码简洁清晰 |
| 性能 | 差 | 借助inline的情况,性能更高 |

* * *

最后总结
====

思考讨论
----

本文主要分享了 空安全、扩展函数、高阶函数、Lambda,

本文分享的Kotlin内容,您认为哪些特性是最有趣或最有用的?

* * *

参考文档:
-----

*   •&nbsp;Kotlin 语言中文站

*   •&nbsp;《Kotlin实战》

*   •&nbsp;《Kotlin核心编程》

*   •&nbsp;《Kotlin编程权威指南》

*   •&nbsp;《Java 8实战》

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章