我爱编程

函数

2018-06-10  本文已影响0人  一江碎月

定义

  1. 使用 fun 关键字定义一个函数。fun 后为函数名,函数名后为参数列表,参数列表后为冒号,冒号后为返回值类型。

    函数格式
  2. 如果函数返回值为 void,则可以省略冒号及返回值类型。

fun test(a: Int) { // 该函数没有返回值
    println("a = $a")
}

fun test2(b: Int): String { // 该函数返回值类型为 String
    return "$b"
}

参数

  1. 不能使用 var 或 val 修饰

  2. 必须指明类型。如:

fun test(a:Int,b:Int = 3 ) = a+b

fun main(args:Array<String>){
    println(test(2)) //5
    print(test(3,4))  // 7
}

命名参数

在调用函数时,可以使用参数名指定为某个参数传值。

命名参数主要是为了提高代码的可读性 —— 阅读代码时,可以很方便的知道不同的实参是为哪个形参进行赋值。

  1. 参数的顺序可以调换

    fun main(args: Array<String>) {
        println(test(a = 1, c = 2, b = "bb"))
    }
    
    fun test(a: Int, b: String, c: Int) = "a = ${a},b = ${b},c = ${c}"
    
  2. 命名参数的后面所有参数必须是命名参数。如上述代码中,如果改写成如下格式便会出错,因为 b 使用了命令参数,而后面没有使用:

    println(test(1, b = "bb", 2))
    
  3. 命名参数只能在 kt 之间使用,kt 调 java 不支持

默认参数值

kt 中,在声明函数时可以为参数指定默认值。当调用者没有传递该参数时,该参数的值为默认值。

默认参数的主要原因是为了避免太多的重载方法。

  1. 当按常规方法调用函数时,除了最后设置有默认值的参数可以省略,必须为每一个参数进行赋值。可以省略末尾的有默认值的参数,中间的不参省略

    如下示例中,传参时必须为 b 进行赋值,但可以省略 d 的实参。常规调用时,没办法做到只为 a,c 赋值。

    
    fun main(args: Array<String>) {
        println(test(1, "bb",2))
    }
    
    fun test(a: Int, b: String = "bb", c: Int,d:Int=3) = "a = ${a},b = ${b},c = ${c},d=${d}"
    
  2. 命名参数时,可以按任意顺序,省略所有有默认值的参数。如下,在调用时,完全没有考虑顺序,也没有为任何一个有默认值的参数进行传参。

    fun main(args: Array<String>) {
        println(test(c = 2,a = 1))
    }
    
    fun test(a: Int, b: String = "bb", c: Int,d:Int=3) = "a = ${a},b = ${b},c = ${c},d=${d}"
    

表达式函数体

当一个函数的函数体中只有一个表达式的时候,可以变成表达式函数。

所谓的表达式函数指的是:除掉普通函数的大括号以及 return 语句,并使用等号将表达式连接在参数列表后。

同时,也可以省略返回值类型。

fun test(b: Int) = "$b"
fun test2(b:String):String = "$b is b"

在第一个表达式函数中,并没有指定返回值类型。因为函数体只是一个表达式,而表达式的返回值可以经过类型推导得到。


顶层函数

kt 中函数可以不依赖于任何类而单独存在。这些函数被称为顶层函数。

因为 jvm 只能调用类中的方法,因此 kt 被编译后,顶层函数会被编译到一个与文件名相同的类中,并且成为该类中的一个静态函数。

例如 kotlin 中代码如下:

// 文件名为 Demo.kt
package com.demo
fun test(a:Int,b:String) = "${a}+${b}"

经过编译后,会生成 DemoKt 类,而 test 是该类中的一个静态方法。其调用过程如下:

import com.demo.DemoKt; // 导入 kt 文件编译后生成的类
public class Test {
    public static void main(String[] args){
        String s = DemoKt.test(1, "2"); // 调用类中的静态方法
        System.out.println(s);
    }
}

@file:JvmName

指定 kt 文件编译后的类名。

该注解必须使用在文件的第一行,凌驾于 package 之上。如将上述文件修改如下,那么调用时就应该使用 Utils.test ,而不是 DemoKt.test。

@file:JvmName("Utils")
package com.demo
fun test(a:Int,b:String) = "${a}+${b}"

顶层属性

