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