Android

kotlin<第三篇>:函数

2022-08-09  本文已影响0人  NoBugException
一、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`()

以上代码不会报错。
八、匿名函数
// 成员doAction的返回类型是一个函数,这个函数是匿名函数
val doAction : () -> String

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

// 打印
println(doAction())

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

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

// 打印
println(doAction())

匿名函数的返回值是不需要添加return关键字的,这一点尤其重要。

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

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")

[本章完...]

上一篇 下一篇

猜你喜欢

热点阅读