专栏首页一个会写诗的程序员的博客Kotlin 函数编程详解函数Kotlin 开发者社区

Kotlin 函数编程详解函数Kotlin 开发者社区

函数

Functions.gif

函数声明

Functions in Kotlin are declared using the fun keyword:

fun double(x: Int): Int {
    return 2 * x
}

调用函数

Calling functions uses the traditional approach:

val result = double(2)

Calling member functions uses the dot notation:

Sample().foo() // create instance of class Sample and call foo

参数

Function parameters are defined using Pascal notation, i.e. name: type. Parameters are separated using commas. Each parameter must be explicitly typed:

fun powerOf(number: Int, exponent: Int) { ... }

参数默认值

Function parameters can have default values, which are used when a corresponding argument is omitted. This allows for a reduced number of overloads compared to other languages:

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { ... }

Default values are defined using the = after type along with the value.

Overriding methods always use the same default parameter values as the base method. When overriding a method with default parameters values, the default parameter values must be omitted from the signature:

open class A {
    open fun foo(i: Int = 10) { ... }
}

class B : A() {
    override fun foo(i: Int) { ... }  // no default value allowed
}

If a default parameter precedes a parameter with no default value, the default value can be used only by calling the function with named arguments:

fun foo(bar: Int = 0, baz: Int) { ... }

foo(baz = 1) // The default value bar = 0 is used

But if a last argument lambda is passed to a function call outside the parentheses, passing no values for the default parameters is allowed:

fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { ... }

foo(1) { println("hello") } // Uses the default value baz = 1 
foo { println("hello") }    // Uses both default values bar = 0 and baz = 1

Named Arguments

Function parameters can be named when calling functions. This is very convenient when a function has a high number of parameters or default ones.

Given the following function:

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {
...
}

we could call this using default arguments:

reformat(str)

However, when calling it with non-default, the call would look something like:

reformat(str, true, true, false, '_')

With named arguments we can make the code much more readable:

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

and if we do not need all arguments:

reformat(str, wordSeparator = '_')

When a function is called with both positional and named arguments, all the positional arguments should be placed before the first named one. For example, the call f(1, y = 2) is allowed, but f(x = 1, 2) is not.

Variable number of arguments (vararg{: .keyword }) can be passed in the named form by using the spread operator:

fun foo(vararg strings: String) { ... }

foo(strings = *arrayOf("a", "b", "c"))

Note that the named argument syntax cannot be used when calling Java functions, because Java bytecode does not always preserve names of function parameters.

Unit-returning functions

If a function does not return any useful value, its return type is Unit. Unit is a type with only one value - Unit. This value does not have to be returned explicitly:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

The Unit return type declaration is also optional. The above code is equivalent to:

fun printHello(name: String?) { ... }

Single-Expression functions

When a function returns a single expression, the curly braces can be omitted and the body is specified after a = symbol:

fun double(x: Int): Int = x * 2

Explicitly declaring the return type is optional when this can be inferred by the compiler:

fun double(x: Int) = x * 2

Explicit return types

Functions with block body must always specify return types explicitly, unless it's intended for them to return Unit, in which case it is optional. Kotlin does not infer return types for functions with block bodies because such functions may have complex control flow in the body, and the return type will be non-obvious to the reader (and sometimes even for the compiler).

Variable number of arguments (Varargs)

A parameter of a function (normally the last one) may be marked with vararg modifier:

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

allowing a variable number of arguments to be passed to the function:

val list = asList(1, 2, 3)

Inside a function a vararg-parameter of type T is visible as an array of T, i.e. the ts variable in the example above has type Array<out T>.

Only one parameter may be marked as vararg. If a vararg parameter is not the last one in the list, values for the following parameters can be passed using the named argument syntax, or, if the parameter has a function type, by passing a lambda outside parentheses.

When we call a vararg-function, we can pass arguments one-by-one, e.g. asList(1, 2, 3), or, if we already have an array and want to pass its contents to the function, we use the spread operator (prefix the array with *):

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

Infix notation

Functions marked with the infix{: .keyword } keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must satisfy the following requirements:

infix fun Int.shl(x: Int): Int { ... }

// calling the function using the infix notation
1 shl 2

// is the same as
1.shl(2)

Infix function calls have lower precedence than the arithmetic operators, type casts, and the rangeTo operator. The following expressions are equivalent:

  • 1 shl 2 + 3 and 1 shl (2 + 3)
  • 0 until n * 2 and 0 until (n * 2)
  • xs union ys as Set<*> and xs union (ys as Set<*>)

