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 兼容

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


能够做什么?

  • Android

  • Server-side

  • Multiplatform Mobile

    Kotlin Multiplatform Mobile is in Beta!

  • Multiplatform libraries

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

    From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
    能够做很多方向的开发!


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 = 100
price = 101
​
val num = 1
num = 2 // 编译器报错

var, val 反编译成 Java :

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


咱们已经知道了 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 等。

类型 位宽度 补白
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 里,一切都是目标。


空安全(Null Safety )

已然 Kotlin 中的一切都是目标,那么目标就有可能为空。假定我写这样的代码:

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

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

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

这是由于 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? = nullif (j != null) {
  i = j // 编译经过
}

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


函数声明( 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 代码:

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


 public class JavaOuterInnerClass2 {
  // 内部类
  public class InnerClass {
   }
  // 静态内部类
  public static final  class StaticInnerClass{
   }
}

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

  • InnerClass
    From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
  • StaticInnerClass
    From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

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

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了


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
}

扩展函数/扩展特点比照

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
转化成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 函数,赋值给另一个函数变量时,不能直接用的,

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

需求运用::操作符 , 后跟要引证的函数名,取得函数引证后才能够去赋值。

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()}) // 匿名函数
   }

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


匿名函数不能独自声明在 ()外面,由于匿名函数是(函数的声明函数引证合二为一)

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

// 签字函数不能直接赋值给变量,由于它不是目标

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

// 函数()内不能直接 声明 签字函数,由于它不是目标

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

这几个个报错是由于,匿名函数是把函数的声明函数引证合二为一了,所以在需求匿名函数的地方,声明一个签字函数是报错的,正确的做法是改用签字函数引证 例如:

 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 声明一个函数式接口:

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

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

// 留意 interface 前的 fun
fun 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)// 高阶函数, 参数是函数类型目标,传 是函数式接口目标 是不能够的。
}

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

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

反过来不能够:

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

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

From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了

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

说明:

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

但是在创立目标时, 函数类型、函数式接口两种类型是泾渭分明的。

高阶函数运用

在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实战》