- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
Kotlin 出自于捷克一家软件研发公司 JetBrains ,这家公司开发出很多优秀的 IDE,如 IntelliJ IDEA、DataGrip 等都是它的杰作,包括 Google 官方的 Android IDE -- Android Studio ,也是 IntelliJ IDEA 的插件版.
Kotlin 源于 JetBrains 的圣彼得堡团队, 名称 取自圣彼得堡附近的一个 小岛 ( Kotlin Island ) ,和 Java一样用岛屿 命名 ,JetBrains 在 2010 年首次推出 Kotlin 编程语言,并在次年将之开源.
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: 在 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 helloFunton(name:String):String = "Hello $name !"
我们称之为 单表达式函数 。
由于Kotlin支持 类型推导 ,返回值类型可以省略:
fun helloFunton(name:String):= "Hello $name !"
这样看起来就更简洁了.
以前面的函数为例子,我们调用它:
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 也可以很方便的定位到参数。 这样 易维护 。
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 设计模式.
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 Person(val name: String) {
abstract fun walk()
// 省略
}
// Java 的继承
// ↓
public class MainActivity extends Activity {
@Override
void onCreate(){ ... }
}
// Kotlin 的继承
// ↓
class MainActivity : AppCompatActivity() {
override fun onCreate() { ... }
}
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接口也引入了类似的特性.
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 文件后:
通过.class 可以发现, 。
$InnerClass 持有外部类的引用.
$StaticInnerClass 不持有外部类的引用.
Java 当中的嵌套类,默认情况下,没有 static关键字 时,它就是一个 内部类 ,这样的内部类是会 持有外部类的引用的 。 所以,这样的设计在 Java 当中会非常容易出现 内存泄漏! 而我们之所以会犯这样的错误,往往只是因为忘记加 static 关键字.
Kotlin 则恰好 相反 ,在默认情况下, 嵌套类变成了静态内部类 ,而这种情况下的嵌套类是 不会持有外部类引用的 。只有当我们真正需要访问外部类成员的时候,我们才会加上 inner 关键字 。这样一来,默认情况下,开发者是不会犯错的,只有手动加上 inner 关键字之后,才可能会出现内存泄漏,而当我们加上 inner 之后,其实往往也就能够意识到内存泄漏的风险了.
Koltin 数据类 ,就是用于存放数据的类,等价于 POJO (Plain Ordinary Java Object)。要定义一个数据类,我们只需要在普通的类前面加上一个关键字 data ,就可以把它变成一个"数据类".
// 数据类当中,最少要有一个属性
↓
data class Person(val name: String, val age: Int)
编译器会为数据类自动生成一些 POJO 常用的方法 。
Koltin 数据类反编译成 Java代码:
fun 关键字代表了定义函数, class 关键字代表了定义类,这些都是固定的, object 关键字,却有三种迥然不同的语义,分别可以定义:
之所以会出现这样的情况,是因为 Kotlin 的设计者认为:
这三种语义 本质 上都是在 定义一个类的同时还创建了对象 .
在这样的情况下,与其分别定义三种不同的关键字,还不如将它们统一成 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()
}
在 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 编译器会将其 转换成静态代码块的单例模式 .
虽然具有简洁的优点,但同时也存在两个缺点.
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 的实例上的方法实现的.
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
}
扩展函数反编译成 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 等等.
用扩展函数简化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)
函数类型(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 的函数类型.
普通的变量有引用的概念,我们可以将一个变量赋值给另一个变量,这一点,在函数上也是同样适用的,函数也有引用,并且也可以赋值给变量.
前面定义的 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中,函数引用和函数调用有以下区别:
val doubleFunc: (Int) -> Int = ::double
// 函数调用
val result = double(5) // 返回 10
在这个例子中,我们定义了一个函数引用,它可以在需要时传递给其他函数,也可以在需要时执行.
第 2 行代码我们还调用了函数“double”,它立即执行代码并返回结果.
高阶函数的定义:高阶函数是将函数用作 参数 或者 返回值 的函数.
如果一个函数的 参数类型 是 函数类型 或者 返回值类型 是 函数类型 ,那么这个函数就是就是高阶函数 .
或者说,如果一个函数的 参数 或者 返回值 ,其中有一个是 函数 ,那么这个函数就是高阶函数.
// 函数类型的变量 函数类型
// ↓ ↓
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) // 函数引用
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 是 Single Abstract Method 的缩写,只有一个抽象方法的接口称为 函数式接口 或 SAM(单一抽象方法)接口 。函数式接口可以有多个非抽象成员,但 只能有一个抽象成员 .
在Java 中可以用注解@FunctionalInterface 声明一个函数式接口:
@FunctionalInterface
public interface Runnable {
void run();
}
在 Kotlin 中可以用 fun 修饰符在 Kotlin 中声明一个函数式接口:
// 注意 interface 前的 fun
fun interface KRunnable {
fun invoke()
}
对于函数式接口,可以通过 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 行代码.
//View.kt
// (View) -> Unit 就是「函数类型 」
// ↑ ↑
var mOnContextClickListener: ((View) -> Unit)? = null
// 高阶函数
fun setOnContextClickListener(l: (View) -> Unit) {
mOnClickListener = l;
}
如果我们将前面Java写的例子的核心逻辑提取出来,会发现这样才是最简单明了的:
// { gotoPreview() } 就是 Lambda
// ↑
customView.setOnContextClickListener({ gotoPreview() })
Kotlin 语言的设计者是怎么做的呢?实际上他们是分成了两个部分:
Kotlin 中引入高阶函数会带来几个好处:一个是针对 定义方 ,代码中 减少 了接口类的定义;另一个是对于 调用方 来说,代码也会更加 简洁 。这样一来,就大大减少了代码量,提高了代码可读性,并通过减少类的数量,提高了代码的性能.
不使用高阶函数 | 使用高阶函数 | |
---|---|---|
定义方 | 需要额外定义接口 | 不需要额外定义接口 |
调用方 | 代码繁琐 | 代码简洁清晰 |
性能 | 差 | 借助inline的情况,性能更高 |
本文主要分享了 空安全、扩展函数、高阶函数、Lambda.
本文分享的Kotlin内容,您认为哪些特性是最有趣或最有用的?
最后此篇关于FromJavaToKotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了的文章就讲到这里了,如果你想了解更多关于FromJavaToKotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
可以使用 lambda 和函数创建有序对(Lisp 中的缺点),如 Use of lambda for cons/car/cdr definition in SICP 所示。 它也适用于 Python
我正在尝试从另一个调用一个 AWS lambda 并执行 lambda 链接。这样做的理由是 AWS 不提供来自同一个 S3 存储桶的多个触发器。 我创建了一个带有 s3 触发器的 lambda。第一
根据以下源代码,常规 lambda 似乎可以与扩展 lambda 互换。 fun main(args: Array) { val numbers = listOf(1, 2, 3) f
A Tutorial Introduction to the Lambda Calculus 本文介绍乘法函数 The multiplication of two numbers x and y ca
我想弄清楚如何为下面的表达式绘制语法树。首先,这究竟是如何表现的?看样子是以1和2为参数,如果n是 0,它只会返回 m . 另外,有人可以指出解析树的开始,还是一个例子?我一直找不到一个。 最佳答案
在 C++0x 中,我想知道 lambda 函数的类型是什么。具体来说: #include type1 foo(int x){ return [x](int y)->int{return x * y
我在其中一个职位发布中看到了这个问题,它询问什么是 lambda 函数以及它与高阶函数的关系。我已经知道如何使用 lambda 函数,但不太自信地解释它,所以我做了一点谷歌搜索,发现了这个:What
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
Evaluate (((lambda(x y) (lambda (x) (* x y))) 5 6) 10) in Scheme. 我不知道实际上该怎么做! ((lambda (x y) (+ x x
我正在处理 MyCustomType 的实例集合如下: fun runAll(vararg commands: MyCustomType){ commands.forEach { it.myM
Brian 在他对问题 "Are side effects a good thing?" 的论证中的前提很有趣: computers are von-Neumann machines that are
在 Common Lisp 中,如果我希望两个函数共享状态,我将按如下方式执行 let over lambda: (let ((state 1)) (defun inc-state () (in
Evaluate (((lambda(x y) (lambda (x) (* x y))) 5 6) 10) in Scheme. 我不知道实际上该怎么做! ((lambda (x y) (+ x x
作为lambda calculus wiki说: There are several possible ways to define the natural numbers in lambda cal
我有一个数据类,我需要初始化一些 List .我需要获取 JsonArray 的值(我使用的是 Gson)。 我做了这个函数: private fun arrayToList(data: JsonAr
((lambda () )) 的方案中是否有简写 例如,代替 ((lambda () (define x 1) (display x))) 我希望能够做类似的事情 (empty-lam
我在 Java library 中有以下方法: public void setColumnComparator(final int columnIndex, final Comparator colu
我正在研究一个函数来计算国际象棋游戏中棋子的有效移动。 white-pawn-move 函数有效。当我试图将其概括为任一玩家的棋子 (pawn-move) 时,我遇到了非法函数调用。我已经在 repl
考虑这段代码(在 GCC 和 MSVC 上编译): int main() { auto foo = [](auto p){ typedef decltype(p) p_t;
我正在阅读一个在 lambda 内部使用 lambda 的片段,然后我想通过创建一个虚拟函数来测试它,该函数从文件中读取然后返回最大和最小数字。 这是我想出来的 dummy = lambda path
我是一名优秀的程序员,十分优秀!