通过例子来学习如何高效的在Kotlin中定义使用函数。

1、命名参数、默认参数、顶层函数、扩展函数

* 展示几种创建集合类的方法和扩展的简单操作集合类的方法

fun createCollection() {
    val set = hashSetOf(1, 12, 26)
    println("hashSetOf -- > ${set.javaClass}")
    val list = arrayListOf(12, 24, 66)
    println("arrayListOf -- > ${list.javaClass}")
    val map = hashMapOf(1 to "one", 2 to "two", 3 to "three")
    println("hashMapOf -- > ${map.javaClass}")

    val list1 = listOf("dsa", "ccc", "ddd")
    println("listOf -- > ${list1.javaClass}")
}


fun easyOptCollection() {
    val strings = listOf("ss", "this", "is", "string ArrayList")
    println(strings.last())

    val numbers = listOf(1, 200, 20, 30)
    println(numbers.max())
}

  Kotlin并没有采用它自己的集合类,而是采用标准的Java集合类,这样Kotlin就能与Java交互。 

再看一个示例:

fun demand() {
    val list = listOf(1, 2, 25)
    println(list)
}

* 上面函数直接输入 list 是调用了集合的默认的toString方法,为了动态修改输入的样子,下面的几个函数是
* 自己的扩展,再扩展中探讨如何让Kotlin 的方法更简单 更高效 更舒服

下面首先按照Java的习惯和风格定义一个自定义格式化输出集合的方法

/**
 * 通过再元素中间加分隔符,在最前面加前缀,再最后面加后缀把集合转成可输出的String
 * @param collection  集合
 * @param separator 分隔符
 * @param prefix 前缀
 * @param postfix 后缀
 */
fun <T> joinToString(collection: Collection<T>,
                     separator: String,
                     prefix: String,
                     postfix: String): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator) //第一个元素之前不用加分隔符
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
* 为了提高代码的可读性
* Kotlin支持  命名参数 --> 调用函数时 显式地指定参数名称  (牛叉的是:显示指定名称之后就可以打乱参数的传递顺序了)
* 注意:为了避免混淆指明了一个参数的名称之后,后面的参数必须都要标明名称
* 警告:使用Kotlin 调用Java函数时,不能采用命名参数

fun callExplicitly() {
    val list = listOf(1, 3, 5)
    println(joinToString(list, prefix = "{", separator = "\\", postfix = "}"))
}
* 为了避免像Java那样过多的重载与重复
* Kotlin 支持默认参数值 --> 在声明函数的时候,指定参数的默认值,在调用的时候不传该参数时就使用默认的参数
* 这样就可以避免创建很多的重载函数
*
* Java中如果想调用指定默认参数的函数必须全部传递参数,如果想像在Kotlin中一样使用其省略参数的调用方式就需要给
* Kotlin中声明的 指定默认参数的函数 添加“@JvmOverloads”注解,原理是编译器会带有"@JvmOverloads"的方法自动生成重载函数

@JvmOverloads
fun <T> joinToStringWithDefaultParams(collection: Collection<T>,
                                      separator: String = ",",
                                      prefix: String = "",
                                      postfix: String = ""): String {
    return joinToString(collection, separator = separator, prefix = prefix, postfix = postfix)
}

fun callWithDefaultParams() {
    val list = listOf(1, 3, 5)
    println(joinToStringWithDefaultParams(list))
    println(joinToStringWithDefaultParams(list, "-"))
    println(joinToStringWithDefaultParams(list, "-", "【", "】"))
    println(joinToStringWithDefaultParams(list, postfix = "!"))
}

在Java 中一些无法从属任何类又可能会被很多类频繁的调用的方法通常会抽取到专门的一个类中,以 public static final sss()最终会形成包含很多这种方法的工具类

* 在Kotlin中根本不需要去创建这样无意义的类。
* 可以把这样的不从属于任何类的函数放到代码文件的顶层,这些放在文件顶层的函数依然是包内的成员
* 如果要从外部访问它,直接导入包就可以用,不要额外包一层类名
*
* 其实编译器会.kt 文件编译成Java类,类名为.kt文件名+Kt 例如:join.kt ---> JoinKt.class
* 因此在Java中调用顶层函数也很简单直接导入编译的包含顶层文件的类就行了
*
* 如果要改变包含Kotlin 顶层函数的文件被编译生成的类名,需要为这个文件添加 “@JvmName”的注解,将其放到文件的开头,位于包名的前面
* 比如本类执行的名称“@file:JvmName("StringsFunctions")”

import com.mauiie.kotlin.chapter2fun.K4ExtendPropertyKt;
import com.mauiie.kotlin.chapter2fun.StringsFunctions;
public class JavaCallTest {
    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<>(5);
        strings.add("dsada");
        strings.add("adsa");
        strings.add("jklj");
        strings.add("dsada");
        System.out.println(StringsFunctions.joinToStringWithDefaultParams(strings));

