美文网首页
Kotlin 函数用法入门

Kotlin 函数用法入门

作者: 灯不利多 | 来源:发表于2018-05-22 21:35 被阅读28次

    本文内容:

    • 函数与函数常量
    • 扩展函数
    • 命名参数与默认参数
    • 运算符重载
    • 递归与尾递归

    定义函数

    在 Kotlin 中,用 fun 关键字与返回值类型可以定义函数,跟变量一样,可空函数要在返回类型后边加上问号:

    // 编译时异常,此函数不可为空
    fun hello1(): String = null
    
    // 编译成功
    fun hello2(): String? = null
    

    函数参数格式为 name: type :

    fun hello(name: String) {
      println(name)
    }
    

    没有返回值的函数默认返回的类型是 Unit 类型,Unit 类似 Java 的 void 类型,因此下面两个函数是等价的:

    fun print1(): Unit { }
    fun print2() { }
    

    单行函数

    单行函数可以用等号表示返回值而不用 return 关键字,非单行函数有返回值时必须声明返回值类型:

    // 单行函数
    fun a(k: Int) = k + 10
    
    // 编译时异常,没有声明返回值类型
    fun b() {
      return 10
    }
    
    // 编译成功
    fun c(): Int {
      return 10
    }
    

    成员函数

    成员函数(Member Funciton)指的是定义在类或对象中的函数:

    class KotlinClass {
      
      // 成员函数
      fun memberFunction() { }
    }
    

    局部函数

    Kotlin 允许函数嵌套,而嵌套中的函数也叫局部函数,局部函数的调用按顺序来的,否则会报异常:

    fun a() {
      
      // 编译时异常,顺序不对找不到 b()
      val b1 = b()
      
      // 局部函数
      fun b() { }
      
      // 编译成功
      val b2 = b()
    }
    

    顶层函数

    Kotlin 中的顶层函数指的是没有嵌套在任何结构中的函数:

    class Sample { }
    
    // 顶层函数
    fun foo() { }
    

    命名参数

    在 Kotlin 中能够对参数进行命名,这样做的好处就是提高代码的可读性:

    fun foo(a: Int, b: Int, c: Int) = a + b + c
    var a = foo(
        a = 10,
      b = 20,
      c = 30
    )
    

    使用命名参数还可以改变参数的顺序:

    fun foo(a: Int, b: Boolean) { }
    var var1 = foo(b = false, a = 1)
    var var2 = foo(a = 1, b = false)
    

    但是要注意的是命名参数不能使用在 Java 函数中:

    public class JavaClass {
      void foo(int a, int b);
    }
    
    class KotlinClass {
      fun bar() {
        // 编译时异常,命名参数不能使用在 Java 函数中
        JavaClass().foo(a = 10, b = 20)
      }
    }
    

    参数默认值

    Kotlin 允许在构造方法中给参数赋默认值。这样做的好处就是当同名函数有多个变量时我们不需要变体,只需要给参数赋默认值即可:

    // Java 的函数变体写法
    public void foo() { }
    public void foo(String a) { }
    public void foo(String a, Boolean b) { }
    
    // Kotlin 的的函数变体写法
    fun foo(a: String = "", b: Boolean = false) { }
    

    扩展函数

    Java 中的 String 有 startsWith 方法,但是并没有 reverse() 反转方法,这时候如果你向反转函数往往是写一个新的函数,然后将字符串传进去,但是用 Kotlin 的扩展函数则只需要写一个名为 String.reverse() 的函数即可:

    // 不是扩展函数
    fun reverse(str: String) { }
    reverse("反转")
    
    // 扩展函数
    fun String.rever() { }
    "反转".reverse()
    

    空值的扩展函数

    甚至空值也可以有扩展函数,这是因为在 Kotlin 中 null 属于 Any 类型,而 Any 类型是全部类的超类,这样以后变量为空时也不会报空指针:

    fun Any?.safeEquals(other: Any?): Boolean {
      // 这里的 this 指的是下面调用 safeEquals 的 null
    }
    

    多重返回值

    Kotlin 支持返回两个或三个参数:

    fun a(): Pair<Int, Int> {
      return Pair(1, 2)
    }
    
    fun b(): Triple<Int, Int, Int> { 
        return Triple(1, 2 3)
    }
    

    中缀函数

    中缀(infix)指的是放在变量中间,比如我们在讲值域的时候讲到的 to 就是一个中缀函数,而定义中缀函数只需要在 fun 关键字前加 infix 即可,要注意的是中缀函数必须是成员函数或扩展函数:

    infix fun String.concat(other: String): String {
      return this + other
    }
    
    "a" concat "b"
    

    运算符

    运算符是使用一个符号名的函数。在 Kotlin 中,许多内置运算符是真的函数调用。比如,访问数组元素的操作是用的函数:

    val array = arrayOf(1, 2)
    val element = array[0]
    

    运算符重载

    Koltin 中的许多运算符都是用函数实现的,比如取数组元素的操作 [0] 等价于 get(0) 。这意味着我们可以对运算符进行重载。:

    var a = "a" + ("b")
    
        // 结果是 ab
        println(a)
    
        operator fun String.plus(other: Any?): String {
            return "cc"
        }
    
        a = "a".plus("b")
        println(a)
    

    运算符重载指的是用运算符定义函数。

    一般来说,编程语言趟某个地方在允许无运算符过载规模穿过允许大多数任何字符被使用的。在 Java 中,运算符集合函数是固定由语言,并且开发者无法自己添加。所以 Java 坐在规模的远左边。Scala 则不然,是更许可并允许你虚拟任何组合。所以,它坐在规模的对立面。

    哪个更好取决于你的立场。不允许运算符过载意味着开发者将无法滥用运算符创建迟钝的函数名。另一方面,允许许多类型的运算符被使用意味着强大的领域专用语言可能被创建为了一些特殊的问题。

    Kotlin 对此采取了中立的态度,允许运算符重载以 一个固定的和控制的行为。这个有一个可以用作函数的固定的运算符列表,不过不允许任意组合。要创建这样一个函数,该函数必须使用 operator 关键字前缀。并用该运算符的英语等价名定义。

    全部运算符有一个用作运算符重载的预定义英语等价名。编译器会重写该运算符的用法以进行函数调用。

    运算符只能以成员函数或扩展函数定义。

    比如刚才说到的矩阵,我们要求将好处从运算符重载,可以以下面的形式定义:

    class Matrix(val a: Int, val b: Int, val c: Int, val d: Int) {
      operator fun plus(matrix: Matrix): Matrix {
        return Matrix(a + matrix.a, b + matrix.b, c + matrix.c, d + matrix.d)
      }
    }
    

    这是一个只允许两个 x 矩阵的简单例子。

    我们定义了一个 plus 函数。这将实现矩阵相加。注意如何这个函数是标识用 operator 关键字在 fun 关键字前。同样的,如上一章提到的,参数必须标识用 val 以用在成员函数里面。

    给定这样一个类,我们可能执行代码以下面的形式:

    val m1 = Matrix(1, 2, 3, 4)
    val m2 = Matrix(5, 6, 7, 8)
    val m3 = m1 + m2
    

    字面函数

    在 Koltin 中,函数可以不用声明就能直接赋值给变量,这种函数叫字面函数(Function Literal),声明为字面函数的变量只有在调用的时候才会运行该函数:

    var print1 ={ println("hello") }
    var print2 = {msg: String -> println(msg) }
    
    // 调用后括号内的操作才会进行
    print1()
    

    标准库函数

    Apply

    在 Apply 代码块中你可以对对象实例进行初始化的操作,apply 会返回该实例,然后你可以继续用该实例进行其他操作:

    // 不用 apply
    classA.a = 5
    classA.foo()
    
    // apply
    classA.apply{ a = 5 }.foo()
    

    Let

    当一个对象调用 let 时可以在 let 代码块中接收到该对象实例,而 let 代码块赋值的变量则为 let 代码块中的返回结果:

    val a = arrayOf("1", "2", "3")
    val b = "123".let{
      321
    }
    println(a)
    

    let 可以接收到调用 let 的

    Run

    假如你想要对变量进行一些操作后返回该变量,而又不想使用一个中间变量,那你可以考虑使用 run 扩展函数:

    val a = arrayOf("1", "2", "3")
    val a = "123".run{
      321
    }
    println(a)
    

    这个好处就是我们不用赋值原来的途径到一个中间变量

    避开 Kotlin 标识符

    一些 Kotlin 关键字是有效标识符在 Java,比如 object、in 和 when。如果你需要调用一个 Java 库方法或字段与一个其中的名字,你可以依然做一些通过包装名字用单引号( ' )。

    比如,考虑一个 Java 库定义下面的类和函数:

    public class Date {
      public void when(str: String) { ... }
    }
    

    如果这个发生,那么它可以包含类似

    data.'when'("2016")
    Chcked exceptions
    

    如之前提到的,Kotlin 不会检查异常。所以有检查异常的 Java 方法会以同样的方式对象如方法不。

    比如,方法 createNewFile 定义在文件:

    public boolean createNewFile() throws IOException {}
    

    在 Java,这意味着我们需要一个 try...catch 语句块。在 Kotlin,我们则不用。

    Java void 方法

    我们知道现在 void 在 Java 是类似于 Kotlin 的 Unit。任何 void Java 方法是对待成 Unit 返回函数。

    从 Java 到 Kotlin

    只是作为 Java 可以用于无缝在 Kotlin,Kotlin 可以只是容易用从你的 Java 代码(待补充)

    顶层函数

    JVM 不支持顶层函数。因此 Kotlin 实现顶层函数的方式是创建一个 Java 类与包名。这个函数是然后定义作为 Java 方法在这个类中,哪个我们必须实例化在用之前。

    比如,下面的顶层函数:

    package com.packt.chapter4
    fun cube(n: Int): Int = n * n * n
    

    @JvmOverloads

    要想让 Kotlin 中有默认参数值的函数在 java 中使用就要用 JvmOverloads 注解,否则在 Java 代码中会报编译时异常:

    class KotlinClass {
      @JvmOverloads
      fun foo(a: String = "a'", b: Boolean = false) { }
      
      fun bar(a: String = "a", b: Boolean = false) { }}
    }
    
    private void javaFunction() {
      new KotlinClass().foo();
      
      // 编译时异常,没有默认参数值
      new KotlinClass().bar();
    }
    

    无论如何,你可以通知 Kotlin 编译器生成该函数作为 Java 静态方法。要做到这个,我们要用 @JvmStatic 标记该方法

    object Console {
      @JvmStatic fun clear() : Unit { }
    }
    

    现在这个方法可以调用从 Java 使用 INSTANCE 在之前,不过同样直接从这个类

    // Java 代码
    Console.clear()
    

    消除命名

    JVM 不支持泛型在字节码中。这意味着当你有一个类型比如一个泛型 List,List<String> 和 List<Int> 是两者都编译进入同样在代表下面。这是因为一个问题当这里是一个冲突在函数前面之后编译,比如

    fun println(array: Array<String>): Unit {}
    fun println(array: Array<Long>): Unit {}
    

    他们将会有同样的函数签名。

    Kotlin 能够区分在两个函数,如果我们明确要用同样的名字,我们可以,只需要表明编译器什么名字它应该使用在编译时,我们用 @JvmName 注解表明:

    @JvmName("printlnStrings")
    fun println(array: Array<String>): Unit {}
    
    @JvmName("printlnLongs")
    fun println(array: Array<Long>): Unit {}
    

    这些函数是可访问在 Kotlin 作为之前,与

    @Throws

    你在 Kotlin 代码中主动抛出的异常在 Java 中默认是不会被编译器提醒处理的,如果你想要让 Java 处理该异常,则可以用 @Throws 注解:

    class KotlinClass {
      @Throws(FileNotFoundException::class)
      fun foo() { }
      
      fun bar() { }
      
      // @Throws 注解在 Kotlin 中依然不会要求捕获函数
      var f = foo()
    }
    
    private void javaFunction() {
      new KotlinClass().bar();
      
      // 编译时异常,需要捕获异常
      new KotlinClass().foo();
    }
    

    总结

    函数是所有现代编程语言的基石,并且完全特性的范围 Kotlin 提供的允许开发者更富有表达力的还写少代码。如我们看到在下一章,函数是关键解锁强大成语显示高阶函数。

    相关文章

      网友评论

          本文标题:Kotlin 函数用法入门

          本文链接:https://www.haomeiwen.com/subject/nxnkjftx.html