Kotlin教程(二)函数

写在开头:本人打算开始写一个Kotlin系列的教程,一是使本身记忆和理解的更加深入,二是能够分享给一样想学习Kotlin的同窗。系列文章的知识点会以《Kotlin实战》这本书中顺序编写,在将书中知识点展现出来同时,我也会添加对应的Java代码用于对比学习和更好的理解。java

Kotlin教程(一)基础
Kotlin教程(二)函数
Kotlin教程(三)类、对象和接口
Kotlin教程(四)可空性
Kotlin教程(五)类型
Kotlin教程(六)Lambda编程
Kotlin教程(七)运算符重载及其余约定
Kotlin教程(八)高阶函数
Kotlin教程(九)泛型正则表达式


在Kotlin中建立集合

上一章咱们已经使用setOf 函数建立一个set了。一样的,咱们也能够用相似的方法建立一个list或者map:数据库

val set = setOf(1, 2, 3)
val list = listOf(1, 2, 3)
val map = mapOf(1 to "one", 2 to "two")
复制代码

to 并非一个特殊的结构,而是一个普通函数,在后面会继续探讨它。 有没有想过这里建立出来的set、list、map究竟是什么类型的那?能够经过.javaClass 属性获取类型,至关于Java中的getClass() 方法:编程

println(set.javaClass)
println(list.javaClass)
println(map.javaClass)

//输出
class java.util.LinkedHashSet
class java.util.Arrays$ArrayList
class java.util.LinkedHashMap
复制代码

能够看到都是标准的Java集合类,Kotlin没有本身专门的集合类,是为了更容易与Java代码交互,当从Kotlin中调用Java函数的时候,不用转换它的集合类来匹配Java的类,反之亦然。 尽管Kotlin的集合类和Java的集合类彻底一致,但Kotlin还不止于此。举个例子,能够经过如下方法来获取一个列表中最后一个元素,或者获得一个数字列表的最大值:数组

val strings = listOf("first", "second", "fourteenth")
println(strings.last())
val numbers = setOf(1, 14, 2)
println(numbers.max())

//输出
fourteenth
14
复制代码

或许你应该知道last()max() 在Java的集合类中并不存在,这应该是Kotlin本身扩展的方法,能够你要知道上面咱们打印出来的类型明确是Java中的集合类,但在这里调用方法的对象就是这些集合类,又是怎么作到让一个Java中的类调用它自己没有的方法那?在后面咱们讲到扩展函数的时候你就会知道了!bash

让函数更好调用

如今咱们知道了如何建立一个集合,接下来让咱们打印它的内容。Java的集合都有一个默认的toString 实现,但它的何世华的输出是固定的,并且每每不是你须要的样子:app

val list = listOf(1, 2, 3)
println(list) //触发toString的调用

//输出
[1, 2, 3]
复制代码

假设你须要用分号来分隔每个元素,而后用括号括起来,而不是采用默认实现。要解决这个问题,Java项目会使用第三方库,好比Guava和Apache Commons,或者是在这个项目中重写打印函数。在Kotlin中,它的标准库中有一个专门的函数来处理这种状况。 可是这里咱们先不借助Kotlin的工具,而是本身写实现函数:ide

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()
}
复制代码

这个函数是泛型,它能够支持元素为任意类型的集合。让咱们来验证一下,这个函数是否可行:函数

val list = listOf(1, 2, 3)
println(joinToString(list, ";", "(", ")"))

//输出
(1;2;3)
复制代码

看来是可行的,接下来咱们要考虑的是如何修改让这个函数的调用更加简洁呢?毕竟每次调用都要传入四个参数也是挺麻烦的。工具

命名参数

咱们关注的第一个问题就是函数的可读性。就以joinToString 来看:

joinToString(list, "", "", "")
复制代码

你能看得出这些String都对应什么参数吗?可能必需要借助IDE工具或者查看函数说明或者函数自己才能知道这些参数的含义。 在Kotlin中,能够作的更优雅:

println(joinToString(list, separator = "", prefix = "", postfix = ""))
复制代码

