From Java To Kotlin, 空平安、扩大、函数、Lambda

概述(Summarize)

  • Kotlin 是什么?
  • 能够做什么?
  • Android 官网开发语言从Java变为Kotlin,Java 有哪些问题?
  • Kotlin的长处
  • Kotlin 个性(Features)

Kotlin 是什么?

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 兼容
<img src="http://seachal-blog-picture-host.oss-cn-beijing.aliyuncs.com/MWeb/2023/03/16/16789367124770.jpg" class="m-0 h-110 rounded shadow" />


能够做什么?

  • Android
  • Server-side
  • Multiplatform Mobile

    Kotlin Multiplatform Mobile is in Beta!
  • Multiplatform libraries

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

    <img src="http://seachal-blog-picture-host.oss-cn-beijing.aliyuncs.com/MWeb/2023/03/13/16786878005647.jpg" class="m-0 h-40 rounded shadow" />
    能够做很多方向的开发!


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

  • 空援用(Null references):Java 中的 null 值是常常导致程序运行出错的起因之一,因为 Java 不反对空平安。
  • 更少的函数式编程个性:Java 语言在函数式编程方面的反对绝对较弱,尽管 Java 8 引入了 Lambda 表达式和 Stream API,然而 Kotlin 语言在这方面的反对更加全面和敌对。
  • 不够灵便,不足扩大能力:咱们不能给 第三方 SDK 中的classes 或者 interfaces 减少新的办法。。
  • 语法繁琐,不够简洁:Java 语言比 Kotlin 语言更为简短,须要写更多的代码来实现雷同的工作,这可能会升高开发效率。

Kotlin的长处

Modern, concise and safe programming language
  • 简洁:应用一行代码创立一个蕴含 getterssettersequals()hashCode()toString() 以及 copy() 的 POJO:
  • 平安:彻底辞别那些烦人的 NullPointerException
  • 互操作性: Kotlin 能够与 Java 混合编程,Kotlin 和 Java 能够互相调用,指标是 100% 兼容。

Kotlin 个性(Features)

  • 空平安(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)

变量(Variables)

在 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 = 100price = 101val num = 1num = 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()

根本数据类型( Basic Data Type )

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

类型位宽度备注
Double64Kotlin 没有 double
Float32Kotlin 没有 float
Long64Kotlin 没有 long
Int32Kotlin 没有 int/Intege
Short16Kotlin 没有 short
Byte8Kotlin 没有 byte

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


空平安(Null Safety )

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

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

以上的代码并不能通过 Kotlin 编译。
<img src="http://seachal-blog-picture-host.oss-cn-beijing.aliyuncs.com/MWeb/2023/03/13/16786156409163.jpg" class="m-0 h-30 rounded shadow" />

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

对于可能为 null 的变量,咱们须要在申明的时候,在变量类型前面加一个问号“?”:

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

并且因为 Kotlin 对可能为空的变量类型做了强制辨别,这就意味着,“可能为空的变量”无奈间接赋值给“不可为空的变量”,反过来 “不可为空的变量” 能够赋值给“可能为空的变量” 。

var i: Double = 1.0var j: Double? = nulli = j  // 编译器报错j = i  // 编译通过

这么设计的起因是,从汇合逻辑上:可能为空 蕴含 不可为空

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

var i: Double = 1.0val j: Double? = nullif (j != null) {    i = j  // 编译通过}




函数申明( Define Function )

在 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 !"

这样看起来就更简洁了。


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

命名参数/具名参数 (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 中的案例

类 (Class)

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;    }}

Class

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 代码:

<img src="http://seachal-blog-picture-host.oss-cn-beijing.aliyuncs.com/MWeb/2023/03/16/16788807777726.jpg" class="m-5 rounded shadow" />


 public class JavaOuterInnerClass2 {   // 外部类    public  class InnerClass {    }    // 动态外部类    public  static  final   class  StaticInnerClass{    }}

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

  • InnerClass
  • StaticInnerClass

通过.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代码:

<img src="http://seachal-blog-picture-host.oss-cn-beijing.aliyuncs.com/MWeb/2023/03/15/16788793363013.jpg" class="m-0 h-110 rounded shadow" />


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<String>) {        // 创立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}

扩大函数/扩大属性比照

转换成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)就是函数<u>的</u>类型
在 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 函数,赋值给另一个函数变量时,不能间接用的,

