从零开始的Kotlin:功能带来更多乐趣

Kotlin是一种现代编程语言,可编译为Java字节码。 它是免费的, 开放 源码 ,并承诺使编码为Android更有趣。

上一篇文章中 ,您了解了Kotlin中的软件包和基本功能。 函数是Kotlin的核心,因此在这篇文章中,我们将更仔细地研究它们。 我们将在Kotlin中探索以下类型的功能:

  • 顶级功能
  • lambda表达式或函数文字
  • 匿名功能
  • 局部或嵌套函数
  • 中缀函数
  • 成员功能

您会为Kotlin中的功能所能完成的所有出色工作感到惊讶!

1.顶级功能

顶层函数是Kotlin包中的函数,它们在任何类,对象或接口之外定义。 这意味着它们是您直接调用的函数,而无需创建任何对象或调用任何类。

如果您是Java编码人员,那么您会知道我们通常在帮助程序类中创建实用程序静态方法。 这些帮助器类实际上并没有做任何事情-它们没有任何状态或实例方法,它们只是充当静态方法的容器。 一个典型的示例是java.util包中的Collections类及其静态方法。

Kotlin中的顶级函数可以代替我们用Java编写的帮助器类中的静态实用程序方法。 让我们看看如何在Kotlin中定义顶级函数。

package com.chikekotlin.projectx.utils

fun checkUserStatus(): String {
    return "online"
}

在上面的代码中,我们在名为UserUtils.kt的文件中定义了一个包com.chikekotlin.projectx.utils并在同一包和文件中定义了一个名为checkUserStatus()的顶级实用程序函数。 为简便起见,此非常简单的函数返回字符串“ online”。

我们要做的下一件事是在另一个包或文件中使用此实用程序功能。

package com.chikekotlin.projectx.users

import com.chikekotlin.projectx.utils.checkUserStatus

if (checkUserStatus() == "online") {
    // do something
}

在前面的代码中,我们将函数导入到另一个包中,然后执行它! 如您所见,我们不必创建对象或引用类来调用此函数。

Java互操作性

鉴于Java不支持顶层函数,幕后的Kotlin编译器将创建一个Java类,并将各个顶层函数转换为静态方法。 在我们自己的情况下,生成的Java类是UserUtilsKt ,具有静态方法checkUserStatus()

/* Java */
package com.chikekotlin.projectx.utils

public class UserUtilsKt {

    public static String checkUserStatus() {
        return "online";
    }
}

这意味着Java调用者可以通过引用其生成的类来简单地调用该方法,就像其他任何静态方法一样。

/* Java */
import com.chikekotlin.projectx.utils.UserUtilsKt

... 

UserUtilsKt.checkUserStatus()

请注意,我们可以使用@JvmName批注来更改Kotlin编译器生成的Java类名称。

@file:JvmName("UserUtils")
package com.chikekotlin.projectx.utils

fun checkUserStatus(): String {
    return "online"
}

在上面的代码中,我们应用了@JvmName批注并指定了一个类名UserUtils   用于生成的文件。 还要注意,此批注放置在Kotlin文件的开头,在程序包定义之前。

可以像这样从Java引用它:

/* Java */
import com.chikekotlin.projectx.utils.UserUtils

... 

UserUtils.checkUserStatus()

2. Lambda表达式

Lambda表达式(或函数文字)也未绑定到任何实体,例如类,对象或接口。 它们可以作为参数传递给称为高阶函数的其他函数(我们将在下一篇文章中进一步讨论)。 Lambda表达式仅表示函数的块,使用它们可以减少代码中的噪音。

如果您是Java编码员,那么您将知道Java 8及更高版本提供对lambda表达式的支持。 要在支持早期Java版本(例如Java 7、6或5)的项目中使用lambda表达式,我们可以使用流行的Retrolambda库

关于Kotlin的令人敬畏的事情之一就是开箱即用地支持lambda表达式。 由于Java 6或7不支持lambda以便Kotlin与其进行互操作,因此Kotlin在后台创建了Java匿名类。 但请注意,在Kotlin中创建lambda表达式与在Java中创建完全不同。

以下是Kotlin中lambda表达式的特征:

  • 它必须用花括号{}包围。
  • 它没有fun关键字。
  • 没有访问修饰符(私有,公共或受保护的),因为它不属于任何类,对象或接口。
  • 它没有函数名称。 换句话说,它是匿名的。
  • 未指定返回类型,因为它将由编译器推断出来。
  • 参数没有用括号()包围。

