kotlin<第三篇>:函数

一、Java函数式API的使用
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread is running");
    }
}).start();


首先将Java代码转换成Kotlin:

Thread(object : Runnable {
    override fun run() {
        System.out.println("Thread is running")
    }
}).start()

Runnable是一个接口,并且只有一个方法需要实现,所以可以使用Java函数式API:

Thread(Runnable {
    System.out.println("Thread is running")
}).start()

由于Thread方法只有一个参数,且 Runnable 是抽象方法接口,那么可以省略接口名:

Thread({ System.out.println("Thread is running") }).start()

 当Lambda表达式是方法的最后一个参数时,可以移到方法的外面:

Thread(){ System.out.println("Thread is running") }.start()

同时,Lambda表达式还是方法的唯一参数,所以小括号可以省略:

Thread { println("Thread is running") }.start()


button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
});

将以上代码转成Kotlin格式:

button.setOnClickListener {
        
}
二、默认值参数和具名参数
默认值参数:

fun printParams(num: Int, str: String = "hello") {
    println("num is $num , str is $str")
}

printParams(11)
printParams(11, "zhangsan")

fun printParams(num: Int = 1, str: String) {
    println("num is $num , str is $str")
}

printParams(str = "zhangsan")

具名参数:

printParams(11, str = "zhangsan")
printParams(num = 11, str = "zhangsan")

使用具名参数,可以忽视传参顺序:

printParams(str = "zhangsan", num = 11)
三、内联函数
Lambda 表达式在底层被转换成了匿名类的实现方式。
我们每调用一次Lambda 表达式,都会创建一个新的匿名类实例,当会造成额外的内存和性能开销。

为了解决这个问题,Kotlin 提供了内联函数的功能,它可以将使用Lambda 表达式带来的运行时开销完全消除。


内联函数的用法非常简单,只需要在定义高阶函数时加上inline关键字的声明即可:

inline fun example(func: (String, Int) -> Unit) = func("hello", 123)

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int = operation(num1, num2)

Kotlin 编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。

如果我们只想内联其中的一个Lambda 表达式该怎么办呢?这时就可以使用 `noinline` 关键字了:

inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {}

内联函数和非内联函数的区别?
【1】内联函数没有真正的参数属性,非内联的函数类型参数可以自由地传递给其他任何函数,
     因为它就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数,
     这也是它最大的局限性;
【2】是内联函数所引用的Lambda 表达式中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回;

返回和局部返回?
【1】内联函数:支持 return@printString 和 return;
【2】非内联函数:仅支持 return@printString

如果在高阶函数中创建了另外的Lambda 或者匿名类的实现,并且在这些实现中调用函数类型参数,
此时再将高阶函数声明成内联函数,编译器一定会提示错误。

如下代码一定会报错:

inline fun runRunnable(lock: () -> Unit) {
    val runnable = Runnable {
        block()
    }
    runnable.run()
}

使用 crossinline关键字可以解决该问题,crossinline 关键字可以解决 return 冲突的问题:

inline fun runRunnable(crossinline block: () -> Unit) {
    val runnable = Runnable {
        block()
    }
    runnable.run()
}

声明了crossinline之后,我们就无法在调用runRunnable函数时的Lambda 表达式中使用return关键字进行函数返回了,
但是仍然可以使用return@runRunnable的写法进行局部返回。
总体来说,除了在return关键字的使用上有所区别之外,crossinline保留了内联函数的其他所有特性。
四、infix函数
目的:增加代码的可读性

新建 infix.kt,代码如下:

package com.example.myapplication

infix fun String.beginsWith(prefix: String) = startsWith(prefix)

infix fun <T> Collection<T>.has(element: T) = contains(element)

使用如下:

if ("Hello Kotlin" beginsWith "Hello") {
    // 处理具体的逻辑
}

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
if (list has "Banana") {
    // 处理具体的逻辑
}
五、泛型的高级特性
【1】泛型实化

inline  + reified 可以将泛型实化

// 泛型实化
inline fun <reified T> getGenericType() = T::class.java

val result1 = getGenericType<String>()
val result2 = getGenericType<Int>()
println("result1 is $result1")
println("result2 is $result2")

Java语言不支持泛型实化,所以 T.class 必然会报错,Kotlin 将 T 实化后,`T::class.java` 不会报错。

可以新建 reified.kt 文件,将泛型实化代码放进去。

启动 Activity 封装:

inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
    val intent = Intent(context, T::class.java)
    intent.block()
    context.startActivity(intent)
}

startActivity<MainActivity>(this) {
    putExtra("name", "zhangsan")
    putExtra("age", 14)
}

【2】泛型的协变和逆变(不常用)

泛型的协变:假如定义了一个MyClass<T>的泛型类,其中 A 是 B 的子类型,
同时 MyClass<A> 又是 MyClass<B> 的子类型,那么我们就可以称MyClass在 T 这个泛型上是协变的。

out:方法返回值位置
in:方法参数位置

如何才能让MyClass<A>成为MyClass<B>的子类型呢?

class SimpleData<out T>(val data : T) {
    fun get(): T? {
        return data
    }
}

out 将泛型只能用于输出位置。

这样,在使用的时候就不会报错:

fun main() {
    val student = Student("Tom", 19)
    val data = SimpleData<Student>(student)
    handleSimpleData(data)
    val studentData = data.get()
}

fun handleSimpleData(data: SimpleData<Person>) {
    val personData = data.get()
}

泛型的逆变:假如定义了一个 MyClass<T> 的泛型类,其中 A 是 B 的子类型,
同时 MyClass<B>又是 MyClass<A> 的子类型, 那么我们就可以称 MyClass 在 T 这个泛型上是逆变的。