须要应用::操作符 , 后跟要援用的函数名,取得函数援用后才能够去赋值。

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 里,
函数类型被申明为一般的接口:一个函数类型的变量是FunctionN接口的一个实现。Kotlin规范库定义了一系列的接口,这些接口对应于不同参数数量函数Function0<R>(没有参数的函数)、Function2<P1,P2,R>(2个参数的函数)...Function22<P1,P2 ... R>。每个接口定义了一个invoke()办法,调用这个办法就会执行函数。一个函数类型的变量就是实现了对应的FunctionN接口的实现类实例。实现类的invoke()办法蕴含了 函数援用对应的函数函数体

反编译成 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);   }

总结

Kotlin中,函数援用和函数调用有以下区别:

  1. 函数援用能够视为函数类型的变量,它持有函数的援用。而函数调用则执行函数自身。因而,能够将函数援用传递给其余函数,并在须要时执行。
  2. 函数援用能够简化调用代码,防止简短的代码。而函数调用则须要编写残缺的函数名称、参数和参数类型。
  3. 函数援用不会立刻执行函数代码,只有在须要时才执行。而函数调用则立刻执行函数代码。
    例如,假如咱们有一个名为“double”的函数,它承受一个整数并返回它的两倍。那么,函数援用和函数调用的代码如下所示:
val doubleFunc: (Int) -> Int = ::double // 函数调用val result = double(5) // 返回 10

在这个例子中,咱们定义了一个函数援用,它能够在须要时传递给其余函数,也能够在须要时执行。

第 2 行代码咱们还调用了函数“double”,它立刻执行代码并返回后果。


高阶函数 (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
  fun anonymousFunctionTest() {        higherOrderAdd(2,2,::add) // 函数援用        higherOrderAdd(2,2,anonymousFunction) // 函数变量        higherOrderAdd(2,2,            fun (a:Int,b:Int):Float{ return (a+b).toFloat()}) // 匿名函数    }

匿名函数实质上也是函数类型的对象,所以能够赋值给变量。


匿名函数不能独自申明在 ()里面,因为匿名函数是(函数的申明函数援用合二为一)

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

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

这几个个报错是因为,匿名函数是把函数的申明函数援用合二为一了,所以在须要匿名函数的中央,申明一个具名函数是报错的,正确的做法是改用具名函数援用 例如:

  higherOrderAdd(2,2,::add) // 函数援用

Lambda

Java 在 Java8中引入的Lambda。

Java Lambda 的根本语法是

(parameters) -> expression 

或(请留神语句的花括号)

  (parameters) -> { statements; }

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

Lambda也能够了解为是匿名函数简写

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

{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}

首先最外层是一对花括号{ },如果有参数传入到Lambda表达式中的话,咱们还须要申明参数列表,参数列表的结尾应用一个 '->' 符号 ,示意参数列表的完结以及函数体的开始,函数体中能够编写任意行代码,并且最初一行代码会主动作为Lambda表达式的返回值


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

相比匿名函数,lambda 表达式定义与援用函数更 简洁


函数式(SAM)接口

SAM 是 Single Abstract Method 的缩写,只有一个形象办法的接口称为函数式接口SAM(繁多形象办法)接口。函数式接口能够有多个非形象成员,但只能有一个形象成员

在Java 中能够用注解@FunctionalInterface 申明一个函数式接口:

@FunctionalInterfacepublic interface Runnable {    void run();}

在 Kotlin 中能够用 fun 修饰符在 Kotlin 中申明一个函数式接口:

// 留神 interface 前的 funfun interface KRunnable {   fun invoke()}

SAM 转换

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

应用 lambda 表达式能够代替手动创立 实现函数式接口的类。 通过 SAM 转换, Kotlin 能够将 签名与接口的单个形象办法的签名匹配的任何 lambda 表达式,转换成实现该接口的类的实例

// 留神需用fun 关键字申明fun  interface  Action{    fun run(str:String)}fun  runAction(action: Action){     action.run("this  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)// 高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不能够的。}

一般函数,参数是函数式接口对象,传 函数类型对象 也是能够的

反过来不能够:

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

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

<img src="http://seachal-blog-picture-host.oss-cn-beijing.aliyuncs.com/MWeb/2023/03/23/16795466653507.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 语言的设计者是怎么做的呢?实际上他们是分成了两个局部:

  • 用函数类型代替接口定义;
  • 用 Lambda 表达式作为函数参数。

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

不应用高阶函数应用高阶函数
定义方须要额定定义接口不须要额定定义接口
调用方代码繁琐代码简洁清晰
性能借助inline的状况,性能更高

最初总结

思考探讨

本文次要分享了 空平安、扩大函数、高阶函数、Lambda,

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


参考文档:

  • Kotlin 语言中文站
  • 《Kotlin实战》
  • 《Kotlin外围编程》
  • 《Kotlin编程权威指南》
  • 《Java 8实战》