On the other hand, infix function call's precedence is higher than that of the boolean operators && and ||, is- and in-checks, and some other operators. These expressions are equivalent as well:

  • a && b xor c and a && (b xor c)
  • a xor b in c and (a xor b) in c

See the Grammar reference for the complete operators precedence hierarchy. {:.note}

Note that infix functions always require both the receiver and the parameter to be specified. When you're calling a method on the current receiver using the infix notation, you need to use this explicitly; unlike regular method calls, it cannot be omitted. This is required to ensure unambiguous parsing.

class MyStringCollection {
    infix fun add(s: String) { ... }
    
    fun build() {
        this add "abc"   // Correct
        add("abc")       // Correct
        add "abc"        // Incorrect: the receiver must be specified
    }
}

Function Scope

In Kotlin functions can be declared at top level in a file, meaning you do not need to create a class to hold a function, which you are required to do in languages such as Java, C# or Scala. In addition to top level functions, Kotlin functions can also be declared local, as member functions and extension functions.

Local Functions

Kotlin supports local functions, i.e. a function inside another function:

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

Local function can access local variables of outer functions (i.e. the closure), so in the case above, the visited can be a local variable:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

Member Functions

A member function is a function that is defined inside a class or object:

class Sample() {
    fun foo() { print("Foo") }
}

Member functions are called with dot notation:

Sample().foo() // creates instance of class Sample and calls foo

For more information on classes and overriding members see Classes and Inheritance.

Generic Functions

Functions can have generic parameters which are specified using angle brackets before the function name:

fun <T> singletonList(item: T): List<T> { ... }

For more information on generic functions see Generics.

Inline Functions

Inline functions are explained here.

Extension Functions

Extension functions are explained in their own section.

Higher-Order Functions and Lambdas

Higher-Order functions and Lambdas are explained in their own section.

Tail recursive functions

Kotlin supports a style of functional programming known as tail recursion. This allows some algorithms that would normally be written using loops to instead be written using a recursive function, but without the risk of stack overflow. When a function is marked with the tailrec modifier and meets the required form, the compiler optimises out the recursion, leaving behind a fast and efficient loop based version instead:

val eps = 1E-10 // "good enough", could be 10^-15

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

This code calculates the fixpoint of cosine, which is a mathematical constant. It simply calls Math.cos repeatedly starting at 1.0 until the result doesn't change any more, yielding a result of 0.7390851332151611 for the specified eps precision. The resulting code is equivalent to this more traditional style:

val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

To be eligible for the tailrec modifier, a function must call itself as the last operation it performs. You cannot use tail recursion when there is more code after the recursive call, and you cannot use it within try/catch/finally blocks. Currently tail recursion is only supported in the JVM backend.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ReentrantLock 实现原理笔记(一)

    singleThreadSum: 20000100000 multiThreadSumNoLock:19496951532 multiThreadSumUs...

    一个会写诗的程序员
  • Koltin Any 类型Koltin Any 类型

    The root of the Kotlin class hierarchy. Every Kotlin class has Any as a supercla...

    一个会写诗的程序员
  • Kotlin 编码规约

    如需根据本风格指南配置 IntelliJ 格式化程序,请安装 Kotlin 插件1.2.20 或更高版本,转到“Settings | Editor | Code...

    一个会写诗的程序员
  • HTTP权威指南_摘录

    以下内容是摘录自《HTTP 权威指南》(HTTP The Definitive Guide),目前没有中文版,可在google中阅读部分章节。有兴趣想购买的可以...

    meteoric
  • 关于db_files和maxdatafiles的问题(r4笔记第31天)

    昨天在做生产监控的时候发现有个库的表空间不够了,就发邮件给客户的dba去处理,但是得到的反馈是尝试添加的时候发现已经超过了数据文件的最大数限制。这个错误毫无疑问...

    jeanron100
  • Redis 为什么这么快?(9)

    因为单线程已经够用了,CPU不是redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用...

    兜兜毛毛
  • 聊聊flink LocalEnvironment的execute方法

    flink-java-1.6.2-sources.jar!/org/apache/flink/api/java/DataSet.java

    codecraft
  • 聊聊flink LocalEnvironment的execute方法

    flink-java-1.6.2-sources.jar!/org/apache/flink/api/java/DataSet.java

    codecraft
  • 多目标进化算法应用于提高医药数据领域学习器的性能(CS AI)

    原文标题完整翻译:多目标进化算法应用于提高在医药数据领域使用整体特征选择和离散化模型的学习器的性能

    Donuts_choco
  • 第十四届浙江财经大学程序设计竞赛重现赛--A-A Sad Story

    Enterprise_

扫码关注云+社区

领取腾讯云代金券