        TopFunAndProperty.performOperation();
        TopFunAndProperty.performOperation();
        TopFunAndProperty.reportOperationCount();

        System.out.println(TopFunAndProperty.getUNIX_LINE_SEPARATOR());
        System.out.println(TopFunAndProperty.UNIX_LINE_SEPARATOR_CONSTANTS);
        System.out.println(TopFunAndProperty.getOpCount());

        System.out.println(K3ExtendFunAndPropertyKt.lastChar_("this is a"));
        /**
         * 扩展函数和扩展方法在Java中调用的时候都必须显示的调用
         */
        String testStr = "test";
        System.out.println(K4ExtendPropertyKt.getLastChar(testStr));
        System.out.println(K4ExtendPropertyKt.lastChar(testStr));

        Button button = new Button();
        System.out.println(button.getCurrentState());
        ;
    }

* 在使用JDK、Android的时候,有时会面临代码不能转成Kotlin的时候,Kotlin支持扩展函数让Kotlin支持不能转的代码
* 理论上来说扩展函数非常简单,它就是一个类的成员函数,不过定义在类的外面。
* 做法很简单:把要扩展的类或接口的名称,放到即将添加的函数前面。

* 这个类的名称叫 “接收者类型”;用来调用这个扩展函数的那个对象叫做“接收者对象”
* 接收者类型 是由扩展函数定义的,接收者对象是该类型的一个实例

fun String.lastChar_(): Char = this.get(this.length - 1)
* String 是定义的接收者类型
* this  是定义的接收这对象  也就是String的一个实例
* 在扩展函数中,可以像其他成员函数一样访问类的其他变量和属性,就好像是在这个类自己的方法中访问他们一样。
* 注意:扩展函数不能打破类的封装性。???????
*      和类的成员变量不同的是,扩展函数不能访问类的私有或者受保护的成员。

* 在扩展函数中,可以像其他成员函数一样使用“this”,而且也可以像其他成员函数一样省略它

fun String.easyLastChar(): Char = get(length - 1)

fun main(args: Array<String>) {
    //定义好扩展函数之后就可以像普通的成员函数一样去使用了
    println("kotlin".lastChar_())
    println("test".easyLastChar())
}

* 在Kotlin中,重写成员函数是很平常的事情,但是,不能重写扩展函数。

* 但是,但是, 不能重写扩展函数
* 扩展函数并不是类的一部分,它是声明在类之外的。尽管可以给基类和子类都分别定义一个同名的扩展函数,当这个函数被调用时
* 它是由该变量的静态类型所决定的,而不是这个变量的运行时类型

* 注意: 如果一个类的成员函数和扩展函数有相同的签名,成员函数往往会被优先使用。
* 记住: 如果添加一个和扩展函数一样名字的成员函数,那么对应类定义的消费者将会重新编译代码,这将会改变它的意义开始指向新的成员函数!

open class View {
    open fun click() = println("View clicked")
}
class Button : View() {
    override fun click() = println("Button clicked")
}

fun View.showOff() = println("View showOff")
fun Button.showOff() = println("Button showOff")

fun main(args: Array<String>) {
    val v: View = Button()
    val v2: View = View()
    val v3 = View()
    //具体调用哪个方法是由view的实际值来决定的,
    v.click()
    v2.click()
    v3.click()
    //但是, 不能重写扩展函数
    v.showOff()
    v2.showOff()
    v3.showOff()

    val button = Button()
    button.showOff()
}

  执行结果:

Button clicked
View clicked
View clicked
View showOff
View showOff
View showOff
Button showOff

Kotlin中直接在文件函数叫做顶层的函数,再Kotlin中看起来不从属任何类从属于包直接导入可以使用,但是从编译之后的字节码看会自动编译xxxKT.Java,顶层函数从属于编译的文件类

@file:JvmName("TopFunAndProperty")
package com.mauiie.kotlin.chapter2fun

/**
 * 这样就声明了一个顶层属性
 */
var opCount = 0 //会生成getter和setter
/**
 * 声明一个顶层函数
 */
fun performOperation() {
    opCount++
}

fun reportOperationCount() {
    println("Operation performed $opCount times ")
}

val UNIX_LINE_SEPARATOR = "\n" //只会生成getter
const val UNIX_LINE_SEPARATOR_CONSTANTS = "\n" //相当于Java中的 public static final String ...l

/**
 * 现在我们可以将joinToString 函数写出终极状态了 ---> 作为扩展函数的工具函数
 */
fun <T> Collection<T>.joinToString(
        separator: String,
        prefix: String,
        postfix: String
): String {
    val result = StringBuilder(prefix)

    // withIndex 省去了this  this.withIndex()
    for ((index, element) in withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)

    return result.toString()
}

  2、扩展属性