package com.example.myapplication

interface Transformer<in T> {
    fun transform(t: T): String
}

fun main() {
    val trans = object : Transformer<Person> {
        override fun transform(t: Person): String {
            return "${t.name} ${t.age}"
        }
    }
    handleTransformer(trans)
}
fun handleTransformer(trans: Transformer<Student>) {
    val student = Student("Tom", 19)
    val result = trans.transform(student)
}
六、Nothing类型函数
当我们自动实现某方法时,IDE工具会自动生成 TODO 代码:

interface A {
    fun show()
}

class AIml : A {
    override fun show() {
        TODO("Not yet implemented")
    }
}

源码如下:

@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")

TODO函数返回的是一个异常,如果放任不管,程序会直接crash。
它的返回类型是 Nothing,在项目开发中不建议将 TODO 作为异常处理,可以直接使用RuntimeException作为异常处理:

throw RuntimeException("Not yet implemented")

从效果上来说,RuntimeException 和 TODO 的作用是一样的,但是意义完全不一样,
RuntimeException 是实打实的异常类,而 TODO 只是在提醒开发者,你还有这个地方忘记实现。
七、Kotlin中反引用函数
【1】用法一:单元测试

fun main() {
    `该函数的作用是xxx,开发者xxx`()
}

fun `该函数的作用是xxx,开发者xxx`() {

}

函数名可以是任意字符串,只是将双引号改成了反引号,这样的方法名很直观,不过不符合编码规范,一般用于单元测试。

【2】处理java和kotlin关键字冲突问题

在Kotlin中,in 和 is 是关键字,但是它们不是java的关键字,所以,在java代码中可以定义这样的函数:

public class Test {

    public static void in() {

    }

    public static void is() {

    }
}

将 in 和 is 作为java的方法名,但是在Kotlin中却性不同,Kotlin中不能将关键字作为方法名,也不允许在Kotlin中直接调用这两个方法,比如:

Test.in()
Test.is()

这样直接调用编译器会直接报错,为了解决这个问题,可以使用反引号:

Test.`in`()
Test.`is`()

以上代码不会报错。
八、匿名函数

含有fun的匿名函数写法:

val method1 = fun() { // 没有指定返回值类型的匿名类,相当于返回值类型Unit
    println("name")
}

val method2:  () -> Unit = fun() { // 指定返回值类型Unit
    println("name")
}

val method3: () -> String = fun(): String { // 指定返回值类型String
    println("name")
    return "name"
}

invoke 函数调用匿名函数:

method1.invoke()
method2.invoke()
method3.invoke()

还有一种写法可以省略 fun:

// 成员doAction的返回类型是一个函数,这个函数是匿名函数
val doAction : () -> String

// 匿名函数的实现
doAction = {
    "zhangsan"
}

// 打印
println(doAction())

一般合格的程序员,匿名函数的声明和实现是写在一起的:

val doAction : () -> String = {
    "zhangsan"
}

// 打印
println(doAction())

这种匿名函数的返回值是不需要添加return关键字的,这一点尤其重要。
另外,执行这种匿名函数不再需要调用 invoke 函数。

下面再来说一下匿名函数的参数,一般代码如下:

val doAction : (num1:Int, num2:Int, num3:Int) -> String = { num1, num2, num3 ->
    "zhangsan$num1$num2$num3"
}

可以简写成:

val doAction : (Int, Int, Int) -> String = { num1, num2, num3 ->
    "zhangsan$num1$num2$num3"
}

匿名函数参数类型和返回类型的推断:

val doAction = { num1 : Int, num2 : Int, num3 : Int ->
    "zhangsan$num1$num2$num3"
}
// 打印
println(doAction(1, 2, 3))
九、高阶函数
定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。

除了基本数据类型之外,Kotlin 还增加了`函数类型`

`函数类型`的基本规则如下:

(String, Int) -> Unit

->左边的部分就是用来声明该函数接收什么参数的,多个参数之间使用逗号隔开,如果不接收任何参数,写一对空括号就可以了
->右边的部分用于声明该函数的返回值是什么类型,如果没有返回值就使用Unit,它大致相当于Java 中的 void

如果将上述函数类型添加到某个函数的参数声明或者返回值声明上,那么这个函数就是一个高阶函数。

举例1:

fun example(func: (String, Int) -> Unit) {
    func("hello", 123)
}

fun func(str : String, num : Int) {
    println("$str $num")
}

example(::func)

函数可以写成表达式的形式:

fun example(func: (String, Int) -> Unit) {
    func("hello", 123)
}

fun main() {

    val func = { str : String, num : Int ->
        println("$str $num")
    }
    example(func)

}


举例2:

fun plus(num1: Int, num2: Int): Int {
    return num1 + num2
}
fun minus(num1: Int, num2: Int): Int {
    return num1 - num2
}

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int = operation(num1, num2)

val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1, num2, ::plus)
val result2 = num1AndNum2(num1, num2, ::minus)
println("result1 is $result1")
println("result2 is $result2")

以上代码可以使用Lambda表达式简化,防止定义多个方法:

举例1(简化):

fun example(func: (String, Int) -> Unit) {
    func("hello", 123)
}

example { str, num -> println("$str $num") }


举例2(简化):

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int = operation(num1, num2)

val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1, num2) { num1, num2 ->
    num1 + num2
}
val result2 = num1AndNum2(num1, num2) { num1, num2 ->
    num1 - num2
}
println("result1 is $result1")
println("result2 is $result2")

[本章完...]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容