当你调用一个Kotlin定义的函数时,能够显示得标明一些参数的名称。若是在调用一个函数时,指明了一个参数的名称,为了不混淆,那它以后的全部参数都须要标明名称。

ps: 当你在Kotlin中调用Java定义的函数时,不能采用命名参数。由于把参数名称存到 .class文件是Java8以及更高版本的一个可选功能,而Kotlin须要保持和Java6的兼容性。

可能到这里你只是以为命名参数让函数便于理解,可是调用变得复杂了,我还得多写参数的名称!别急,与下面说的默认参数相结合时,你就知道命名参数的好了。

默认参数值

Java的另外一个广泛存在问题是:一些类的重载函数实在太多了。这些重载大可能是为了向后兼容,方便API的使用者,最终致使的结果是重复。 在Kotlin中,能够在声明函数的时候,指定参数的默认值,这样能够避免建立重载的函数。让咱们尝试改进一下前面的joinToString 函数。在大多数状况下,咱们可能只会改变分隔符或者改变先后缀,因此咱们把这些设置为默认值:

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()
}
复制代码

如今在调用一下这个函数,能够省略掉有默认值的参数,效果就像在Java中声明的重载函数同样。

println(joinToString(list))
println(joinToString(list, ";"))

//输出
1,2,3
1;2;3
复制代码

当你使用常规的调用语法时,必须按照函数申明中定义的参数顺序来给定参数,能够省略的只有排在末尾的参数。若是使用命名参数,能够省略中的一些参数,也能够以你想要的任意 顺序只给定你须要的参数:

//打乱了参数顺序,而且separator参数使用了默认值
println(joinToString(prefix = "{", collection = list, postfix = "}"))

//输出
{1,2,3}
复制代码

注意,参数的默认值是被编译到被调用的函数中,而不是调用的地方。若是你改变了参数默认值并从新编译这个函数,没有给参数从新赋值的调用者,将会开始使用新的默认值。

Java没有参数默认值的概念,当你从Java中调用Kotlin函数的时候,必须显示得指定全部参数值。若是须要从Java代码中调用也能更简便,可使用@JvmOverloads注解函数。这个指示编译器生成Java的重载函数,从最后一个开始省略每一个函数。例如joinToString函数,编译器就会生成以下重载函数: public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) public static final String joinToString(@NotNull Collection collection, @NotNull String separator) public static final String joinToString(@NotNull Collection collection) 所以,当你项目同时存在Java和Kotlin时,对有默认参数值的函数习惯性地加上@JvmOverloads注解是个不错的作法。

消除静态工具类:顶层函数和属性

Java做为一门面对对象的语言,须要全部的代码都写做类的函数。但实际上项目中总有一些函数不属于任何一个类,最终产生了一些类不包含任何状态或者实例函数,仅仅是做为一堆静态函数的容器。在JDK中,最明显的例子应该就是Collections了,还有你的项目中是否是有不少以Util做为后缀的类? 在Kotlin中,根本不须要去建立这些无心义的类,你能够把这些函数直接放到代码文件的顶层,不用从属于任何类。事实上joinToString函数以前就是直接定义在Join.kt 文件。

package com.huburt.imagepicker

@JvmOverloads
fun <T> joinToString(...): String {...}
复制代码

这会怎样运行呢?当编译这个文件的时候,会生成一些类,由于JVM只能执行类中的代码。当你在使用Kotlin的时候,知道这些就够了。可是若是你须要从Java中来调用这些函数,你就必须理解它将怎样被编译,来看下编译后的类是怎样的:

package com.huburt.imagepicker

public class JoinKt {
	public static String joinToString(...){...}
}
复制代码

能够看到Kotlin编译生成的类的名称,对应于包含函数的文件名称,这个文件中的全部顶层函数编译为这个类的静态函数。所以,当从Java调用这个函数的时候,和调用任何其余静态函数同样简单:

import com.huburt.imagepicker.JoinKt

JoinKt.joinToString(...)
复制代码

修改文件类名

