今天给大家分享的是 Kotlin 这门语言有别的于 Java 的一些新特性,在 Kotlin 没有发布的时候,我们使用 Java 进行 Android 开发,现在呢?Google 已经向宣布了,Kotlin 也可以进行 Android 开发(毕竟如果可以不用 Java 的话,也就不用和甲骨文有版权纠纷了),而且学习成本也很低,因为简单的语法可以使得开发人员快速上手,且和 Java 百分百兼容,你的项目可以不用从头开始再写一遍,而是可以两种语言共存,从而慢慢的过渡到 Kotlin。
我们可以在哪里学习 Kotlin 呢?
- Android developer https://developer.android.com/kotlin/index.html
- Kotlin 官网 http://kotlinlang.org
- Kotlin 中文 https://www.kotlincn.net
- Kotlin 论坛 https://discuss.kotlinlang.org
- Kotlin 博客 https://blog.jetbrains.com/kotlin
当我们点进 Kotlin 的官网时,可以看到官网是如何介绍它的呢?
- 简洁,大大减少样板代码的数量,如数据类,Lambda,单例创建等等。
- 安全,避免类的错误,如空指针异常,可空类型,类型检测与自动转换等等。
- 互操作,开发时可利用JVM,Android和浏览器的现有库。
- 工具友好,任何 Java IDE 或者命令行都可以进行开发。
在介绍今天要讲内容之前,我想先来介绍 Kotlin 的一些基本使用,也就是一些基本语法,然后我再讲新特性的时候也就更容易看得懂了。
1.如何定义变量
// In Java
// 变量
int index = 0;
// 常量
final PI = 3.14;
// In Kotlin
// 变量
var index: Int = 0
// 或者使用类型推导,编译器会知道 index 是 Int 类型
var index = 0
// 常量
val PI = 3.14
2.如何定义方法
// In Java
public int sum(int a, int b) {
return a + b;
}
// In Kotlin
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
// 表达式函数体
// 这里使用了类型推导
fun sum(a: Int, b: Int) = a + b
3.如何定义类
In Java
interface AC {}
class Super {
public Super() {}
}
class Sub extends Super implements AC{
public Sub () {
super();
}
}
// In Kotlin
// 默认会自动生成一个无参构造
open class B
// 这里必须显示的调用父类的构造方法
// 如果父类的的构造带参那么也必须调用带参的构造
class RB : B()
interface AC {
// 带默认实现的方法
// Java 8 之后也支持接口方法的默认实现,不过需要声明 default 关键字
// 由于 Kotlin 是以 Java 6 为目标设计的,所以默认方法在转成 Java 代码后其实是
// 接口中有一个默认实现了接口的静态类
fun showOff() = println("Clickable 默认实现")
}
// 没有参数的话可以省略 ()
// 也可以省略大括号
// Kolin 的类默认是 public 和 final,想要被集成需要改为 open 的
// (p: Int) 这里被括号围起来的语句就叫做主构造方法
open class Super(p: Int)
// 这里需要写 Super(10) 是因为需要调用父类的构造函数
// 而接口 AC 并不存在构造直接写 AC 即可
// 无论是继承还是现实都是通过 : 实现
// constructor 用来声明一个构造(主构造),constructor 关键字可省略
class Sub constructor(s: Int): Super(s), AC {
// init 表示引入一个初始化语句块,这种语句块会在类被创建时执行,并且与主构造方法一起使用
init {
this.s = 0
}
}
// 实例化
// In Java
Sub sub = new Sub(2);
// In Kotlin
var sub = Sub(3)
接下来开始介绍今天的重点,Kotlin 的一些新特性~
1.扩展函数/成员(给别人的类添加方法)
// 在扩展函数中可以直接访问被扩展的类的其他方法和属性
// 但扩展函数并不允许打破它的封装性,所以扩展函数不能访问私有或是受保护的成员
// 对应 Java 代码中 扩展函数是 static final 的,所以扩展函数是不能被子类重写的
// 扩展函数并不是类的一部分,因为扩展函数是声明在类外部的
// 如果扩展函数与成员函数签名相同,往往成员函数会被优先使用
// 书中解释说扩展函数无非就是静态函数的一个高效的语法糖
// 如:为 String 扩展一个 重复多次的字符串方法
// String.multiply 前面为为 String 扩展,后面为方法名
// int 为重复次数 返回一个 String
fun String.multiply(int: Int): String {
val stringBuild = StringBuffer()
for (i in 0 until int) {
// 这时候里面就有了一个 this,而这个 this 就指代调用的这方法的对象了
stringBuild.append(this)
}
return stringBuild.toString()
}
// Kotlin 中的调用
// 直接用字符串调用该方法即可
println("Jaaaelu ".multiply(8))
// Java 中的调用
// Java 中如何调用呢? 类名.方法名
// 这里其实相当于静态方法
// 如 Expand.multiply("Jaaaelu ", 8)
// 给 String 扩展成员常量
// 并不能进行初始化,因为没有合适的地方来存放值
val String.test: String
get() = "abc"
var StringBuilder.lastChar
// var 必须要有 getter 和 setter
get() = get(length - 1)
set(value) = this.setCharAt(length - 1, value)
2.默认参数
// Kotlin 可以给函数参数定义默认值,这样大大降低了重载函数的必要性,
// 而且命名函数让多参数函数的调用更加易读。
// 我们现在为 Collection 写一个集合拼接成字符串的一个扩展方法
// 拼接时我们指定前缀、后缀和分隔符
// 这里使用了默认参数,为参数指定默认的值,这样在调用方法的时候就
// 可以不指定参数或者只为某个参数指定值
fun <T> Collection<T>.collectionJoinToString(separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = ""): String {
val result = StringBuilder(prefix)
for ((index, value) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(value)
}
result.append(postfix)
return result.toString()
}
// 调用
val set = setOf(1, 2, 5)
// 全部使用默认值
println(set.collectionJoinToString())
// 为第一个参数赋值
println(set.collectionJoinToString("-"))
// 配合命名参数,第一个参数值使用默认值,只为指定的参数赋值
println(set.collectionJoinToString(prefix = "[", postfix = "]"))
// 如果在方法上面添加 @JvmOverloads 那么在 Java 中调用也会生成该函数的各个重载版本
3.object 关键字
// object 关键字的核心用途就是定义一个类时同时创建一个实例。
// 一些不同的使用场景:
// 1.对象声明是定义单例的一种方式。
// 2.伴生对象可以持有工厂方法和其他与这个类相关,但在调用时并不依赖类实例的方法。
// 它们的成员可以通过类名来访问。
// 3.对象表达式用来替代 Java 的匿名内部类。
// 1.创建单例
// 引入 object 后,与类一样依然可以有属性、方法、初始化语句等声明,
// 但是唯一不允许的就是构造方法(主构造、从构造)都没有
object Patroll :Driver{
override fun drive() {
println("日常飙车")
}
private val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person in allEmployees) {
// ...
}
println("Jaaaelu")
}
}
// 调用
val p1:Driver = Patroll
val p2 = Patroll
// 2.伴生对象
// 关键字 companion,使用后可以通过类名访问这个对象的方法属性,看上去非常像是静态调用
// 可以访问私有
class ACompanion {
private fun privateFun() {
println("私有方法")
}
companion object {
val PI = 3.14
fun bar() {
println("测试方法")
}
}
}
// 调用
println(ACompanion.PI)
// 3.匿名内部类的新写法
// object 除了声明单例外还能用来声明匿名对象(也就是 Java 中的匿名内部类)
// 于声明对象不同,匿名对象不是单例,比如每次执行 other()。都会创建新的对象实例
var count = 0
fun other() {
// 除了去掉名字外,语法与对象声明相同
val a = object : Driver {
override fun drive() {
count++
// 也可以访问外面的内容
println("匿名内部类 $count")
}
}
println(a)
a.drive()
}
4.Lambda 表达式
// Lambda 表达式本质上就是可以传递给其他函数的一小段代码(作为函数参数的代码块)
// 函数式表层提供了另一种解决方案:把函数作为值来看待
// 基础语法
// { x: Int, y: Int -> x + y } 其中 x: Int, y: Int 为参数, x + y 为函数体。
// Kotlin 中的 Lambda 始终用花括号包围,只需箭头就将参数列表与函数体分开。
// 可以将 Lambda 赋值给变量或者常量,然后调用
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
// 正常 sum
// 相当于(Int, Int) -> Int
fun sumOld(arg1: Int, arg2: Int): Int {
return arg1 + arg2
}
// Lambda 表达式形式的 sum
val sumLambda = { arg1: Int, arg2: Int -> arg1 + arg2 }
// Lambda 表达式形式的 sum 且其中还能有其他语句
// 相当于(Int, Int) -> Int
val sumAndPrint = { arg1: Int, arg2: Int ->
println("$arg1 + $arg2 = ${arg1 + arg2}")
// 最后一行表示你的返回值
arg1 + arg2
}
// 没有参数和返回值的时候,可以直接将其写在大括号内
val printHello = {
// 相当于函数体
println("Hello")
}
// 在作用域中访问变量
// 捕捉的原理:当你捕捉 final 变量时,它的值和使用这个值的 Lambda 代码一起存储。
// 对于非 final 变量来说,它的值被封装在一个特殊的包装器中,这样你就可以改变这个值,
// 这个包装器的引用会和 Lambda
// 代码一起被存储。
fun printProblemCounts(responses: Collection<String>) {
// 默认情况下,局部变量的生命周期被限制在声明这个变量的函数中
// 但如果被 Lambda 捕捉了,那么使用这个变量的代码可以被存储并稍后再执行
var clientErrors = 0
var serverErrors = 0
responses.forEach {
if (it.startsWith("4")) {
// Kotlin 中允许在 Lambda 中访问非 final 变量,甚至修改它们
// 从 Lambda 内访问外部变量,我们称这些变量被 Lambda 捕捉
// 就像例子中的 clientErrors 以及 serverErrors
clientErrors++
} else if (it.startsWith("5")) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors")
}
// Lambda 的实现细节:从 Kotlin 1.0 开始,每个 Lambda 表达式都会被编译成一个匿名类,
// 除非它是一个内联 Lambda
// 不过这里所说的匿名类只对期望函数式接口的 Java 方法有效
// 内联函数:消除 Lambda 带来的运行时开销
// 例如 Lambda 表达式会被正常的编译为匿名类,这样每次调用 Lambda 就会创建一个类,
// 这样会带来额外的开销。
// Kotlin 提供了 inline 修饰符标记一个函数
// 内联函数被编译后,它的字节码连同传递给它的 Lambda 的字节码被插入到调用函数的代码中,
// 这使得函数调用相比于直接编写相同的代码,不会产生额外的运行时开销。
inline fun <T> synchronized(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
val l = Lock()
synchronized(l) {
println("J")
}
// 上面代码会编译为
l.lock()
try {
println("J")
} finally {
l.unlock()
}
// 内联函数的限制
// 由于不是所有的 Lambda 函数都可以被内联,当函数内敛的时候,
// 作为参数的 Lambda 表达式的函数会直接替换掉最终生成代码中。
// 这也限制了函数体中对应的(Lambda)参数的使用,如果(Lambda)参数被调用,
// 这样的代码能被容易地内联。
// 但如果(Lambda)在某个地方被保存起来了,以便后面继续使用,Lambda 表达式的代码将不能被内联。
// 例如系统提供的这个函数,它没有直接调用作为 transform 参数传递进来的函数,
// 而是将这个函数传递给了一个类的构造,构造方法将它保存在一个属性中,
// 所以 transform 需要被编译为标准的非内联的表示法,即一个实现了函数接口的匿名类。
// fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
// return TransformingSequence(this, transform)
// }
// 如果一个函数期望两个或者更多 Lambda 参数,可以选择只内联一些参数。
inline fun foo(inlined: () -> Unit, noinline noinlined: () -> Unit) {
}
// 决定何时将函数声明成内联
// 使用 inline 关键字只能提高带有 Lambda 参数的函数的性能。
// 对于普通的函数调用,JVM 已经提供了强大的内联支持。
// 在使用 inline 关键字时,应该注意代码的长度,把一些和 Lambda 无关的方法抽取到一个
// 独立非内联函数中,从而减少字节码拷贝长度。
// 对于 Koltin 集合来说,提供了更多高阶函数
// 高阶函数就是以另一个函数作为参数或者返回值的函数
// 在 Kotlin 中函数可以用 Lambda 或者函数引用来表示
// 因此,任何以 Lambda 或者函数引用作为参数货返回值的函数(或者两者都是)都是高阶函数
// 以集合为例:
// 检查集合中所有元素是否都符合某个条件,可以通过 all 或 any 来实现
// all 判断所有元素是否都满足该条件
// any 判断元素列表中是否存在至少一个匹配元素
// count 对满足该表达式的元素计数
// find 找到一个满足 Lambda 的第一个元素
// filter 会从集合中一处你不想要的元素
// map 函数对集合的每一个元素应用给定的函数,并把结果收集到一个新集合
// flatMap 可将多列表平铺
// groupBy 把列表转为分组的 map
// 等等
// 不过对于某些高阶函数来说,如做 map 或 filter 操作时,这些函数都会创建中间集合
// 如果元素数量过多,这种链式调用就会变得十分低效
// 所以这时候就引入序列,将上述操作变成使用序列,而不是直接返回集合
// 序列:惰性集合操作,由于序列中的元素求值是惰性的,因此,
// 可以使用序列更高效的对集合元素执行链式操作,而不需要创建额外中间集合来保存中间过程中产生的结果
// 惰性操作是对元素逐个处理
// 先将集合转为序列,然后进一步进行函数操作,最后再将序列转为集合
// 这时就不会创建中间集合
// 下面的计算中.map(Person::name).filter { it.startsWith("A") } 就是中间操作
// 而 toList() 是末端操作
// 由于中间操作时惰性的,所以 map 和 filter 变换被延期了
// 末端操作才会触发被延期的计算
// 序列的执行顺序是按照元素来的,所以打印出来的内容是 map 第一个元素,filter 第一个元素,如此往下
// 而我们正常集合操作的话就是先全部 map,然后再全部 filter
println(people
.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList())
5.可空性
// Kotlin 对可空类型的支持,可以帮助我们在编译期,检测出潜在的 NullPointerException 错误。
// Kotlin 提供了像安全调用(?.)、Elvis 运算符(?:)、
// 非空断言(!!)及 let 函数这样的工具来简洁的处理可空类型。
// String = String,不能存储 null 引用
// 所以默认情况下类型都是非空的
fun strLen(s: String) = s.length
// String? = String or null
// 这里是显式的标记出使用可空的 String
// s?.length 表示如果 s 不为空会返回 s.length 否则返回 null
// 相当于 if (s != null) s.length else null
fun strLenCanNullNoException(s: String?) = s?.length
// 带有默认值 ?. 后面跟的就是默认值
fun strLenCanNullNoExceptionAndWithDefault(s: String?) = s?.length ?: 0
// 虽然可以用作默认值,当然也可以用于其他功能,如抛出异常
fun strLenThrowException(s: String?) = s?.length ?: throw NullPointerException("字符串为空")
// s!!.length 表示如果 s 为 null 会直接抛出空指针异常,不为 null 则可以返回 s.length
// 一般情况下,只有我们知道 s 一定不为 null 的时候使用
// !! 也叫做非空断言,尽量不在一行代码中使用多个非空断言,
// 因为你很难分清除是哪个 !! 让你程序抛出空指针异常
fun strLenCanNullWillException(s: String?) = s!!.length
val p: Person? = Person("Gzw", 23)
// sendEmail 接收一个非空类型的 Person,所以需要先进行空判断,判断通过再继续
if (p != null) sendEmail(p, "啦啦啦啦啦啦")
// 还可以使用另一种方式 let
// 如果 p 不为空,那么 it 就不为空,如果 p 为空什么都不会发生
// 这里必须是 ?. 的调用方式,否则会报错
p?.let { sendEmail(it, ",,,,,") }
// Kotlin 中所以泛型类和泛型函数的类型参数默认都是可空的
fun <T> printlnHashCode(t: T) {
println(t?.hashCode())
}
// T 被推导为 Any?
printlnHashCode(null)
// 为类型参数添加非空上界后,现在的 T 就不是可空的了
fun <T: Any> printlnHashCodeNotNull(t: T) {
println(t.hashCode())
}
// Java 中的类型在 Kotlin 中被解释成平台类型,允许开发者把他们当做可空或非空来对待。
// Java 中 @Nullable + Type = Type? / @NotNull + Type = Type
// Java 中的 Type = Kotlin 中的 Type? or Type
// 如 Java 中的 ArrayList<String> 在 Kotlin 中被当做了 ArrayList<String?>?
// 表示基本数字的类型(如 Int)看起来用起来都像普通的类,但通常会被编译成 Java 基本数据类型。
// 可空基本类型(如 Int?)对应着 Java 中的装箱基本数据类型(如 Integer)。
// Any 类型是所有其他类型的超类型,类型于 Java 中的 Object。而 Unit 类比于 void。
6.集合
// 创建可空性的集合时,需要注意的时候,要小心决定什么是可空的,
// 是元素可空还是集合本身可空,还是两者都可为空?
// List<Int?>、List<Int>?、List<Int?>?
// Kotlin 中把访问集合数据的接口和修改集合数据的接口分开了
// (只读集合 Collection 与可变集合 MutableCollection)
// kotlin.collections.Collection 只读集合提供了 size、iterator、contains 等操作来查看读取数据
// kotlin.collections.MutableCollection 可变的集合提供了 add、remove、clear 等修改集合的操作,
// 不过 MutableCollection 继承自 Collection,所有也拥有那些读取操作
// 如果函数接收 Collection 而不是 MutableCollection,
// 那么就很容易知道这个方法不会修改集合,只会读取集合数据。
// 只读集合并不一定是不可变的,当只读集合和可变集合指向同一个集合对象的时候,可变可以进行操作,
// 然后只读读取操作后的集合。
// 只读集合并不总是线程安全的。
// 即使在 Kotlin 中将集合声明成只读,Java 代码也能够修改这个集合,
// 因为 Java 中并不区分只读集合和可变集合。
// List 创建集合函数,只读 -> listOf,可变 -> mutableListOf、arrayListOf
// Set 创建集合函数,只读 -> setOf,可变 -> mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
// Map 创建集合函数,只读 -> mapOf,可变 -> mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf
// 只读集合并一定是不可变的
var mutable: MutableCollection<Int> = mutableListOf(1, 2, 3, 4, 5)
val c: Collection<Int> = mutable
var m: MutableCollection<Int> = mutable
// Array<Int> 将会是一个包含装箱整型的数组
// IntArray 是基础类型 int 的数组,也就是 Java 中的 int[],这样效率会更高一些
// 通过 Java 方法操作了只读集合
7.运算符
// Kotlin 允许使用对应名称的函数来重载一些标准的数学运算,但是不能定义自己的运算符
// 可重载的二元算数运算符
// a * b -> times
// a / b -> div
// a % b -> mod
// a + b -> plus
// a - b -> minus
// 特殊运算符
// shl -> 带符号左移
// shr -> 带符号右移
// ushr -> 无符号右移
// and -> 按位与
// or -> 按位或
// xor -> 按位异或
// inv -> 按位取反
// 一元运算符
// +a -> unaryPlus
// -a -> unaryMinus
// !a -> not
// ++a, a++ -> inc
// --a, a-- -> dec
data class Point(val x: Int, val y: Int) {
// 重载 plus 运算符
// 且并不要求两个运算数是相同类型,返回值也可以不同
// 不支持交换性
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
operator fun unaryMinus(): Point {
return Point(-x, -y)
}
}
// 也相当于 Point(10, 20).plus(Point(20, 10))
println(Point(10, 20) + Point(20, 10))
// 复合运算符
// += 可以被转换为 plus 或者 plusAssign(但最好不要同时重载这两个方法)
// point = point + Point(10, 20)
point += Point(10, 20)
println(point)
// 把运算符定义为扩展方法
operator fun Point.minus(other: Point): Point {
return Point(x - other.x, y - other.y)
}
// 等号运算符:equals,在 Kotlin 中使用 == 会被转成 equals
// 而 != 也会被转成 equals,只是返回的结果是相反的
// == 与 != 可用于空运算数,因为这些运算符实际上会检查运算数是否为 null
// 如 a == b,会先检查 a 是否为空,如果不是,就会调用 a.equals(b),
// 否则两个都是 null 的时候才会返回 true a == b -> a?.equals(b) ?: (b == bull)
// Comparable 便于比较值
class P(val x: Int, val y: Int) : Comparable<P> {
// 用于比较一个对象是否大于另一个对象
// 比较运算符 >, <, >=, <= 将被转换为 compareTo
// 即 a > b -> a.compareTo(b) >= 0
override fun compareTo(other: P): Int {
return compareValuesBy(this, other, P::x, P::y)
}
// 重写 equals 方法
// 这里我们虽然是重载了比较运算符 equals,但是方法前缀不是 operator 而是 override,
// 因为 equals 是 Any 中定义的方法,equals 不能实现扩展函数,
// 因为集成自 Any 类的实现始终优先于扩展方法
override fun equals(other: Any?): Boolean {
// 先检查是否为同一个对象
// 这里 === 与 Java 中的 == 完全相同
// (检查两个参数是否是同一个对象的引用,基本类型的话会比较值)
// === 不能被重载
if (other === this) return true
// 检查参数类型
if (other !is P) return false
// 检查值
return other.x == x && other.y == y
}
}
// in 运算符会被转为 contains
// a in b -> a.contains(b)
// rangeTo 是 Comparable 的扩展函数
// rangeTo 运算符的优先级低于算数运算符
// start..end -> start.rangeTo(end)
// 日期区间
val now = LocalDate.now()
// 未来十天
val vacation = now..now.plusDays(10)
println(now.plusDays(1) in vacation)
(1..10).forEach(::println)
// for 循环中也可以使用 in 运算符,但这种情况下和上面的表示含义不同,它被用来执行迭代
// for (x in list) {...} 实际上被转为 list.iterator(),然后就像 Java 中一样,
// 反复调用 hasNext 和 next
for (x in 1..10) {
print("$x ")
}
// 这种写法叫字符串模板
// $ 后面跟着变量就可以直接打印出来变量的值
// 如果想要打印 $ 直接写出来就行了
println("Hello $name !")
// In Java
System.out.println(“Hello ” + name + " !")
// 但如果 $ 后面还有其他字符,那么 $ 就需要使用转义字符 \$
println("\$name")
// ${ 里面可以放各种表达式 }
println("Hello ${if (args.isNotEmpty()) args[0] else "Kotlin"} !")
// until 用来构建一个开区间,然后用 in 判断是否在区间内
// 10..20 表示闭区间为 10 - 20, 10 until 20 位开区间表示 10 - 19
8.其他
// 1.内部类和嵌套类
// 内部类和嵌套类:默认是嵌套类
// Kotlin 中的嵌套类一般情况下不能访问外部类的实例
class Button : View {
override fun getCurrentState(): State = ButtonState()
// 这个类与 Java 中的静态嵌套类类似,而不是内部类
// 所以 ButtonState 不会持有外部引用
class ButtonState : State {
override fun toString(): String {
return "ButtonState"
}
}
// inner class 与 Java 的内部类类似
// 所以 OtherButtonState 会持有外部类的应用
inner class OtherButtonState : State {
// 拿到外部类的引用
fun getOutReference(): Button = this@Button
}
}
// 2.修饰符
// Kotlin 中的默认可见性是 public 的
// Java 中的默认可见性是包私有
// Kotlin 中有一个不同的修饰符 internal,表示模块内可见
// 修饰符 public 对于类成员与顶层声明都表示所有地方可见
// 修饰符 internal 对于类成员与顶层声明都表示模块中可见
// 修饰符 protected 对于类成员表示子类可见
// (与 Java 中不同,Java 还允许同包内的文件访问 protected)
// 修饰符 private 对于类成员表示类中可见,对于顶层声明表示文件中可见
// 3.局部函数
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 address")
}
// 然后存储逻辑省略
}
// 通过提取局部函数和扩展来避免重复
fun saveUserWithOptimize(user:User) {
user.validateBeforeSave()
// 然后存储逻辑省略
}
// 通过提取局部函数来避免重复
fun User.validateBeforeSave() {
// 声明一个局部函数来进行字段校验
fun validate(value:String, fieldName:String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user $id: empty $fieldName")
}
}
validate(name, "name")
validate(address, "address")
}
// 4.类委托(帮助避免在代码中出现许多相似的委托方法)
// 实现接口后可以通过 by 关键字将接口的实现委托到另一个对象
// 这样看上去我们需要重写的方法就都消失了,其实是会去执行被委托的对象
class DelegateCollection<T>(private val innerList:ArrayList<T>) : Collection<T> by innerList {
// 虽然委托给了别人执行,但是仍然可以重写,最终会执行你重写的方法
override fun contains(element: T): Boolean {
return false
}
}
// 5.中缀表达式
// 中缀表达式(调用只有一个参数的函数时,使得代码更简练)
// 简单的自定义函数
// 当然中缀
infix fun Any.toAny(other:Any) = Pair(this, other)
// 调用
println(2 toAny 3)
就到这里了...