而且,此外,我们可以将lambda表达式分配给变量,然后执行它。

创建Lambda表达式

现在让我们看一些lambda表达式的示例。 在下面的代码中,我们创建了一个不带任何参数的lambda表达式,并为其分配了变量message 。 然后,我们通过调用message()执行lambda表达式。

val message = { println("Hey, Kotlin is really cool!") }
message() // "Hey, Kotlin is really cool!"

我们还要看看如何在lambda表达式中包含参数。

val message = { myString: String -> println(myString) }
message("I love Kotlin") // "I love Kotlin"
message("How far?") // "How far?"

在上面的代码中,我们使用参数myString和参数类型String创建了一个lambda表达式。 如您所见,在参数类型的前面有一个箭头:这是指lambda主体。 换句话说,此箭头将参数列表与lambda主体分隔开。 为了更简洁,我们可以完全忽略参数类型(已由编译器推断)。

val message = { myString -> println(myString) } // will still compile

要具有多个参数,我们只需用逗号将它们分开。 记住,我们不会像在Java中那样将参数列表包装在括号中。

val addNumbers = { number1: Int, number2: Int ->
        println("Adding $number1 and $number2")
        val result = number1 + number2
        println("The result is $result")
    }
addNumbers(1, 3)

但是,请注意,如果无法推断参数类型,则必须显式指定它们(如本例所示),否则代码将无法编译。

Adding 1 and 3
The result is 4

将Lambda传递给函数

我们可以将lambda表达式作为参数传递给函数:因为它们是函数的函数,所以它们被称为“高阶函数”。 这些类型的函数可以接受lambda或匿名函数作为参数:例如, last()集合函数。

在下面的代码中,我们将lambda表达式传递给了last()函数。 (如果您想对Kotlin中的集合进行复习,请访问本系列第三个教程。 )顾名思义,它返回列表中的最后一个元素。 last()接受一个lambda表达式作为参数,而该表达式又接受一个String类型的参数。 其功能主体可作为谓词在集合中的元素子集中进行搜索。 这意味着lambda表达式将决定在寻找最后一个元素时将考虑集合中的哪些元素。

val stringList: List<String> = listOf("in", "the", "club")
print(stringList.last()) // will print "club"
print(stringList.last({ s: String -> s.length == 3})) // will print "the"

让我们看看如何使上面的最后一行代码更具可读性。

stringList.last { s: String -> s.length == 3 } // will also compile and print "the"

如果函数中的最后一个参数是lambda表达式,则Kotlin编译器允许我们删除函数括号。 正如您在上面的代码中所观察到的,我们被允许这样做是因为传递给last()函数的last和only参数是lambda表达式。

此外,我们可以通过删除参数类型来使其更加简洁。

stringList.last { s -> s.length == 3 } // will also compile print "the"

我们不需要显式指定参数类型,因为参数类型始终与集合元素类型相同。 在上面的代码中,我们在String对象的列表集合上调用了last ,因此Kotlin编译器足够聪明,可以知道该参数也将是String类型。

it参数名称

我们甚至可以通过将lambda表达式参数替换为自动生成的默认参数name it进一步简化lambda表达式。

stringList.last { it.length == 3 }

it参数名称是自动生成的,因为last可以接受带有一个参数的lambda表达式或匿名函数(我们稍后将介绍),并且其类型可以由编译器推断。

Lambda表达式中的本地返回

让我们从一个例子开始。 在下面的代码中,我们将一个lambda表达式传递给intList集合上调用的foreach()函数。 此函数将遍历集合并在列表中的每个元素上执行lambda。 如果任何元素可被2整除,它将停止并从lambda返回。

fun surroundingFunction() {
    val intList = listOf(1, 2, 3, 4, 5)
    intList.forEach {
        if (it % 2 == 0) {
            return
        }
    }
    println("End of surroundingFunction()")
}

surroundingFunction() // nothing happened

运行上面的代码可能没有给您预期的结果。 这是因为return语句不会从lambda中返回,而是从包含函数surroundingFunction()的Function surroundingFunction() ! 这意味着surroundingFunction()中的最后一条代码语句将不会执行。

// ...
println("End of surroundingFunction()") // This won't execute
// ...

要解决此问题,我们需要使用标签或名称标签明确告诉它要从哪个函数返回。

