文章目录
1 在kotlin中创建集合
val set = hashSetOf(1, 7, 53) // 创建HashSet
val list = arrayListOf(1, 7, 53) // 创建ArrayList
val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three") // 创建HashMap,to是一个函数,比如1为key,value为one,具体函数后面说
// javaClass相当于java中的getClass()
// kotlin使用的是java的集合类
println(set.javaClass)
println(list.javaClass)
println(map.javaClass)
输出:
class java.util.HashSet
class java.util.ArrayList
class java.util.HashMap
2 让函数更好调用
2.1 命令参数
java的集合都有一个默认的 toString
实现,但是它格式化的输出是固定的
val list = listOf(1, 2, 3)
println(list)
输出:
[1, 2, 3]
下面将使用kotlin的方式实现自定义的 toString
集合输出格式
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)
// 输出(1; 2; 3)
// 以下语句晦涩难懂
println(joinToString(list, ";", "(", ")"))
// kotlin可以在设置参数时指定参数,更明显的查看参数表示的意义
// 即根据方法参数名指定传递的参数,让代码有更好的可读性,比如这里的参数";"、"("、")"分别为separator、prefix和postfix
println(joinToString(list, separator = ";", prefix = "(", postfix = ")"))
2.2 默认参数值
// 形参可以设置默认参数值
fun <T> joinToString(
collection: Collection<T>,
separator: String = ";",
prefix: String = "(",
postfix: String = ")"): String {
...
}
// 可以省略所有或部分参数的值,让方法重载更加方便
val list = joinToString(list);
kotlin和java文件是可以互相转换的,比如在IDEA或Android Studio提供了很方便的工具一键转换。具体的如何用IDE转换参考:Kotlin和Java文件互转
比如将java文件转换为kotlin文件:
public class Demo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(joinToString(list, ";", "(", ")"));
}
public static <T> String joinToString(
Collection<T> collection,
String separator,
String prefix,
String postfix) {
StringBuilder result = new StringBuilder(prefix);
Iterator<T> iterator = collection.iterator();
for (int index = 0; index < collection.size(); index++) {
if (index > 0) result.append(separator);
if (iterator.hasNext()) {
result.append(iterator.next());
}
}
result.append(postfix);
return result.toString();
}
}
转换后:
object Demo {
@JvmStatic
fun main(args: Array<String>) {
val list: MutableList<Int> = ArrayList()
list.add(1)
list.add(2)
list.add(3)
println(joinToString(list, ";", "(", ")"))
}
fun <T> joinToString(
collection: Collection<T>,
separator: String?,
prefix: String?,
postfix: String?
): String {
val result = StringBuilder(prefix)
val iterator = collection.iterator()
for (index in collection.indices) {
if (index > 0) result.append(separator)
if (iterator.hasNext()) {
result.append(iterator.next())
}
}
result.append(postfix)
return result.toString()
}
}
在kotlin转java时如果不想一个个的写重载方法参数,可以在方法添加注解 @JvmOverloads
,反编译时会生成默认参数值的重载方法:
在kotlin时:
@JvmOverloads
fun <T> joinToString(
collection: Collection<T>,
separator: String = ";",
prefix: String = "(",
postfix: String = ")"): String {
...
}
转换到java时(转换后的文件要做一定的删减):
@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix, @NotNull String postfix) {
...
}
public static String joinToString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
...
}
@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) {
return joinToString$default(collection, separator, prefix, (String)null, 8, (Object)null);
}
@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator) {
return joinToString$default(collection, separator, (String)null, (String)null, 12, (Object)null);
}
@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection) {
return joinToString$default(collection, (String)null, (String)null, (String)null, 14, (Object)null);
}
2.3 消除静态工具类:顶层函数和属性
在java开发过程中总会用到一些工具类,有的是处理字符串有的是处理其他数据,往往这些方法都不会写在某个功能模块的类里,而是写在单独的util包下提供xxxUtils工具类,这样我们可以在处理相关数据的时候可以直接调用而不实例化。
在kotlin中有一种写法叫顶层函数,这类函数一般定义在一个单独的kotlin文件中
// 假设该文件为StringUtils.kt
// kotlin与java不同的地方在于,kotlin不需要函数、属性、常量都要在class类中,可以直接将它们直接写在文件上
// 写在这些文件上的函数、属性、常量都是static的,公开给所有模块使用
@file:JvmName("StringFunctions") // 提供给Java调用时使用的名称
import java.lang.StringBuilder
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中使用时:
fun main() {
// 可以直接通过函数名称访问
val list = listOf(1, 2, 3)
println(joinToString(list))
}
在java中使用时:
StringFunctions.joinToString(...)
// 如果没有kt文件没有注明@file,要访问顶层函数,是直接使用文件名+Kt名称访问,比如StirngUtilsKt.joinToString(...)
顶层属性
// 外部变量
var opCount = 0
fun performOperation() {
opCount++;
}
fun reportOperationCount() {
println("Operation performed $opCount times")
}
// 该变量会被存储到一个静态的字段中,val声明的属性是有一个getter的
// 如果想要声明为一个常量,可以使用const关键字
const val UNIX_LINE_SEPERATOR = "\n"
等价于java的
public static final String UNIX_LINE_SEPERATOR = "\n";
3 给别人的类添加方法:扩展函数和属性
扩展函数,它就是一个类的成员函数,不过定义在类的外面。
// 计算一个字符串的最后一个字符
fun String.lastChar(): Char = this.get(this.length - 1)
或
fun String.lastChar(): Char = this[this.length - 1]
你所要做的,就是把你要扩展的类或接口的名称,放到即将添加的函数前面。这个类的名称被称为接收者类型;用来调用这个扩展函数的那个对象,叫做接收者对象。
String.lastChar()的String为接收者类型
this.get(this.length - 1)的this为接收者对象
// 顾名思义,扩展函数就是扩展一个类,在这个类的基础上再添加新的函数。
// 比如这里扩展了String类,在调用时将扩展函数中的this都替换成了传递给扩展函数的String对象,比如"kotlin",就像在String类的内部添加了这个函数一样,下面代码的效果类似于:
// fun lastChar(): Char = "Kotlin".length - 1
println("Kotlin".lastChar())
注意,扩展函数并不允许你打破它的封装性。和在内部定义的方法不同的是,扩展函数不能访问私有的或者是受保护的成员。
简单说,扩展函数就是只要类型相同,就可以将函数的接收者类型进行替换的操作,扩展函数是一个静态方法,this就是指代这个接收者类型对象
3.1 导入和扩展函数
需要使用扩展函数时,需要显式地导入
// 在strings包下的扩展函数lastChar()
import strings.lastChar
// import strings.*
// import strings.lastChar as last // 修改导入的类或者函数名称,使用as关键字,这里把lastChar修改为last
val c = "Kotlin".lastChar()
// val c = "Kotlin".last() // 修改扩展函数名称后使用
3.2 作为扩展函数的工具函数
扩展函数无非就是静态函数的一个高效语法糖,可以使用更具体的类型作为接收者类型,而不是一个类。
fun <T> Collection<T>.joinToString(
separator: String = ",",
prefix: String = "",
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun Collection<String>.join(
separator: String = ",",
prefix: String = "",
postfix: String = "") = joinToString(separator, prefix, postfix)
fun main() {
println(listOf("1", "2", "3").join())
}
3.3 扩展属性
// 这里必须定义getter函数,因为没有支持字段,因此没有默认getter实现,同理也不能初始化因为没有地方存储值
val String.lastChar: Char get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value) = setCharAt(length - 1, value)
fun main() {
// 使用扩展函数打印字符串最后一个字符
println("kotlin".lastChar)
// 使用扩展函数将最后一个函数替换为!
val sb = StringBuilder("kotlin")
sb.lastChar = '!'
println(sb)
}
如果你尝试给扩展属性添加默认值,会出现编译错误:扩展属性不能有初始化器:
// initializer is not allowed here because this property has not backing field
val MutableList<Int>.sumIsEven: Boolean = false
get() = this.sum() % 2 == 0
为什么不能编译通过?其实,这与扩展函数一样,其本质也是对应java中的静态方法。由于扩展没有实际地将成员插入类中,因此对扩展属性来说幕后字段是无效的。
public final class ExampleTestKt {
// 扩展属性实际上也是一个静态方法
public static final boolean getSumIsEven(@NotNull List $this$sumIsEven) {
Intrinsics.checkParameterIsNotNull($this$sumIsEven, "$this$sumIsEven");
return CollectionsKt.sumOfInt((Iterable)$this$sumIsEven) % 2 == 0;
}
}
扩展属性只能由显式提供的getter和setter定义。
3.4 扩展函数的实现机制
扩展函数如此方便,会不会对性能产生影响?我们可以写一个扩展函数然后将它编译为java查看:
fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
val temp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = temp
}
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 2,
d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006"},
d2 = {"exchange", "", "", "", "fromIndex", "toIndex", "app_debug"}
)
public final class ExampleTestKt {
public static final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
Intrinsics.checkParameterIsNotNull($this$exchange, "$this$exchange");
int temp = ((Number)$this$exchange.get(fromIndex)).intValue();
$this$exchange.set(fromIndex, $this$exchange.get(toIndex));
$this$exchange.set(toIndex, temp);
}
}
我们可以发现,扩展函数是一个静态方法。静态方法的特点:它独立于该类的任何对象,且不依赖类的特定实例,被该类的所有实例共享。此外,被 public
修饰的静态方法本质上也就是全局方法。
所以,扩展函数不会带来额外的性能消耗。
3.5 扩展函数的作用域
既然扩展函数是全局的,一般情况下我们可以定义在包内,比如 com.example.extension
包下:
// Extension.kt
package com.example.extension
fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
val tmp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = tmp
}
但如果你尝试将扩展函数放在一个类中管理,就会出现问题:
class Extension {
// 即使添加了修饰符为public也一样无法访问
public fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
val tmp = this[fromIndex]
this[fromIndex] = this[toIndex]
this[toIndex] = tmp
}
}
你会发现无法调用扩展函数 mutableList.exchange()
了,方法修饰为 public
也一样,我们可以看下编译代码:
public final class Extension {
public final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
Intrinsics.checkParameterIsNotNull($this$exchange, "$this$exchange");
int temp = ((Number)$this$exchange.get(fromIndex)).intValue();
$this$exchange.set(fromIndex, $this$exchange.get(toIndex));
$this$exchange.set(toIndex, temp);
}
}
扩展函数变成类的方法了,只能提供给类或子类使用。
3.6 父类和子类相同名称扩展函数
open class Animal
class Dog : Animal()
fun Animal.name() = "animal"
fun Dog.name() = "dog"
fun Animal.printName(anim: Animal) {
println(anim.name())
}
fun main(args: Array<String>) {
Dog().printName(Dog())
}
animal
上面有一个扩展函数,Dog 是 Animal 的子类,它们都提供了相同名称的扩展函数,使用了 Dog 子类调用最终却显示了父类扩展函数的输出结果,原因和上面分析一样,因为扩展函数是一个静态函数,它是不具备运行时的多态。可以看下 java 反编译上面示例的结果:
public static final String name(Animal receiver) {
return "animal"
}
public static final String name(Dog receiver) {
return "dog"
}
// 扩展函数在反编译为 java 或在 java 调用时,第一个函数会是调用函数的类
// 可以看到第二个参数为 Animal 对象,而不是 Dog 对象
public static final void printName(Animal r, Animal a) {
String str = name(a);
System.out.println(str);
}
public static final void main(String[] args) {
// 在 kotlin 的 Dog 对象被强转为 Animal 类型
printName((Animal) new Dog(),(Animal) new Dog());
}
3.7 成员方法优先级总高于扩展函数
// 类中定义成员函数foo()
class Son {
fun foo() = println("son called member foo")
}
// 定义扩展函数foo
fun Son.foo() = println("son called extension foo")
Son().foo()
输出:
son called member foo
当扩展函数和现有类的成员方法同时存在时,kotlin将会默认使用类的成员方法。
这么设计其实是有意义的,在多人开发的时候,如果每个人都对 Son
扩展了 foo()
,那就很容易造成混淆,对于第三方库来说甚至是异常灾难:我们把不应该更改的方法改变了。
3.7 标准库中的扩展函数:run、let、also、takeIf
3.7.1 run
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
run
是任何类型 T
的通用扩展函数,run
中执行了返回类型为 R
的扩展函数 block
,最终返回该扩展函数的结果。
run
扩展函数的作用域是独立的:
fun testFoo() {
val nickanme = "prefert"
run {
val nickname = "test"
println(nickname)
}
println(nickname)
}
这个范围函数本身似乎不是很有用。但相比范围,还有一点不错的是,它返回范围内最后一个对象。
// 如果没有登录的话,显示登录对话框,否则显示其他对话框
run {
if (!islogin) loginDialog else getNewAccountDialog
}.show()
3.7.2 let
// let
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this) // 返回闭包结果
}
// apply
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this // 返回本身对象T
}
let
和 apply
类似,唯一不同的是返回值:apply
返回的是原来的对象,而 let
返回的是闭包里面的值。
apply
的使用可以将同一个变量的处理全都放在一个语句块中方便管理:
import java.lang.StringBuilder
fun main() {
val sb = StringBuilder()
val result = sb.apply {
append("I'm")
append("Using")
append("apply")
append("function")
}
// hash值是相同的
println(sb.hashCode())
println(result.hashCode())
}
let
的使用很多时候都和可空类型判断结合处理:
fun main() {
val kot = Kot()
kot.dealStudent()
}
class Kot {
val student: Student? = Student(10)
fun dealStudent() {
// student不为null的时候调用扩展函数let
val result = student?.let {
println(it.age)
it.age // let返回闭包的结果
}
println(result)
}
}
data class Student(val age: Int)
3.7.3 also
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
also
像是 let
和 apply
函数的加强版。
class Kot {
val student: Student? = getStudent()
var age = 0
fun dealStudent() {
val result = student?.also { stu ->
// this.age是外部类Kot的成员变量var age
// stu是指定的名称,默认会提供it使用
this.age += stu.age
println(this.age)
println(stu.age)
this.age
}
}
}
值得注意的是:如果使用 apply
,由于它内部是一个扩展函数,this
将指向 studuent
本身而不是外部类 Kot
,而且也无法调用到 Kot.age
3.7.4 takeIf
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
如果我们不仅仅只想判空,还想加入条件,这时 let
可能就显得有点不足,就可以使用 takeIf
。
val result = student?.takeIf { it.age >= 18 }.let { ... }
takeIf
是接收满足条件的情况下执行后续操作,还有相反的 takeUnless
。
3.7.5 标准函数使用示例
data class User(var name: String)
fun main(args: Array<String>) {
val user = User("user")
// let 与 run 都会返回闭包的执行结果,区别在于 let you闭包参数,而 run 没有闭包参数
// let 的闭包参数是调用的对象,即 it 是 User 对象
val letResult = user.let { "let::${it.javaClass}"}
println(letResult)
// this 是调用的对象,即 this 是 User 对象
val runResult = user.run { "run::${this.javaClass}" }
println(runResult)
// also 与 apply 都不返回闭包的执行结果,区别在于 also 有闭包参数,而 apply 没有闭包参数
user.also {
println("also:${it.javaClass}")
}
user.apply {
println("apply:${this.javaClass}")
}
// takeIf 的闭包返回一个判断结果,为 false 时 takeIf 函数会返回空
// takeUnless 与 takeIf 相反,闭包的判断结果为 true 时函数会返回空
user.takeIf {
it.name.length > 0
}?.also {
println("姓名为${it.name}")
} ?: println("姓名为空")
user.takeUnless {
it.name.length > 0
}?.also {
println("姓名为空")
} ?: println("姓名为${user.name}")
}
3.8 扩展不是万能的
kotlin是一种静态类型语言,我们创建的每个对象不仅具有运行时,还具有编译时类型。在使用扩展函数时,要清楚的了解静态和动态调度之间的区别。
3.8.1 静态与动态调度
以一个例子来说明什么是静态和动态调度。
class Base {
public void fun() {
System.out.println("I'm Base foo!");
}
}
class Extended extends Base {
@Override
public void fun() {
System.out.println("I'm Extended foo!");
}
}
Base base = new Extended();
base.fun();
输出:
I'm Extended foo!
我们声明一个名为 base
的变量,它具有编译时类型 Base
和运行时类型 Extended
。当我们调用时,base.fun()
将动态调度该方法,这意味着运行时类型 Extended
的 fun()
被调用。
当我们调用重载方法时,调度变为静态并且仅取决于编译时类型。
void foo(Base base) {... }
void foo(Extended extended) { ... }
public static void main(String[] args) {
Base base = new Extended();
foo(base); // 即使base是Extended实例,但是会调用foo(Base base)
}
3.8.2 扩展函数始终静态调度
扩展函数都有一个接收器(receiver),由于接收器实际上只是字节代码中编译方法的参数,因此你可以重载它,但不能覆盖它。这可能是成员和扩展函数之间最重要的区别:前者是动态调度的,后者总是静态调度的。
open class Base
class Extended: Base()
fun Base.foo() = "I'm Base.foo!"
fun Extended.foo() = "I'm Extended foo!"
fun main(args: Array<String>) {
val instance: Base = Extended()
val instance2 = Extended()
println(instance.foo()) // I'm Base.foo!
println(instance2.foo()) // I'm Extended foo!
}
3.8.3 类中的扩展函数
如果我们在类的内部声明扩展函数,那么它将不是静态的。如果该扩展函数加上 open
关键字,我们可以在子类中进行重写。这是否意味着它将被动态调度?
当在类内部声明扩展函数时,它同时具有调度接收器和扩展接收器。
-
调度接收器(dispatch receiver):扩展被声明为成员时存在的一种特殊接收器,它表示声明扩展名的类的实例
-
扩展接收器(extension receiver):与kotlin扩展密切相关的接收器,表示我们为其定义扩展的对象
// X是调度接收器,Y是扩展接收器
class X {
fun Y.foo() = "I'm Y.foo"
}
如果将扩展函数声明为 open
,则它的调度接收器只能是动态的,而扩展接收器总是在编译时解析。
open class Base
class Extended : Base()
open class X {
open fun Base.foo() {
println("I'm Base.foo in X")
}
open fun Extended.foo() {
println("I'm Extended.foo in X")
}
}
class Y : X() {
override fun Base.foo() {
println("I'm Base.foo in Y")
}
override fun Extended.foo() {
println("I'm Extended.foo in Y")
}
}
X().deal(Base()) // I'm Base.foo in X
Y().deal(Base()) // I'm Base.foo in Y - dispatch receiver被动态处理
X().deal(Extended() // I'm Base.foo in X - extension receiver被静态处理
Y().deal(Extended() // I'm Base.foo in Y
可以发现 Extended
的扩展函数始终没有被调用。决定两个 Base
类扩展函数执行哪一个,直接因素是执行 deal()
的类的运行时类型。
扩展函数使用注意事项:
-
如果该扩展函数是顶级函数或成员函数,则不能被覆盖
-
我们无法访问其接收器的非公共属性
-
扩展接收器总是被静态调度
3.8.4 被滥用的扩展函数
fun Context.loadImage(url: String, imageView: ImageView) {
Glide.with(this)
.load(url)
.placeholder(R.mipmap.img_default)
.error(R.mipmap.ic_error)
.into(imageView)
}
ImageActivity.kt
this.loadImage(url, imageView)
上面的扩展函数其实是滥用的,实际上并没有以任何方式扩展现有类。
Context
作为上下文已经承担了很多责任,基于 Context
扩展还很可能产生 ImageView
于传入上下文周期不一致导致的问题。
正确做法是在 ImageView
进行扩展:
fun ImageView.loadImage(url: String) {
Glide.with(this.context)
.load(url)
.placeholder(R.mipmap.img_default)
.error(R.mipmap.ic_error)
.into(this)
}
// 图片请求框架扩展
object ImageLoader {
fun with(context: Context, url: String, imageView: ImageView) {
Glide.with(context)
.load(url)
.placeholder(R.mipmap.img_default)
.error(R.mipmap.ic_error)
.into(imageView)
}
}
3.9 高阶函数
高阶函数指的是将函数用作一个函数的参数或者返回值的函数(如果你觉得高阶函数好难看不懂,你可以简单的把它看成是一个callback回调【大多数时候可以作为callback用】,这样会好理解一些)。
如下一个高阶函数:
fun test(block: () -> Unit) {
block()
}
test {
...
}
一个高阶函数格式如下:
高阶函数名称: [类型].(参数列表) -> 返回值
block: () -> Unit
block: (Int, Int) -> Int
3.9.1 将函数作为参数的高阶函数
public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
var sum: Int = 0
// 遍历字符串字符
for (element in this) {
sum += selector(element) // 高阶函数的操作由外部处理
}
return sum
}
val str = "abc"
val sum = str.sumBy {
// 遍历字符串每个字符转为int值返回
it.toInt()
}
println(sum)
3.9.2 将函数作为返回值的高阶函数
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body() // 高阶函数操作后返回类型T
} finally {
lock.unlock()
}
}
val result = lock(lock) {
// 不需要写return关键词
sharedResource.operation()
}
3.9.3 自定义高阶函数
fun operation(num1: Int, num2: Int, result: (Int, Int) -> Int) {
return result(num1, num2)
}
val plus = operation(10, 10) { num1, num2 ->
num1 + num2
}
val reduce = operation(10, 10) { num1, num2 ->
num1 - num2
}
val ride = operation(10, 10) { num1, num2 ->
num1 * num2
}
val except = operation(10, 10) { num1, num2 ->
num1 / num2
}
3.9.4 run()和T.run()的区别
run()
和 T.run()
两个函数差不多,两者之间的区别可以通过源码查看:
public inline fun <T> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
可以发现 T.run()
的高阶函数 block()
是一个扩展在 T
类型下的函数,说明这个高阶函数可以使用当前对象 T
的上下文。当我们想要使用当前对象的上下文的时候,可以使用这个函数。
"kotlin".run {
// 可以使用this对象,它代表对象"kotlin"
println("length=${this.length}")
println("first=${first()}")
println("last=${last()}")
}
上面说明的扩展函数 run
、let
、also
、takeIf
都是有使用到高阶函数。
4 处理集合:可变参数、中缀调用和库支持
4.1 扩展java集合的API
val strings: List<String> = listOf("first", "second", "fourteenth")
strings.last() // 输出fourteenth
val numbers: Collection<Int> = setOf(1, 14, 2)
numbers.max() // 输出14
last和max都是扩展函数
fun <T> List<T>.last(): T { .. }
fun Collection<Int>.max(): Int { .. }
4.2 可变参数:让函数支持任意数量的参数
val list = listOf(2, 3, 5, 7, 11)
// vararg是可变参数修饰符,相当于java的可变参数符号"..."
fun listOf<T>(vararg values: T): List<T> { .. }
// kotlin和java的另一个区别是,当需要传递的参数已经包装在数组中时,调用该函数的语法
// 在java中,可以按原样传递数组,而kotlin则要求你显式地解包数组,以便每个数组元素在函数中能作为单独的参数来调用
// 这个功能被称为展开运算符,而使用的时候,不过是在对应的参数前面放一个*
fun main(args: Array<String>) {
val list = listOf("args:", *args); // 展开运算符展开数组内容
println(list)
}
4.3 键值对的处理:中缀调用和解构声明
4.3.1 中缀调用
// to是一种特殊的函数,称为中缀调用
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
// 下面是等价的
1.to("one")
1 to "one"
// 中缀调用可以与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数
// 要允许使用中缀符号调用函数,需要使用infix修饰符标记
// Pair是kotlin标准库中的类,用来表示一对元素
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
4.3.2 解构声明
我们想要从对象获取数值存储到临时变量时经常会这样做:
val b1 = Bird(20.0, 1, "blue")
val weight = b1.weight
val age = b1.age
val color = b1.color
kotlin为我们提供了解构声明,我们就可以这样做:
val b1 = Bird(20.0, 1, "blue")
val (weight, age, color) = b1
println("$weight, $age, $color")
但是kotlin对于数组的解构也有一定限制,在数组中它默认最多允许赋值5个变量。
解构内部的原理是通过 componentN
的方法来实现,N代表类中属性的顺序:
// 数据类Bird反编译为java代码
public final double component1() { return this.weight; }
public final int component2() { return this.age; }
@NotNull
public final String component3() { return this.color; }
5 字符串和正则表达式的处理
kotlin的正则表达式和java相同
5.1 分割字符串
// 可以指定多个分隔符,且在java中"."是需要做转义操作的而在kotlin不用
"12.345-6.A".split(".", "-") // 输出[12, 345, 6, A]
5.2 正则表达式和三重引号的字符串
// val path = "/Users/yole/kotlin-book/chapter.adoc"
fun parsePath(path: String) {
val directory = path.substringBeforeLast("/") // /Users/yole/kotlin-book
val fullname = path.substringAfterLast("/") // chapter.adoc
val filename = fullname.substringBeforeLast(".") // chapter
val extension = fullname.substringAfterLast(".") // adoc
println("Dir:$directory, name:$filename, ext:$extension")
}
fun parsePath(path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
val (directory, filename, extension) = matchResult.destructured
println("Dir:$directory, name:$filename, ext:$extension")
}
}
6 让你的代码更整洁:局部函数和扩展
在java中减少重复代码时会使用 Extract Method抽取分解函数来实现重用代码,但是这样可能让代码更费解,因为你以一个包含许多小方法的类告终,而且它们之间并没有明确关系。
在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}:empty name")
}
if (user.address.isEmpty() {
throw IllegalArgumentException("Can't save user ${user.id}:empty address")
}
...
}
上面判断 isEmpty()
的代码是重复的,在kotlin中可以将验证代码放到局部函数中,可以摆脱重复,并保持清晰的代码结构:
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
// 在saveUser()方法中声明一个validate()局部函数验证有效性
// 局部函数可以访问所在函数中的所有参数和变量
// 即validate()局部函数可以直接使用saveUser()函数的User参数
fun validate(value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}:empty $fieldName")
}
}
// 调用局部函数验证代码有效性
validate(user.name, "Name")
validate(user.address, "Address")
// 执行后续操作
...
}
将上面代码放到扩展函数中:
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 $id, empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave()
}