写给 Java 开发者的 Kotlin 教程 (6) - 函数基础

kotlin functions
函数是构成软件的基础块。我们今天就开始 Kotlin 旅程的第二站 - 函数

函数定义

我们都知道函数的本质也就是接受一些东西然后吐出一些东西,那最为重要的是 入参出惨,然后又知道 Kotlin 是一门静态语言,那类型又很重要,那我们来看看 Kotlin 是如何申明函数的。

1
2
3
fun avg(a: Double, b: Double): Double {
return (a + b)/2
}
1
2
3
Double avg(Double a, Double b) {
return (a + b) / 2;
}

Java 略有不同

  • Kotlin 申明函数使用关键字 fun
  • Kotlin入参类型是后置
  • Kotlin出参类型是后置

所以我们通常定义一个函数都会使用以下的格式

1
2
3
fun functionName(param1: Type1, param2: Type2,..., paramN: TypeN): Type {
// 函数体
}

单行函数定义

java 略有不同,kotlin 有一些特殊的函数格式比如 单行函数

1
fun avg(a: Double, b: Double) = (a + b)/2

Unit 类型

如果希望函数无返回值,可以采用 Unit 作为返回类型,这个和 javavoid 一个含义

1
2
3
fun printAverage(a: Double, b: Double): Unit {
println("Avg of ($a, $b) = ${(a + b)/2}")
}

当然,默认如果这里不填写任何类型,默认即是 Unit,下面的代码和上面等价

1
2
3
fun printAverage(a: Double, b: Double) {
println("Avg of ($a, $b) = ${(a + b)/2}")
}

函数特性

函数默认参数

Kotlin 借鉴其他语言的实现,也支持函数的默认参数值。如下

1
2
3
fun displayGreeting(message: String, name: String = "Guest") {
println("Hello $name, $message")
}

这样我们就可以这样调用这个函数

1
2
3
4
displayGreeting("Welcome to the Yann Blog", "John") 
// Hello John, Welcome to the Yann Blog
displayGreeting("Welcome to the Yann Blog")
// Hello Guest, Welcome to the Yann Blog

考虑以下的定义

1
2
3
fun arithmeticSeriesSum(a: Int = 1, n: Int, d: Int = 1): Int {
return n/2 * (2*a + (n-1)*d)
}

那我们尝试用下面的方式调用,我们会发现失败

1
arithmeticSeriesSum(10) // error: 缺少参数

因为我们的参数只能自左向右的传递入函数,所以我们可以这样调用

1
arithmeticSeriesSum(1, 10)  // 结果 = 55

所以我们在申明函数的时候,尽可能的将带默认值的参数放在函数的右侧。

命名参数

我们经常在 Python 的代码中看见,如下的定义

1
2
3
def named_function(a, b=20, c=10):
return a + b + c
named_function(10, c=30)

这种称之为 Named ArgumentsKotlin 也可以做到如何

1
2
3
4
5
6
7
fun arithmeticSeriesSum(a: Int = 1, n: Int, d: Int = 1): Int {
return n/2 * (2*a + (n-1)*d)
}

arithmeticSeriesSum(n=10) // 结果 = 55
arithmeticSeriesSum(n=10, d=2, a=3) //OK
arithmeticSeriesSum(n=10, 2) //EROOR

注意最后一种方式不被允许了,原因也很简单,因为一旦命名了,我们就无法得知后续的参数应该从哪里开始。

可变参数

Java 不同Kotlin采用一种特殊的关键字 vararg 来申明可变参数

1
2
3
4
5
6
7
fun sumOfNumbers(vararg numbers: Double): Double {
var sum: Double = 0.0
for(number in numbers) {
sum += number
}
return sum
}
1
2
3
4
5
6
7
Double sumOfNumbers(Double... numbers) {
Double rs = 0D;
for (Double d : numbers) {
rs += d;
}
return rs / numbers.length;
}

值得注意 在一个函数中只允许出现一个 vararg

函数作用域

包级别作用域

比如下面这个函数的作用域就是这个包级别,也就是在同一个里都可以访问这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package maths

fun findNthFibonacciNo(n: Int): Int {
var a = 0
var b = 1
var c: Int

if(n == 0) {
return a
}

for(i in 2..n) {
c = a+b
a = b
b = c
}
return b
}

比如我们可以这么调用

1
2
3
4
5
package maths

fun main(args: Array<String>) {
println("10th fibonacci number is - ${findNthFibonacciNo(10)}")
}

成员函数

和大部分的 Java 函数相同,我们可以在 声明函数

1
2
3
4
5
6
class User(val firstName: String, val lastName: String) {
// 成员函数
fun getFullName(): String {
return firstName + " " + lastName
}
}

嵌套函数

java 程序中,我们经常需要为了代码的可读性将一些列的行为抽象成一个独特的函数,这些函数往往会被定义为
private 但是往往被复用一次而已。比如下面这个 calculateBMI,对比之下,我个人更喜欢的是嵌套函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Double calculateBMI(Double weightInKg, Double heightInCm) {
return weightInKg / (heightInCm / 100);
}

Double findBodyMassIndex(Double weightInKg, Double heightInCm) {
if (weightInKg <= 0) {
throw new IllegalArgumentException("Weight must be greater than zero")
}
if (heightInCm <= 0) {
throw new IllegalArgumentException("Height must be greater than zero")
}

return calculateBMI(weightInKg, heightInCm);
}
1
2
3
4
5
6
7
8
9
10
11
12
Double findBodyMassIndex(Double weightInKg, Double heightInCm) {
if (weightInKg <= 0) {
throw new IllegalArgumentException("Weight must be greater than zero");
}
if (heightInCm <= 0) {
throw new IllegalArgumentException("Height must be greater than zero");
}

BiFunction<Double, Double, Double> calculateBMI = (w, h) -> w / (h / 100);

return calculateBMI.apply(weightInKg, heightInCm);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun findBodyMassIndex(weightInKg: Double, heightInCm: Double): Double {

if(weightInKg <= 0) {
throw IllegalArgumentException("Weight must be greater than zero")
}
if(heightInCm <= 0) {
throw IllegalArgumentException("Height must be greater than zero")
}

fun calculateBMI(weightInKg: Double, heightInCm: Double): Double {
val heightInMeter = heightInCm / 100
return weightInKg / (heightInMeter * heightInMeter)
}

// 计算 BMI
return calculateBMI(weightInKg, heightInCm)
}

内联函数

内联函数可以在编译器展开,减少 Function Stack 的使用,对于提高性能有 迷之作用
我们想要将一个函数设置为内联函数只需要如下声明即可:

1
2
3
inline fun <T> lock(lock: Lock, body: () -> T): T {
// 函数体
}

只需要在函数的前面增加一个 inline 关键字即可。

扩展函数

想想一个场景,我们需要给某个类型的对象增加一个函数,而这个类型的源码可能是在某个第三方的Jar内,我们仅仅能通过继承来实现我们的需求,而现在 kotlin 可以通过拓展函数来实现。

1
2
3
4
5
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}

这样我们就为了 MutableList<Int> 增加一个新的函数 swap(index1: Int, index2: Int)

值得注意: 扩展是静态解析的,也就是说并不会因为运行时的状态而采用多态的函数进行调用。举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
println(c.foo())
}

printFoo(D())
// "c" not "d"

函数泛型

既然有函数,怎么可能没有泛型呢?Kotlin 的泛型系统比 java 更加复杂,关于这块,我们将在一个独立的章节进行讲解。

1
2
3
fun <T> singletonList(item: T): List<T> {
// 函数体
}

参考文献