最新要闻
- 时讯:出口货值增长近40倍!我国这个地方制造的汽车加速出口中亚→
- 焦点!拓邦发布钠离子电池:-40℃放电容量近80% 秒杀锂电
- 玩家化身手工耿血洗《塞尔达》:不仅造出了坦克飞机还要修仙
- 独悬变扭力梁、气囊也少了 新款丰田卡罗拉上市:11.68万起
- 国内营收占10%以上 美光成美国存储芯片独苗:内存全球第三_每日快看
- 新车下饺子!比亚迪宋Pro DM-i冠军版内饰官图发布:新配色真高级
- 优选营养素密度高的食物,合理膳食提升免疫力
- 没有5G卖4G的华为手机比苹果还牛:消息称逆势上调全年出货量目标 全行业第一家|天天精选
- 1499元!小度青禾学习手机开启预售:一机顶6台 立省1万元
- 热门看点:400年前的画里有男孩穿耐克鞋 网友称穿越:还有百年前画作出现iPhone
- 全红婵的水花让物理学不存在了 网友点赞:物理学对她不适用|全球观热点
- 一个月没找到 美国30吨爆炸性化学品铁路运输时丢失:科普硝酸铵理化性质-世界热点
- 快报:中国抗体-B(03681.HK):SM17新药研究申请获国家药监局药品审评中心受理
- 印为何收回2000卢比纸币?专家:缓解银行流动性危机和加息压力
- BLG夺《英雄联盟》MSI亚军 B站:BLG粉丝赠送一年大会员 全球微动态
- 比亚迪汉DM-i冠军版/DM-p战神版爆火!3天订单破1.3万辆:女车主高达37.2%
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
天天快讯:From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
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 兼容
(资料图片仅供参考)
可以做什么?
Android
Server-side
Multiplatform Mobile
Kotlin Multiplatform Mobile is in Beta!
Multiplatform libraries
可以做很多方向的开发!Create a multiPlatform library for JVM, JS, and Native platforms.
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
- 简约:使用一行代码创建一个包含
getters
、setters
、equals()
、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 等。
类型 | 位宽度 | 备注 |
---|---|---|
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 编译。
这是因为 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 代码:
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代码:
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}
扩展函数/扩展属性对比转换成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() }
将第三行代码里的“ IntIntFloat”抽出来,就可以确定该函数的类型。
将函数的“参数类型”和“返回值类型”抽象出来后,加上()
,->
符号加工后,就得到了“函数类型”。
(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
(没有参数的函数)、Function2
(2个参数的函数)...Function22
。每个接口定义了一个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中,函数引用和函数调用有以下区别:
- 函数引用可以视为函数类型的变量,它持有函数的引用。而函数调用则执行函数本身。因此,可以将函数引用传递给其他函数,并在需要时执行。
- 函数引用可以简化调用代码,避免冗长的代码。而函数调用则需要编写完整的函数名称、参数和参数类型。
- 函数引用不会立即执行函数代码,只有在需要时才执行。而函数调用则立即执行函数代码。例如,假设我们有一个名为“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)// 高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。}
普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的
反过来不可以:
高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。
前面说的都是函数传不同的参数类型。
这张图中的三处报错都是,类型不匹配。
说明:
作为函数实参时, 函数类型对象 单向代替 函数式接口对象。
但是在创建对象时, 函数类型、函数式接口两种类型是泾渭分明的。
高阶函数应用
在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实战》
关键词:
-
天天快讯:From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
Kotlin是什么? 可以做什么? Android官方开发语言从Java变为Kotlin,Java有哪些问题? Kotlin的优点 Kotlin特性(Features)
来源: -
微动态丨HTTP1.0、HTTP1.1、HTTP2.0 协议的区别
HTTP1 1相比HTTP1 0具有以下优点:1 持久连接:HTTP1 1引入了持久连接机制,允许多个请求和响应复用同一个T
来源: 天天快讯:From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
GPS北斗校时服务器(时间同步装置)助力桥梁检测系统建设
微动态丨HTTP1.0、HTTP1.1、HTTP2.0 协议的区别
时讯:出口货值增长近40倍!我国这个地方制造的汽车加速出口中亚→
焦点!拓邦发布钠离子电池:-40℃放电容量近80% 秒杀锂电
玩家化身手工耿血洗《塞尔达》:不仅造出了坦克飞机还要修仙
独悬变扭力梁、气囊也少了 新款丰田卡罗拉上市:11.68万起
国内营收占10%以上 美光成美国存储芯片独苗:内存全球第三_每日快看
新车下饺子!比亚迪宋Pro DM-i冠军版内饰官图发布:新配色真高级
优选营养素密度高的食物,合理膳食提升免疫力
c语言程序设计知识点总结03-精彩看点
没有5G卖4G的华为手机比苹果还牛:消息称逆势上调全年出货量目标 全行业第一家|天天精选
1499元!小度青禾学习手机开启预售:一机顶6台 立省1万元
热门看点:400年前的画里有男孩穿耐克鞋 网友称穿越:还有百年前画作出现iPhone
全红婵的水花让物理学不存在了 网友点赞:物理学对她不适用|全球观热点
一个月没找到 美国30吨爆炸性化学品铁路运输时丢失:科普硝酸铵理化性质-世界热点
快报:中国抗体-B(03681.HK):SM17新药研究申请获国家药监局药品审评中心受理
天天精选!交易商协会发布4月债务融资工具业务量统计和主承分类统计
印为何收回2000卢比纸币?专家:缓解银行流动性危机和加息压力
BLG夺《英雄联盟》MSI亚军 B站:BLG粉丝赠送一年大会员 全球微动态
比亚迪汉DM-i冠军版/DM-p战神版爆火!3天订单破1.3万辆:女车主高达37.2%
盖茨再爆与20多岁俄女子发生婚外情 遭美国淫魔富豪威胁 全球速看料
学系统集成项目管理工程师(中项)系列24a_信息系统集成专业技术知识(上)-当前播报
世界快播:雅顿橘灿精华使用方法 雅顿橘灿精华用在哪个步骤
JDG战胜BLG夺《英雄联盟》MSI冠军!官方发福利:皮肤免费送 全球头条
《王国之泪》开发之初没有剧情:优先考虑游戏体验-环球热消息
全国首个DNA存储领域预训练大模型 ChatDNA发布 今日快看
最新消息:[工具/资源] Web应用开发的基础工具与资源
全球观察:Pytest - pytest 命令(2) - 命令参数及含义
计算机组成原理:控制器实验|全球今亮点
焦点播报:记录一次系统恢复的经历
linux 性能自我学习 ———— 理解平均负载 [一] 环球看热讯
焦点速讯:我们看腻了的电视剧 却在东南亚成了顶流
清华女生获选美冠军 回应质疑:打破对理工科女生的偏见 全球热文
音量调节器怎么在桌面显示_音量调节器不见
情定三生40集大结局_你都学会了吗_全球讯息
Netflix 网站架构学习
wps邮件合并功能无法打开数据源_wps邮件合并
三大运营商最新财报出炉:一天净赚4.2亿|每日消息
国内AI大模型 VS chatgpt (20)-- H265/H264有何不同,h265有什么优势?
Revit二次开发实战04(元素搜集过滤器FilteredElementCollector)
雅顿和珂润哪个好 珂润和雅漾哪个好
世界时讯:官方:美光在华销售产品 未通过网络安全审查!关键领域停止采购
DDR5内存之疯狂:频率突破11.2GHz!延迟惨不忍睹-全球观点
每日快看:完全自主知识产权 国产全新3D dToF芯片发布
伊拉克“抱脸虫”追着美国大兵跑!比你想象的还可怕|每日视讯
《GTA6》男主现身:和游戏画面一模一样|速读
期权即将到期 黄金重回区间还是下破?_环球快播报
世界报道:C++ Primer 5th 阅读笔记:字符串,vector 和数组
焦点热文:杂项·入门
AMD RX 7600公版卡曝光:双风扇也迷你可爱!国内会卖吗?|今日热讯
全国第一次!两条高铁的两座特大桥同时转体|焦点关注
大众CC开着开着天窗没了 车主:吓得腿发抖-世界聚焦
万科150亿元定增申请获深交所受理 当前快讯
独特多媒体旋钮设计瞩目:Keychron即将推出Q3 Pro SE机械键盘 观天下
环球时讯:锐龙7 7800X3D开盖“果奔”:60秒后 惨烈一幕出现
妇女能顶半边天下一句_我歌月徘徊下一句 全球信息
即时看!首批名额仅600名:魅族17系列迎来Flyme10内测招募
你的走路姿势 可能一直都错了!难看还伤腿
观速讯丨珍惜白菜价的电视吧 液晶电视面板价格继续大涨
武汉市三角路中学(关于武汉市三角路中学介绍)_世界今热点
499元新机竟敢对标苹果 看完真的有点香!
每日动态!凸六式扫把火了 商家剪坏扫把低价引流 男子将计就计下单
SpringBoot利用自定义注解实现多数据源
2尺2是多少毫米(2尺2多少厘米简介介绍)_今日热议
杭州现鸭屎香冰淇淋:味道揭秘!原来鸭屎香是一种乌龙茶
头条焦点:英国CMA或让步!微软收购动视暴雪新变动
天天关注:《英雄联盟》2023 MSI决赛JDG对战BLG!首发名单出炉:萍乡第一中单之争
青沼英二坦言自己老了:《王国之泪》将我逼到极限
银川市为500名女童免费接种HPV疫苗
IMU 积分进行航迹推算 天天快报
Win7经典“顽疾”:微软确认Win11硬盘可能被错误识别|全球今热点
“挖呀挖”黄老师开始转型:参与文旅宣传!曾否认辞职带货
焦点热文:小李子新片《花月杀手》M站91分 多家媒体给出满分
格致男排,夺冠! 每日速递
CesiumJS 源码杂谈 - 时间与时钟系统_前沿热点
《利益区间》冲击金棕榈大奖
4999元起!小牛MQiL电动两轮车开售:顶配能骑170km_世界动态
对话盛希泰:“专精特新”企业给二三四线城市带来发展机会_世界视点
快报:河南局地大暴雨!女子家中厨房窗户被暴风雨整个吹掉:庆幸无人受伤
每日关注!李想:汽车行业规模要求太苛刻 更容易出问题的不是成本而是销量
04-多路选择器
快报:我国南海发现两处古代沉船
我国深海考古重大进展!首次发现大型古代沉船:水下第一视角太壮观 环球热文
因为AI 让80%的职业原画师下岗
帮助Linux管理员简化任务并实现自动化的七大工具
自己办理深圳创业补贴难吗?创业开饭店有补贴吗深圳
爱驰汽车被曝经营困难:开通员工自费交社保通道 焦点热讯
中国取代日本成全球第一大汽车出口国 俄罗斯为最大买家|天天热门
React闭包陷阱
每日观点:当日快讯:沙特外交大臣表示阿拉伯国家将继续在俄乌冲突中维持中立
全球快报:【财经分析】全球贸易增长正在恢复 应继续加强多边贸易合作
环球短讯!近视可用 特步0~700度专业大框泳镜狂促:12元包邮
店员称衣服掉地上摔个洞需赔款:最终结果让网友不淡定 速看
如何保存新鲜活虾 活虾怎么保存? 全球即时看
全球微动态丨体验阿斯顿·马丁DBX707 看看超跑品牌是怎么做SUV的
比亚迪首家全品牌体验中心开业:几万块到一百万的车全都有 当前简讯
第一批升级iOS 16.5正式版的用户被坑了!_要闻速递
学系统集成项目管理工程师(中项)系列23b_信息系统集成及服务管理(下)
【爬虫数据集】滇西小哥YouTube频道TOP10热门视频的热评数据,共2W条!_世界速看料