与顶层函数一样,属性也可以不依赖任何类而单独存在。该定义的属性,与普通的类中的属性一样,java 使用时需要通过 getter / setter 进行处理。

@file:JvmName("Utils")
package com.demo
var count = 0
val age = 10

Java 使用时,如下:

Utils.getAge();
Utils.setCount(1);
Utils.getCount();

const

定义常量的顶层属性。

上述定义的顶层属性,都会生成 getter / setter 方法以供 java 调用,并不是 java 中的 public static final 。如果使用 const 修饰的变量转换成 Java 后,就是 public static final。如:

@file:JvmName("Utils")
package com.demo
const val score = 10;

外界调用时,可以直接使用 Utils.score,该值不能被修改。

kt 中无法定义出被 public static 修饰的属性。


可变参数

  1. 使用 vararg 修饰符修饰

  2. 可变参数可定义在任意位置

  3. java 中可以将一个数组直接赋值给一个可变参数,但 kt 中数组必须使用 * 进行解包

  4. 可变参数后面的形参进行赋值时,必须使用命名参数 —— 不然无法识别该实参是给可变参数的,还是给别的形参的,即使类型不同。

// 可变参数可以定义在任意位置
fun test(vararg a: Int, b: Int) {
    val sb = StringBuilder()
    for (index in 0..(a.size - 1)) {
        sb.append("${a[index]} ")
    }
    sb.append(b)
    println(sb.toString())
}

fun main(args: Array<String>) {
    val a: IntArray = intArrayOf(1, 2, 3, 4)
    test(*a, b = 5) // 数组必须被解包
}

中缀调用

使用 infix 关键字修改的函数为中缀函数。

调用中缀函数时可以省略点号和括号,而且中缀函数需要满足以下条件:

// 扩展函数的中缀
infix fun Any.test(a: Int) = println("${a * 2}")

class My {
    // Any 为所有 kt 类的父类,该函数表示任意一个对象都可以调用 add 函数。
    infix fun Any.add(s: String) = "${this}+${s}"

    fun test(s: String) = this add s
    // test2 与 test 效果一样
    fun test2(s:String) = this.add(s)
}

fun main(args: Array<String>) {
    val my = My()
    println(my.test("xxx"))
    println(my.test2("xxx"))
}

注意其中的 test 与 test2 两个函数,都没有省略 this。这是中缀函数要求的:不能省略 this。


局部函数

kt 中可以在函数中嵌套函数。

  1. 局部函数可以访问所在函数的所有参数和变量,但只能是 定义在局部函数前面的参数和变量

    fun test(){
        val b = "name"
        fun name() = println("${b}") // 局部函数
    
        val a = "age" // name 函数无法访问到该变量
        name()
    }
    
  2. 局部函数可以定义在扩展函数中

    fun User.validate() {
        fun vali(name:String,value:String){
            if (value.isEmpty()) {
                throw IllegalArgumentException("${name} 不能为空")
            }else{
                println("正确的")
            }
        }
        vali("id",this.id)
    }
    
    fun main(args: Array<String>) {
        val u1 = User("","11")
        val u2 = User("2","22")
        u2.validate()
        u1.validate() // 该句报错
    }
    

可空类的扩展

扩展函数的接收者类型可以为可空类型

定义成可空类型时,扩展函数中使用 this 可能为 null,所以需要单独判断。

fun main(args:Array<String>){
    var t:Test? = Test()
    t.test()
    t = null
    t.test()
}

class Test
// 类型使用 Test?,表示该方法可以被可空类型的对象调用
fun Test?.test() {
    if (this == null) println("xxx") else println("$this")
}

成员扩展

在一个类中为另一个类定义扩展函数或属性,这样的函数或属性叫成员扩展

  1. 成员扩展不能在类的作用域之外使用

    fun main(args: Array<String>) {
        Outer().test("12345678")    
      //  println("aabbcc".six())  在类的外面无法访问在类中定义的扩展函数 six()
    }
    
    class Outer {
        fun String.six(): Char = toCharArray()[6]
        fun test(s: String) {
            println(s.six())
        }
    }
    
  2. 无法像普通的扩展函数那样,在不改变类的情况下添加新的扩展。如上例,在不改变 Outer 类的情况下,无法为 String 在 Outer 类中定义新的成员扩展。

上一篇下一篇

猜你喜欢

热点阅读