是否是以为Kt结尾的类使用起来很别扭,Kotlin提供了方法改变生成类的类名,只须要为这个kt文件添加@JvmName的注解,将其放到这个文件的开头,位于包名的前面:

@file:JvmName("Join") //指定类名
package com.huburt.imagepicker

@JvmOverloads
fun <T> joinToString(...): String {...}
复制代码

如今就能够用新的类名调用这个函数:

import com.huburt.imagepicker.Join

Join.joinToString(...)
复制代码

顶层属性

和函数同样,属性也能够放到文件的顶层。从Java的角度来看就是静态属性,没啥特别的,并且因为没有了类的存在,这种属性用到的机会也很少。 须要注意的是顶层函数和其余任意属性同样,默认是经过访问器暴露给Java使用的(也就是经过getter和setter方法)。为了方便使用,若是你想要把一个常量以public static final 的属性暴露给Java,能够用const 来修饰属性:

const val TAG = "tag"
复制代码

这样就等同与Java的:

public static final String TAG = "tag"
复制代码

给别人的类添加方法:扩展函数和属性

Kotlin的一大特点就是能够平滑的与现有代码集成。你能够彻底在原有的Java代码基础上开始使用Kotlin。对于原有的Java代码能够不修改源码的状况下扩展功能:扩展函数。这一点是我认为Kotlin最强大的地方了。 扩展函数很是简单,它就是一个类的成员函数,不过定义在类的外面。为了方便阐述,让咱们添加一个方法,来计算一个字符串的最后一个字符:

package strings

	//String ->接收者类型               //this ->接收者类型
fun String.lastChar(): Char = this.get(this.length - 1)
复制代码

你所要作的,就是把你要扩展的类或者接口的名称,放到即将添加的函数前面,这个类的名称被称为接收者类型;用来调用这个扩展函数的那个对象,叫作接收者对象。 接着就能够像调用类的普通成员函数同样去调用这个函数了:

println("Kotlin".lastChar())
//输出
n
复制代码

在这个例子中,String就是接收者类型,而“Kotlin”就是接收者对象。 在这个扩展函数中,能够像其余成员函数同样用this,也能够像普通函数同样省略它:

package strings

fun String.lastChar(): Char = get(length - 1) //省略this调用string对象其余函数
复制代码

导入扩展函数

对于你定义的扩展函数,它不会自动的在整个项目范围内生效。若是你须要使用它,须要进行导入,导入单个函数与导入类的语法相同:

import strings.lastChar

val c = "Kotlin".lastChar()
复制代码

固然也能够用*表示文件下全部内容:import strings.*

另外还可使用as 关键字来修改导入的类或则函数的名称:

import strings.lastChar as last

val c = "Kotlin".last()
复制代码

在导入的时候重命名能够解决函数名重复的问题。

从Java中调用扩展函数

实际上,扩展函数是静态函数,它把调用对象做为函数的第一个参数。在Java中调用扩展函数和其余顶层函数同样,经过.kt文件生成Java类调用静态的扩展函数,把接收者对象传入第一个参数便可。例如上面提到的lastChar扩展函数是定义在StringUtil.kt中,在Java中就能够这样调用:

char c = StringUtilKt.lastChar("Java")
复制代码

做为扩展函数的工具函数

如今咱们能够写一个joinToString 函数的终极版本了,它和你在Kotlin标准库中看到的如出一辙:

@JvmOverloads
fun <T> Collection<T>.joinToString(
        separator: String = ",",
        prefix: String = "",
        postfix: String = ""
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) { //this是接收者对象,即T的集合
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

//使用
println(list.joinToString())
println(list.joinToString(";"))
println(list.joinToString(prefix = "{", postfix = "}"))
复制代码

将原来的参数Collection,提出来,做为接收者类型编写的扩展函数,使用方法也像是Collection类的成员函数同样了(固然Java调用仍是静态方法,第一个参数传入Collection对象)。

不可重写的扩展函数

先来看一个重写的例子:

//Kotlin中class默认是final的,若是须要继承须要修饰open,函数也相同
open class View {
    open fun click() = println("View clicked")
}

class Button : View() { //继承
    override fun click() = println("Button clicked")
}
复制代码

当你声明了类型为View的变量,那它能够被赋值为Button类型的对象,由于Button是View的一个子类。当你在调用这个变量的通常函数,好比click的时候,若是Button复写了这个函数,name这里将会调用到Button中复写的函数:

val view: View = Button()
view.click()

//输出
Button clicked
复制代码

可是对于扩展函数来讲,并非这样的。扩展函数并非类的一部分,它是声明在类以外的。尽管能够给基类和子类都分别定义一个同名的扩展函数,当这个函数被调用时,它会用到哪个呢?这里,它是由该变量的静态类型所决定的,而不是这个变量的运行时类型。

fun View.showOff() = println("i'm a view!")

fun Button.showOff() = println("i'm a button!")

val view: View = Button()
view.click()

//输出
i'm a view! 复制代码

当你在调用一个类型为View的变量的showOff函数时,对应的扩展函数会被调用,尽管实际上这个变量如今是一个Button对象。回想一下,扩展函数会在Java中编译为静态函数,同时接受值将会做为第一个参数。这样其实2个showOff扩展函数就是不一样参数的静态函数,

View view = new Button();
XxKt.showOff(view);  //定义在Xx.kt文件中
复制代码

参数的类型决定了调用那个静态函数,想要调用Button的扩展函数,则必须先将参数转成Button类型才行:XxKt.showOff((Button)view);

所以,扩展函数也是有局限性的,扩展函数是能扩展,即定义新的函数,而不能重写改变原有函数的实现(本质是一个静态函数)。若是定了一个类中自己存在成员函数同名的扩展函数,Kotlin种调用该方法的时候会如何呢?(Java中没有这个顾虑,调用方式不一样)

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

fun View.click() = println("扩展函数")

val view = View()
view.click()

//输出
View clicked
复制代码

明显了吧~ 对于有同名成员函数和扩展函数时,在Kotlin中调用始终执行成员函数的代码,扩展函数并不起做用,至关于没有定义。这一点在实际开发中须要特别注意了!

扩展属性

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

val String.lastChar: Char
    get() = get(length - 1)
复制代码

一样是获取字符串的最后一个字符,此次是用扩展属性的方式定义。扩展属性也像接收者的一个普通成员属性同样,这里必须定义getter函数,由于没有支持字段,所以没有默认的getter的实现。同理,初始化也不能够:由于没有地方存储初始值。 刚刚定义是一个val的扩展属性,也能够定义var属性:

var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) {
        setCharAt(length - 1, value)
    }
复制代码

还记得上一篇文章的自定义访问器的内容吗?这里的定义方式与自定义访问器一致,val 属性不可变,所以只须要定义getter,而var 属性可变,因此getter和setter都须要。 可能不是很好理解扩展属性,或者会和真正的属性混淆,下面列出了扩展属性转换成Java的代码,你就会比较直观的理解了。

public static final char getLastChar(@NotNull String $receiver) {
      return $receiver.charAt($receiver.length() - 1);
   }

   public static final char getLastChar(@NotNull StringBuilder $receiver) {
      return $receiver.charAt($receiver.length() - 1);
   }

   public static final void setLastChar(@NotNull StringBuilder $receiver, char value) {
      $receiver.setCharAt($receiver.length() - 1, value);
   }
复制代码

和扩展函数是相同的,仅仅是静态函数:提供获取lastChar的功能,这样的定义方式能够在Kotlin中像使用普通属性的调用方式来使用扩展属性,给你一种这是属性的感受,但本质上在Java中就是静态函数。

处理集合:可变参数、中缀调用和库的支持

扩展Java集合的API

val strings = listOf("first", "second", "fourteenth")
println(strings.last())
val numbers = setOf(1, 14, 2)
println(numbers.max())
复制代码

还记的以前咱们使用上面的方式获取了list的最后一个元素,以及set中的最大值。到这里你可能已经知道了,last()max() 都是扩展函数,本身点进方法验证一下吧!