fun surroundingFunction() {
    val intList = listOf(1, 2, 3, 4, 5)
    intList.forEach {
        if (it % 2 == 0) {
            return@forEach
        }
    }
    println("End of surroundingFunction()") // Now, it will execute
}

surroundingFunction() // print "End of surroundingFunction()"

在上面的更新代码中,我们在lambda内部的return关键字之后立即指定了默认标签@forEach 。 现在,我们已指示编译器从lambda返回,而不是从包含的函数surroundingFunction() 。 现在, surroundingFunction()的最后一条语句将执行。

请注意,我们还可以定义自己的标签或名称标签。

// ...
    intList.forEach myLabel@ {
        if (it % 2 == 0) {
            return@myLabel
    // ...

在上面的代码中,我们定义了名为myLabel@自定义标签,然后将其指定为return关键字。 编译器为forEach函数生成的@forEach标签不再可用,因为我们已经定义了自己的标签。

但是,不久之后,当我们在Kotlin中讨论匿名函数时,您很快就会看到如何在没有标签的情况下解决本地返回问题。

3.成员职能

这种功能是在类,对象或接口中定义的。 使用成员函数有助于我们进一步模块化程序。 现在让我们看看如何创建成员函数。

class Circle {

    fun calculateArea(radius: Double): Double {
        require(radius > 0, { "Radius must be greater than 0" })
        return Math.PI * Math.pow(radius, 2.0)
    }
}

此代码段显示了一个Circle类(我们将在后面的文章中讨论Kotlin类),该类具有成员函数calculateArea() 。 此函数采用参数radius来计算圆的面积。

要调用成员函数,我们在包含类或对象实例的名称前加一个点,然后在函数名后加上任何参数(如果需要)。

val circle = Circle()
print(circle.calculateArea(4.5)) // will print "63.61725123519331"

4.匿名函数

匿名函数是定义可以传递给函数的代码块的另一种方法。 它没有绑定任何标识符。 以下是Kotlin中匿名函数的特征:

  • 没有名字
  • fun关键字创建
  • 包含功能主体
val stringList: List<String> = listOf("in", "the", "club")
print(stringList.last{ it.length == 3}) // will print "the"

因为我们在上面的last()函数中传递了一个lambda,所以我们无法明确说明返回类型。 为了明确说明返回类型,我们需要使用匿名函数。

val strLenThree = stringList.last( fun(string): Boolean {
    return string.length == 3
})
print(strLenThree) // will print "the"

在上面的代码中,我们已将lambda表达式替换为匿名函数,因为我们希望对返回类型进行明确说明。

在本教程的lambda部分的结尾处,我们使用了一个标签来指定要从哪个函数返回。 在forEach()函数中使用匿名函数代替lambda可以更简单地解决此问题。 返回表达式是从匿名函数返回的,而不是从周围的函数返回的,在我们的例子中是surroundingFunction()函数。

fun surroundingFunction() {
    val intList = listOf(1, 2, 3, 4, 5)
    intList.forEach ( fun(number) {
        if (number % 2 == 0) {
            return
        }
    })
    println("End of surroundingFunction()") // statement executed
}

surroundingFunction() // will print "End of surroundingFunction()"

5.局部或嵌套函数

为了进一步实现程序模块化,Kotlin为我们提供了局部功能-也称为嵌套功能。 局部函数是在另一个函数内部声明的函数。

fun printCircumferenceAndArea(radius: Double): Unit {

    fun calCircumference(radius: Double): Double = (2 * Math.PI) * radius
    val circumference = "%.2f".format(calCircumference(radius))

    fun calArea(radius: Double): Double = (Math.PI) * Math.pow(radius, 2.0)
    val area = "%.2f".format(calArea(radius))

    print("The circle circumference of $radius radius is $circumference and area is $area")
}

printCircumferenceAndArea(3.0) // The circle circumference of 3.0 radius is 18.85 and area is 28.27

正如您在上面的代码片段中所看到的,我们有两个单行函数: calCircumference()calArea()嵌套在printCircumferenceAndAread()函数内。 只能从封闭函数内部而不是外部调用嵌套函数。 同样,嵌套函数的使用使我们的程序更加模块化和整洁。

通过不向其显式传递参数,可以使局部函数更加简洁。 这是可能的,因为局部功能可以访问封闭功能的所有参数和变量。 让我们看看现在正在执行的操作:

fun printCircumferenceAndArea(radius: Double): Unit {

    fun calCircumference(): Double = (2 * Math.PI) * radius
    val circumference = "%.2f".format(calCircumference())

    fun calArea(): Double = (Math.PI) * Math.pow(radius, 2.0)
    val area = "%.2f".format(calArea())
    // ... 
}

如您所见,此更新的代码看起来更具可读性,并减少了我们之前的噪音。 尽管在此示例中,封闭函数很小,但是在较大的封闭函数中,可以将其分解为较小的嵌套函数,此功能确实可以派上用场。

6.中缀功能

infix表示法使我们可以轻松地调用一个参数的成员函数或扩展函数。 除了一个参数是函数之外,还必须使用infix修饰符定义该函数。 要创建一个infix函数,需要涉及两个参数。 第一个参数是目标对象,而第二个参数只是传递给函数的单个参数。

创建中缀成员函数

让我们看一下如何在一个类中创建一个infix函数。 在下面的代码示例中,我们创建了一个带有可变kotlinScore实例字段的Student类。 我们通过在fun关键字之前使用infix修饰符创建了一个infix函数。 如您在下面看到的,我们创建了一个固定函数addKotlinScore() ,该函数获取一个分数并将其添加到kotlinScore实例字段中。

class Student {
    var kotlinScore = 0.0
    
    infix fun addKotlinScore(score: Double): Unit {
        this.kotlinScore = kotlinScore + score
    }
}

调用中缀函数

我们还要看看如何调用我们创建的infix函数。 要在Kotlin中调用infix函数,我们不需要使用点表示法,也不需要在参数中加上括号。

val student = Student()
student addKotlinScore 95.00
print(student.kotlinScore) // will print "95.0"

在上面的代码中,我们称为infix函数,目标对象是student ,双95.00是传递给该函数的参数。

明智地使用infix函数可以使我们的代码比普通样式更具表现力和更清晰。 在Kotlin中编写单元测试时,将不胜感激(我们将在以后的文章中讨论在Kotlin中进行测试)。

"Chike" should startWith("ch")
myList should contain(myElement) 
"Chike" should haveLength(5)
myMap should haveKey(myKey)

to功能

在Kotlin中,我们可以使用to infix函数而不是Pair构造函数来使Pair实例的创建更加简洁。 (在幕后, to还会创建一个Pair实例。)请注意, to函数也是扩展函数(我们将在下一篇文章中进一步讨论)。

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

现在,让我们比较使用to infix函数和直接使用Pair构造函数创建的Pair实例,该实例执行相同的操作,然后看看哪个更好。

val nigeriaCallingCodePair = 234 to "Nigeria"
val nigeriaCallingCodePair2 = Pair(234, "Nigeria") // Same as above

如您在上面的代码中看到的,使用to infix函数比直接使用Pair构造函数创建Pair实例更为简洁。 请记住,使用to infix函数时, 234是目标对象,而String “ Nigeria”是传递给该函数的参数。 此外,请注意,我们还可以执行以下操作来创建Pair类型:

val nigeriaCallingCodePair3 = 234.to("Nigeria") // same as using 234 to "Nigeria"

Ranges and Collections文章中 ,我们在Kotlin中创建了一个地图集合,方法是提供一个成对的列表-第一个值为键,第二个为键。 我们还通过同时使用to infix函数和Pair构造函数来创建单个对来比较地图的创建。

val callingCodesMap: Map<Int, String> = mapOf(234 to "Nigeria", 1 to "USA", 233 to "Ghana")

在上面的代码中,我们使用to infix函数创建了一个用逗号分隔的Pair类型列表,并将它们传递给mapOf()函数。 我们还可以通过直接为每个对使用Pair构造函数来创建相同的地图。

val callingCodesPairMap: Map<Int, String> = mapOf(Pair(234, "Nigeria"), Pair(1, "USA"), Pair(233, "Ghana"))

再次您可以看到,坚持使用to infix函数比使用Pair构造函数具有更少的噪音。

结论

在本教程中,您了解了Kotlin中的函数可以执行的一些有趣操作。 我们介绍了:

但这还不是全部! 还有更多关于Kotlin中功能的知识。 因此,在下一篇文章中,您将学习函数的一些高级用法,例如扩展函数,高阶函数和闭包。 再见!

翻译自: https://code.tutsplus.com/tutorials/kotlin-from-scratch-more-functions--cms-29479

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值