* 扩展属性提供了一种方法,用来扩展类的API,可以用类访问属性,用的是属性语法而不是函数的语法。
* 尽管它被称为属性,但是他们可以没有任何状态,因为没有适合的地方来存储它,不可能给现有的Java对象实例添加额外的字段。
* 但有时段语法仍然是便于使用的。

先扔出来一个扩展函数

fun String.lastChar(): Char = get(length - 1)

* 上面是为String 扩展的方法 lastChar() 现在把它转成属性试试
* 可以看到扩展属性也像接收者的一个普通成员属性一样。
* 这里,必须定义getter函数,因为没有支持字段,因此没有默认的getter实现。
* 同理,初始化也不可以:因为没有地方可以存储初始值

val String.lastChar: Char
    get() = get(length - 1)


var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }

fun main(args: Array<String>) {
    println("Kotlin".lastChar)

    val sb: StringBuilder = StringBuilder("Kotlin")
    sb.lastChar = 'a'
    println(sb.lastChar)
}

 注意 

* 如果要从Java中访问扩展属性,应该显示地调用它的getter函数  K4ExtendPropertyKt.getLastChar("Java")
*对于定义的扩展函数,不会自动在整个项目中生效,使用的时候需要像其他函数和类一样导入
 * 可以使用 as 关键字来修改导入的类或者名称   同样在Java也可以使用定义好的扩展函数 参照 java.JavaCallKotlin

package com.mauiie.kotlin.chapter2fun

//import com.mauiie.kotlin.chapter2fun.*
import com.mauiie.kotlin.chapter2fun.lastChar_ as last

val c = "Kotlin".last()

 3、可变参数、中缀表示、解构声明

* a.可变参数的关键字 vararg,可以用来声明一个函数可能有任意数量的参数
* b.一个中缀表示法,当你在调用一些只有一个参数的函数时,使用它会让代码更简练
* c.解构声明,用来把一个单独的组合值展开到多个变量中

首先看可变参数 vararg的使用:

fun kebiancanshu() {
    val list = listOf(1, 45, 36)
   //listOf源码
    //public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()
}

* 上面可以看在Kotlin使用了 vararg 代替了 Java中的... 声明了可变参数
* 与Java不用的一点是如果在传入参数是已经包装在数组中的参数时,在Java中会原样传递数组,而Kotlin则要求你显示着解包数组,以便于每个数组元素能作为单独的参数来调用
* 从技术角度讲这个功能被称为"展开运算符",而在使用的时候不过是在参数前面放一个 “*”

fun testVararg(args: Array<String>) {
    val list = listOf("test", * args)
    println(list)
}

fun main(args: Array<String>) {
    testVararg(listOf("ds", "dsa", "111").toTypedArray())  //toTypedArray Returns a *typed* array containing all of the elements of this collection.
}
* 声明map的代码中的to,不是内置解构,而是一种特殊函数调用,被称为中缀调用。
* 在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称参数之间的。
* 1 to "one"  和 1.to("one")是等价的

fun testMap() {
    val map = mapOf(1 to "one", 2 to "two", 3.to("three"))
// 源码   public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> = if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()

    val list = listOf(1.45)
    //这也是一种解构声明
    for ((index, element) in list.withIndex()) {
        println("this $index is $element")
    }
}

* 中缀调用可以与之哟普一个参数的参数的函数一起使用,无论是普通的函数还是扩展函数。
* 要允许使用中缀符号调用函数,需要使用infix修饰符来标记它。
* 下面是一个简单的to函数的声明

* Pair 是Kotlin标准库中的类,它表示已对元素。Pair和to 都使用了泛型,这里为了简单都省略了他们

infix fun Any.to(other: Any) = Pair(this, other)

fun testPair() {
    val (number, name) = 1 to "one"
    println("this is $number and $name")
}

4、局部函数

为了让代码更简洁,Kotlin提供了局部函数的功能:
* 局部函数指的是 可以在函数中嵌套这些提取的函数,这样既可以获得所需的结构,也无须额外的语法开销

先看下一个简单的例子,稍后使用局部函数改造,体验局部函数:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user[${user.id}] with empty name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user[${user.id}] with empty address")
    }
    println("save user successful!")
}

* 提取局部函数避免重复

fun saveUser1(user: User) {
    fun validate(user: User, value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user[${user.id}] with empty $fieldName ")
        }
    }
    validate(user, user.name, "Name")
    validate(user, user.address, "address")
    println("save user successful!")
}

* 可以在局部函数总直接访问外部函数的参数!!

fun saveUser2(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            //可以在局部函数总直接访问外部函数的参数!!
            throw IllegalArgumentException("Can't save user[${user.id}] with empty $fieldName ")
        }
    }
    validate(user.name, "Name")
    validate(user.address, "address")
    println("save user successful!")
}

* 提取逻辑到扩展函数

fun User.validateBeforSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user[${this.id}] with empty $fieldName ")
        }
    }
    validate(name, "Name")
    validate(address, "Address")
}