可变参数

若是你也看了listOf 函数的定义,你必定看到了这个:

public fun <T> listOf(vararg elements: T): List<T>
复制代码

也就是vararg 关键字,这让函数支持任意个数的参数。在Java中一样的可变参数是在类型后面跟上... ,上面的方法在Java则是:

public <T> List<T> listOf(T... elements)
复制代码

可是Kotlin的可变参数相较于Java仍是有点区别:当须要传递的参数已经包装在数组中时,调用该函数的语法,在Java中能够按原样传递数组,而Kotlin则要求你显示地解包数组,以便每一个数组元素在函数中能做为单独的参数来调用。从技术的角度来说,这个功能被称为展开运算符,而使用的时候,不过是在对应的参数前面放一个*

val array = arrayOf("a", "b")
val list = listOf("c", array)
println(list)
val list2 = listOf<String>("c", *array)
println(list2)

//输出
[c, [Ljava.lang.String;@5305068a]
[c, a, b]
复制代码

经过对照能够看到,若是不加* ,实际上是把数组对象当作了集合的元素。加上* 才是将数组中全部元素添加到集合中。listOf 也能够指定泛型<String> ,你能够尝试在listOf("c", array) 这里加泛型,第二个参数array就会提示类型不正确。

Java中没有展开,咱们也能够调用Kotlin的listOf函数,该函数声明在Collections.kt文件下:

List<String> strings = CollectionsKt.listOf(array);
System.out.println(strings);
//List<String> strings = CollectionsKt.listOf("c", array);//没法编译

//输出
[a, b]
复制代码

Java中能够直接传入数组,可是不能同时传入单个元素和数组。

键值对的处理:中缀调用和解构声明

还记得建立map的方式吗?

val map = mapOf(1 to "one", 7 to "seven", 52 to "fifty-five")
复制代码

以前说过to 并非一个内置的结构,而是一种特殊的函数调用,被称为中缀调用。 在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间的,如下两种调用方式是等价的:

1.to("one")//普通调用
1 to "one" //中缀调用
复制代码

中缀调用能够与只有一个参数的函数一块儿使用,换句话说就是只要函数只有一个参数,均可以支持在Kotlin中的中缀调用,不管是普通的函数仍是扩展函数。要容许使用中缀符号调用函数,须要使用infix 修饰符来标记它。例如to 函数的声明:

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
复制代码

to函数会返回一个Pair类型的对象,Pair是Kotlin标准库中的类,它是用来表示一对元素。咱们也能够直接用Pair的内容来初始化两个变量:

val (number, name) = 1 to "one"
复制代码

这个功能称之为解构声明,1 to "one" 会返回一个Pair对象,Pair包含一对元素,也就是1和one,接着又定义了变量(number, name) 分别指向Pair中的1和one。 解构声明特征不止用于Pair。还可使用map的key和value内容来初始化两个变量。而且还适用于循环,正如你在使用的withIndex 函数的joinToString 实现中看到的:

for ((index, element) in collection.withIndex()) {
    println("$index, $element")
}
复制代码

to 函数是一个扩展函数,能够建立一对任何元素,这意味着它是泛型接受者的扩展:可使用1 to "one""one" to 1list to list.size()等写法。咱们来看看mapOf 函数的声明:

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>
复制代码

listOf 同样,mapOf 接受可变数量的参数,但此次他们应该是键值对。尽管在Kotlin中建立map可能看起来像特殊的解构,而它不过是一个具备简明语法的常规函数。

字符串和正则表达式的处理

Kotlin定义了一系列扩展函数,使标准Java字符串使用起来更加方便。

分割字符串

Java中咱们会使用String的split方法分割字符串。但有时候会产生一些意外的状况,例如当咱们这样写"12.345-6.A".split(".") 的时候,咱们期待的结果是获得一个[12, 345-6, A]数组。可是Java的split方法居然返回一个空数组!这是应为它将一个正则表达式做为参数,并根据表达式将字符串分割成多个字符串。这里的点(.)是表示任何字符的正则表达式。 在Kotlin中不会出现这种使人费解的状况,由于正则表达式须要一个Regex类型承载,而不是String。这样确保了字符串不会被当作正则表达式。

println("12.345-6.A".split("\\.|-".toRegex())) //显示地建立一个正则表达式
//输出
[12, 345, 6, A ]
复制代码

这里正则表达式语法与Java的彻底相同,咱们匹配一个点(对它转义表示咱们指的时字面量)或者破折号。 对于一些简单的状况,就不须要正则表达式了,Kotlin中的spilt扩展函数的其余重载支持任意数量的纯文本字符串分隔符:

println("12.345-6.A".split(".", "-")) //指定多个分隔符
复制代码

等同于上面正则的分割。

正则表达式和三重引号的字符串

如今有这样一个需求:解析文件的完整路径名称/Users/hubert/kotlin/chapter.adoc 到对应的组件:目录、文件名、扩展名。Kotlin标准库中包含了一些能够用来获取在给定分隔符第一次(或最后一次)出现以前(或以后)的子字符串的函数。

val path = "/Users/hubert/kotlin/chapter.adoc"
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")

//输出
Dir: /Users/hubert/kotlin, name: chapter, ext: adoc
复制代码

解析字符串在Kotlin中变得更加容易,但若是你仍然想使用正则表达式,也是没有问题的:

val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
    val (directory, fileName, extension) = matchResult.destructured
    println("Dir: $directory, name: $fileName, ext: $extension")
}
复制代码

这里正则表达式写在一个三重引号的字符串中。在这样的字符串中,不须要对任何字符进行转义,包括反斜线,因此能够用\. 而不是\\. 来表示点,正如写一个普通字符串的字面值。在这个正则表达式中:第一段(.+) 表示目录,/ 表示最后一个斜线,第二段(.+) 表示文件名,\. 表示最后一个点,第三段(.+) 表示扩展名。

多行三重引号的字符串

三重引号字符串的目的,不只在于避免转义字符,并且使它能够包含任何字符,包括换行符。它提供了一种更简单的方法,从而能够简单的把包含换行符的文本嵌入到程序中:

val kotlinLogo = """|// .|// .|/ \ """.trimMargin(".")
print(kotlinLogo)

//输出
|//
|//
|/ \
复制代码

多行字符串包含三重引号之间的全部字符,包括用于格式化代码的缩进。若是要更好的表示这样的字符串,能够去掉缩进(左边距)。为此,能够向字符串内容添加前缀,标记边距的结尾,而后调用trimMargin 来删除每行中的前缀和前面的空格。在这个例子中使用了. 来做为前缀。

让你的代码更整洁:局部函数和扩展

许多开发人员认为,好代码的重要标准之一就是减小重复代码。Kotlin提供了局部函数来解决常见的代码重复问题。下面的例子中是在将user的信息保存到数据库前,对数据进行校验的代码:

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}:empty Name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}:empty Name")
    }
    //保存user到数据库
}
复制代码

分别对每一个属性校验的代码就是重复的代码,特别当属性多的时候就重复的更多。这种时候将验证的代码放到局部函数中,能够摆脱重复同时保持清晰的代码结构。局部函数,顾名思义就是定义在函数中的函数。咱们使用局部函数来改造上面这个例子:

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

fun saveUser(user: User) {
    //声明一个局部函数
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            //局部函数能够直接访问外部函数的参数:user
            throw IllegalArgumentException("Can't save user ${user.id}:empty $fieldName")
        }
    }
    validate(user.name,"Name")
    validate(user.address,"Address")
    //保存user到数据库
}
复制代码

咱们还能够继续改进,将逻辑提取到扩展函数中:

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

fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user $id:empty $fieldName")
        }
    }
    validate(name, "Name")//扩展函数直接访问接收者对象user的属性
    validate(address, "Address")
}

fun saveUser(user: User) {
    user.validateBeforeSave()
    //保存user到数据库
}
复制代码
相关文章
相关标签/搜索