Kotin是一种 静态类型的 编程语言。这意味着在编译时就可以确定程序中每 一个表达式的类型。编译器可以验证你所访问的对象中的方法和字段。 Kotlin是一个 编译语言。这意味着在你运行Kotlin代码之前,你必须编译它。 Kotlin源码通常存放在带有 .kt 后缀的文件中。Kotlin编译器分析源码并生成 .class 文件,就像Java编译那样。你可以使用 kotlinc 命令从命令行来编译你的代码,然后使用 java 命令来执行你的代码:
kotlinc <source file or directory> -include-runtime -d <jar name>
java -jar <jar name>
以下是静态类型的一些好处:
- 性能 - 由于不需要再运行时判断需要调用哪个方法,方法调用将会变得更快。
- 可读性 - 编译器校验了程序的准确性,所以在运行时发生崩溃的可能性将会降低。
- 可维护性 - 由于你能看到代码调用了什么类型的对象,使用不熟悉的代码将会变得更加容 易。
- 工具支持 - 静态类型使得可靠的重构、精确的代码填充和其他的IDE特性变得可能。
函数式编程:
- 函数是一等公民 - 你把函数(行为块)看做是一个值。你可以把。存储在变量中,把它们 作为一个参数进行传递或者从其他函数中返回它们。
- 不变性 - 你使用的是不可改变的对象。一旦它被创建,它的状态便不可更改。
- 没有副作用 - 你使用的纯函数对于给定的相同输入将会返回相同的结果。同时,它不会修 改其他对象的状态或者和外界进行交互。
fun findAlice() = findPerson { it.name == "Alice" }
一、Kotlin基础
1.基本元素:函数与变量
- fun 关键词被用来声明一个函数。fun main(){}
- 参数类型写在参数名后面。val a:Int
- 函数可以声明在文件的顶层。你不需要把它放入一个类中。
- 数组只是一个类。
- 可以省略行末的分号.
1) 函数
fun main(a:Int, b:Int):Int {
return if (a>b) a else b
}
- 函数返回值 写在参数列表后面,以冒号分割。
- kotlin没有三元运算符,此处 if 就代表 三元。
在Kotlin中,if 是一个表达式,并不是一个声明。 两者的区别在于,表达式有值。它可以用作另一个表达式的一部分。然而,一个声明却总是闭合块中的一个顶层元素,而没有自己的值。 在Java中,所有的控制结构都属于声明(statement)。而在Kotlin中,循环 以外的大多数控制结构都是表达式。
另一方面,赋值在 Java中是表达式,但在Kotlin中却是声明。
//精简上面函数
fun main(a:Int, b:Int):Int = if (a>b) a else b
如果一个函数拥有大括号,我们说这个函数有一个块主体。如果它直接返回一个表达式,我 们说它有一个表达式主体。
// 如果函数返回 Unit 类型,该返回类型应该省略:
fun foo() { // 省略了 ": Unit"
}
很多场合 ⽆参的函数 可与 只读属性 互换。 底层算法优先使⽤属性⽽不是函数:
- 不会抛异常
- O(1) 复杂度
- 计算廉价(或缓存第⼀次运⾏)
- 不同调⽤返回相同结果
2) 变量 var、val
变量的声明 以关键字开始(var 或 val),不是以类型开始,类型放置在变量名后面,一般可以省略。
//鼓励你使用不可变的,而不是可变数据的。 val answer:Int = 32 --(省略)--> val answer = 32 //kotlin会自己类型推断
//如果一个变量没有初始化,你需要显式的指定它的类型: val answer:Int answer = 43
- val(从一个值) 不可变的引用。用val声明一个变量不能在初始化后重新分配值。它对应于 Java中的final变量。
- var(从一个变量) 可变的引用。它的值是可以改变的,比如变量。这个声明对应于Java中 的常规(非final)变量。
- 默认的,在Kotlin中,应该尽量使用 val 声明所有的变量。仅在你必要的时候将 val 改为 var 。使用不可变引用、不可变对象和函数没有副作用.
- 一个 val 变量在使用时必须被初始化而且只能一次(但可以分情况进行初始化)。
val msg:String
if (isTrue){
msg = "Success"
}else {
msg = "failed"
}
注意,尽管一个 val 引用本身是不可变的,也不可被改变。但是它指向的对象可能是可变 的。例如,下面的代码是完全有效的:
val languages = arraylistOf("Java") //声明一个不可变的引用
languages.add("Kotlin") //引用指向可变的对
尽管 var 关键字允许一个变量改变自己的值,但是他的类型是固定的。(在初始化时固定了类型)例如,下面的代码不 能成功编译:
var answer = 42
answer = "no answer" //错误:类型匹配错误(int类型不能在赋值字符串了)
//但可以强制转换:(这里字符串需是 数字类型 强制转换 int 类型)
字符串模板:
val name = "Kotlin"
println("Hello, $name!") //复杂的表达式 放在大括号内 ${}
println("Hello, ${name}!")
println("Hello, ${if (args.size > 0) args[0] else "someone"}!") //也可以在双引号内部嵌套双引号
//若想打印 $ 符号,需要转义:println("\$")
3) 数字类型:
- 十进制:123 Long类型:123L
- 十六进制:0x0F
- 二进制:0b0101
【注意】不支持八进制
- 默认double:123.5、12.2e10
- Float类型:123.4F 【可以使用下划线0xFF_7E,1_000】
//注意数字装箱不必保留同⼀性:
val a: Int = 10000
print(a === a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a //如果把?去掉就是TRUE
print(boxedA === anotherBoxedA) // !!!输出“false”!!!
另⼀⽅⾯,它保留了相等性:
print(a == a) // 输出“true”
print(boxedA == anotherBoxedA) // 输出“true”
kotlin不能隐式转换类型,必须显示转换:.toXXX()
每个数字类型⽀持如下的转换: toByte(): Byte toShort(): Short toInt(): Int toLong(): Long toFloat(): Float toDouble(): Double toChar(): Char
val l = 1L + 3 <----------> Long + Int => Long
4) 位运算
对于位运算,没有特殊字符来表⽰,⽽只可⽤中缀⽅式调⽤命名函数:(只⽤于 Int 和 Long)
val x = (1 shl 2) and 0x000FF000
- shl(bits) ‒ 有符号左移 (Java 的 <<)
- shr(bits) ‒ 有符号右移 (Java 的 >>)
- ushr(bits) ‒ ⽆符号右移 (Java 的 >>>)
- and(bits) ‒ 位与 &
- or(bits) ‒ 位或 |
- xor(bits) ‒ 位异或 ^
- inv() ‒ 位非 ~
5) 字符 Char
- 字符⽤ Char 类型表⽰。 它们不能直接当作数字
- 字符字⾯值⽤单引号括起来: '1'。
- 特殊字符可以⽤反斜杠转义。 ⽀持这⼏个转义序列:\t、 \b、\n、\r、'、"、\ 和 $。
- 编码其他字符要⽤ Unicode 转义序列语法:'\uFF00'。
- 可以显式把字符转换为 Int 数字:c.toInt()
- 当需要可空引⽤时,像数字、字符会被装箱。 装箱操作不会保留同⼀性。
6) 布尔
- 布尔⽤ Boolean 类型表⽰,它有两个值: true 和 false。
- 若需要可空引⽤布尔会被装箱。
- 内置的布尔运算有:
- || ‒ 短路逻辑或
- && ‒ 短路逻辑与
- ! - 逻辑⾮
7)数组
数组在 Kotlin 中使⽤ Array 类来表⽰。
创建数组: arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 arrayOfNulls() 可以⽤于创建⼀个指定⼤⼩、元素都为空的数组。
val asc = Array(5, { i -> (i * i).toString() }) //创建⼀个 Array<String> 初始化为 ["0", "1", "4", "9", "16"] [] 运算符代表调⽤成员函数 get() 和 set()。
原⽣类型数组: ByteArray、 ShortArray、IntArray 等等等。 这些类和 Array 并没有继承关系。
val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2]
//java | //kotlin
ArrayList<T> | arrayListOf("aa","bb")
Array<T>(size){lambda - 最后一句是返回} | arrayOf("aaa","bbb") - 需要 toList 打印
Arrays.asList = listOf //数组集合 | (*array) - 解包数组
8)字符串 String
字符串⽤ String 类型表⽰。 字符串是不可变的。可以用索引访问:s[i] 字符串字面值:
val s = "hello" val s = """ hello """ val s = """ |tell me """.trimMargin() //去除字符串前后空格,默认 | 用作边界前缀 val s = "tell me".trimMargin(">") //参数是代表边界前缀
字符串模板:(模板表达式以 $ 开头)
val s = "hello: $i" // i 是变量 val s = "length is ${s.length}" //⽤花括号扩起来的任意表达式 val price = """ ${'$'}9.99 """ //字⾯值 $ 字符(它不⽀持反斜杠转义)
【见:下方 “三引号字符串”】 <br>
2.包
public 是默认的可见性。 类的思想是封装数据以及用于将数据转换为单一实体的代码的。
1)包
源⽂件(即 kt 文件)通常以包声明开头:
package foo.bar fun baz() {} class Goo {} ... 源⽂件所有内容(⽆论是类还是函数)都包含在声明的包内。 所以上例中 baz() 的全名是 foo.bar.baz、Goo 的全名是 foo.bar.Goo。 //如果没有指明包,该⽂件的内容属于⽆名字的默认包。
导入:
可以导⼊⼀个单独的名字,如. import foo.Bar // 现在 Bar 可以不⽤限定符访问
也可以导⼊⼀个作⽤域下的所有内容(包、类、对象等) import foo.* //“foo”中的⼀切都可访问
如果出现名字冲突,可以使⽤ as 关键字在本地重命名冲突项来消歧义: import foo.Bar // Bar 可访问 import bar.Bar as bBar // bBar 代表“bar.Bar”
关键字 import 并不仅限于导⼊类;也可⽤它来导⼊其他声明:
- 顶层函数及属性
- 在对象声明中声明的函数和属性;
- 枚举常量
与 Java 不同, Kotlin 没有单独的 "import static" 语法; 所有这些声明都⽤ import 关键字导⼊。
<br>
2)目录和包结构
所有定义在文件中的声明(类、函数和属性)都会被放置在这个包里面。
- 如果(它们)在同一 个包里面,其他文件中定义的声明也能够被直接使用。
- 如果不在同一个包,它们需要被导入。跟 Java 一样,导入声明放在文件的开头,同样是使用 import 关键字。
package geometry.shapes //1包声明
import java.util.Random //2导入标准的Java类库
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
//顶层函数
fun createRandomRectangle():Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}
//Kotlin 并没有在导入类和函数之间做区别。它允许你使用 import 关键字导入任何类型的声明。
//你可以通过名字导入顶层的函数:
package geometry.example
import geometry.shapes.createRandomRectangle //1 通过名字来导入函数
fun main(args: Array<String>) {
println(createRandomRectangle().isSquare) //2 很少概率会打印true
}
//你也可以通过在报名后面添加 .* 来导入定义在某一个的包里所有的声明。
//注意这个通配符导入不仅会是得包内所有的类可见,也会使得【顶层函数】可见。
//kotlin的包层级并不需要跟随目录的层级
<br>
3. ‘if’ 和 ‘when’
1)if 表达式
在 Kotlin 中, if是⼀个表达式,即它会返回⼀个值。 因此就不需要三元运算符(条件 ? 然后 : 否则) ,因为普通的 if 就能胜任这个⻆⾊。
// 传统用法 即为传统用法
// 作为表达式
val max = if (a > b) a else b
// if的分⽀可以是代码块,最后的表达式作为该块的值:
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
如果你使⽤ if 作为表达式⽽不是语句(例如:返回它的值或者 把它赋给变量),该表达式需要有 else 分⽀。
<br>
2)when
kotlin中没有Switch,用when代替。when 是一个返回值的表达式。 使用常量表达:
//1. -> 后面有多条语句时可以使用大括号包裹起来{}
fun getMnmonic(color: Color) = //1直接返回一个when表达式,【此处Color是枚举类,见“枚举类”章节】
when (color) { //2如果颜色等于枚举常量,返回对应的字符串
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Grave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
>>> println(getMnemonic(Color.BLUE))
Battle
//2. 在一个分支中合并多个值,用逗号将它们分隔开:
fun getWarmth(color: Color) = when(color) {
Color.RED, Color.ORANGGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
>>> println(getWarmth(Color.ORANGE))
warm
//3. 通过导入 常量值 来简化代码:
import ch02.colors.Color //1 导入在另一个包里声明的Color类
import ch02.colors.Color.* //2 显式地导入枚举常量,然后通过名字来使用它们
fun getWarmth(color: Color) = when(color) {
RED, ORANGE, YELLOW -> "warm" //3 通过名字来使用常量
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
<br> 使用任意对象 Switch必须使用常量(枚举常量,字符串,数字字面量),when允许任意的对象。 如果 when 作为⼀个表达式使⽤,则必须有 else 分⽀, 除⾮编译器能够检测出所有的可能情况都已经 覆盖了。
//1.⽤任意表达式(⽽不只是常量)作为分⽀条件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
//2.检测⼀个值在(in)或者不在(!in)⼀个区间或者集合中:【见下方 “in”】
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
//3.是检测⼀个值是(is)或者不是(!is)⼀个特定类型的值。 【见下方 “is”】
val hasPrefix = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
//4.
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)) { //1 一个when表达式的参数可以是任意的对象。它检查分支的等价性。
setOf(RED, YELLOW) -> ORANGE //2 枚举颜色键值对可以是混合的
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color") //3 如果没有一个分支被匹配,将执行该语句
}
>>> println(mix(BLUE, YELLOW))
GREEN
//一个 set 是一个元素顺序无关的集合
<br> 使用不带参数的 when 前一个例子(的实现)有些效率低下。你每次调用这个函数,它都会创建 多个仅仅是用来检查给出的两个颜色是否匹配另外两个颜色的 Set 实例。你可以通过使用不带参数的 when 语句来达到这个目的。这样代码可读性会降低,但这往往是你为了达到更好性能而必须付出的代价: 如果 when 表达式没有使用任何参数,分支条件将会是任意的布尔表达式。
fun mixOptimized(c1: Color, c2: Color) =
when { //不带参数的when
(c1==RED && c2==YELLOW) ||
(c1==YELLOW && c2==RED) -> ORANGE
(c1==YELLOW && c2==BLUE) ||
(c1==BLUE && c2==YELLOW) -> GREEN
(c1==BLUE && c2==VIOLET) ||
(c1==VIOLET && c2==BLUE) -> INDIGO
else -> throw Exception("Dirty color") //3 如果没有一个分支被匹配,将执行该语句
}
>>> println(mixOptimized(BLUE, YELLOW))
GREEN
// mixOptimized 函数跟之前的 mix 函数做的是同样一件事。它的好处是不会创建额外的对象,但(相应的)代 价是变得难以阅读。
<br> 代码块作为 if 和 when 的分支 if 和 when 都能够使用块作为分支。(一般,代码块的最后一个表达式是结果)
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value //1 这是代码块中的最后一个表达式。如果e是Num类型的它将会被返回。
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right //2 如果e是Sum类型的,这个表达式将会被返回
}
else -> throw IllegalArgumentException("Unknown expression")
}
>>> println(evalWithLogging(Sum(Sum(Num(1), Num(2)), Num(4))))
num: 1
num: 2
sum: 1 + 2
num: 4
sum: 3 + 4
7
//“代码中的最后一个表达式就是结果”这个规则在所有可以使用代码并需要一个(返回)结果的 地方都是有效的。
//同样的规则对 try代码块catch从句 有效。
使⽤可空布
val b: Boolean? = ……
if (b == true) {
……
} else {
// `b` 是 false 或者 null
}
<br>
4.集合遍历
while 循环跟 Java 中的一样。for 循环只有一种形式,等价于 Java 中的 for-each。
1)while 循环
//Kotlin 有 while 和 do-while 循环
while (condition) { //1 当while条件为真时,执行主体代码
/*...*/
}
do {
/*...*/
} while (condition) //2 第一次无条件的执行主体代码。在这之后,当条件为真时才执行。
<br>
2)for 循环
Kotlin 使用了 ranges 区间的概念。 一个范围,你用 ..
操作符来写它:
val oneToTen = 1..10 // 等同于 1 <= i && i <= 10
【注意】: Kotlin 中的范围是闭合的或者说是包含的。这意味着第二个值也始终是范围的一部分。
//fizz替换被 3 整除的数,buzz替换被 5 整除的数,FizzBuzz替换3和5的乘积
fun fizzBuzz(i:Int) = when {
i % 15 ==0 -> "FizzBuzz " //1 如果i能被15整除,返回FizzBuzz。就像在Java中,%是求模运算符
i % 3 ==0 -> "Fizz " //2 如果i能被3整除,返回Fizz
i % 5 ==0 -> "Buzz "
else -> "$i " //4 否则返回这个数字的原始值
}
>>> for (i in 1..100) {
... print(fizzBuzz(i))
... }
// .. 语法始终产生一个包含终点的范围( .. 右边的值)。
for (i in 4..1) print(i) //什么都不输出
//倒序,且步数为2
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
//步进值(step),这个步进值也可以是负 的。(经测试,好像step不能为负值,必须position值)
//半闭合范围 用 util 函数。
for (x in 0 until size) 循环等价于 for (x in 0..size-1)
//
for (i in 1..100) { …… } // 闭区间:包含 100
for (i in 1 until 100) { …… } // 半开区间:不包含100 即 [1, 100)
for (x in 2..10 step 2) { …… } //步进
for (x in 10 downTo 1) { …… } //倒叙
for (i in array.indices) { ... } //基于索引
for ((index, value) in array.withIndex()) { ... } //⽤库函数 withIndex
if (x in 1..10) { …… }
rangeTo() //Int、Long类型,浮点数Double、Float未定义不能用于迭代
downTo() //整型类型
reversed() //为每个 *Progression 类定义的,并且所有这些函数返回反转后的数列。
step() //为每个 *Progression 类定义的,都返回带有修改了 step 值的数列。step值必须始终为正
//请注意,返回数列的 last 值可能与原始数列的 last 值不同,以便保持不变式 (last - first) % step == 0 成⽴。 如:
(1..12 step 2).last == 11 // 值为 [1, 3, 5, 7, 9, 11] 的数列
(1..12 step 3).last == 10 // 值为 [1, 4, 7, 10] 的数列
(1..12 step 4).last == 9 // 值为 [1, 5, 9] 的数列
区间 实现了 公共接口:ClosedRange<T> 闭区间,它是为可⽐较类型定义的。数列*Progression:IntProgression、 LongProgression、 CharProgression。
遍历映射集
val binaryReqs = TreeMap<Char, String>() // 1 使用了TreeMap,因此键值是有序的
for (c in 'A'..'F') { // 2 使用字符范围,从A到F遍历字符
val binary = Integer.toBinaryString(c.toInt()) // 3 把ASCII编码转换成二进制
binaryReqs[c] = binary // 4 以c为键把数值保存在映射集
//代码 binaryReps[c] = binary 等价于 Java 版中的 binaryReps.put(c, binary)。
}
for ((letter, binary) in binaryReqs) { // 5 遍历一个映射集,吧映射的键跟值分配 给两个变量
println("$letter = $binary") // 6 即 key-value 值
}
//------⽤库函数 withIndex
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) { // 通过索引遍历集合
println("$index: $element")
}
//----下标索引:indices
val items = listOf("apple", "banana", "kiwi")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
<br>
3)使用 in 检查
你可以使用 in 操作符来检查一个值是否在某个范围内(闭区间),或者相反的, !in (操作符)来检查一个值是否不再某个范围内。这个逻辑只是隐藏在了标准库中 range 类的实现中.
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
>>> println(isLetter('q'))
true
>>> println(isNotDigit('x'))
true
in 和 !in 操作符在 when 表达式中也是有效的:
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!" //1 判断值是否在0到9的范围内
in 'a'..'z', in 'A'..'Z' -> "It's a letter!" //2 你可以合并多个范围
else -> "I don't know…"
}
>>> println(recognize('8'))
It's a digit!
范围并没有局限于字符。如果你有任何支持(通过实现 java.lang.Comparabble 接口的) 实例比较算法的类.
>>> println("Kotlin" in "Java".."Scala") // "Java".."Kotlin"和"Kotlin".."Scala"是一样的
true
//注意字符串在这里是可以按字符顺序比较的,因为 String 类就是这样实现 Comparable 接口 的。
// in 检查对集合也是有效的:
>>> println("Kotlin" in setOf("Java", "Scala")) // 这个集合并没有包含"Kotlin"字符串
false
<br>
4)返回 和 跳转
- return:默认从最直接包围它的函数或者匿名函数返回。
- break:终⽌最直接包围它的循环。
- continue:继续下⼀次最直接包围它的循环。 所有这些表达式都可以⽤作更⼤表达式的⼀部分:这些表达式的类型是 Nothing 类型。见 Kotlin简介 5 <br>
标签格式:标识符后面跟@ 如:abc@,foo@
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
break 跳转到刚好位于该标签指定的循环后⾯的执⾏点。
continue 继续标签指定的循环的下⼀次迭代。
标签处返回 由于 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。(默认是从最直接包围它的函数中返回。)
val ints = listOf(1,2,0,4,5)
fun foo() {
ints.forEach {
if (it == 0) return
print(it)
}
} //1,2
//1. 这个 return 表达式从最直接包围它的函数即 foo 中返回。
(注意,这种 ⾮局部的返回 只⽀持传给内联函数的 lambda 表达式。 )
2. 如果我们需要从
lambda 表达式中返回,我们必须给它加标签并⽤以限制 return。
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit //只是当前值返回,之后的语句还是会执行(相当于过滤了此值)
print(it)
}
} //1,2,4,5
3. 现在,它只会从 lambda 表达式中返回。 通常情况下使⽤隐式标签更⽅便。 该标签与接受该 lambda 的函数同名。
fun foo() {
ints.forEach {
if (it == 0) return@forEach //(相当于过滤了此值)
print(it)
}
} //1,2,4,5
4. 或者,我们⽤⼀个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数⾃⾝返回
fun foo() {
ints.forEach(fun(value: Int) {
if (value == 0) return //(相当于过滤了此值)
print(value)
})
} //1,2,4,5
//5. 当要返⼀个回值的时候,解析器优先选⽤标签限制的 return,即
return@a 1 //意为“从标签 @a 返回 1”
<br>
5. 类型检查 is
is
是否是,跟 Java 中的 instanceof 很相似。
!is
与 !( obj is String) 相同
<br>
智能类型转换:
合并类型检查和转换 (smart cast)
1. 普通对象的智能转换
if (x is String) { print(x.length) } // x ⾃动转换为字符串
//反向检查
if (x !is String) return
print(x.length) //x ⾃动转换为字符串
//在 && 和 || 的右侧 智能转换
if (x !is String || x.length == 0) return
if (x is String && x.length > 0) { print(x.length) // x ⾃动转换为字符串 }
--------------------------------------------------------
2. 类对象的智能转换
//day01.kt
interface Expr
class Num(val value: Int) : Expr //1 带有一个属性、值而且实现了Expr接口的简单的值对象类
class Sum(val left: Expr, val right: Expr) : Expr //2 求和操作的参数可以是任意的Expr:Num对象或者其他的Sum对象
//求和
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num // 1 显式的Num类型转换是多余的
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left) //2 变量e是智能类型转换
}
throw IllegalArgumentException("Unknown expression")
}
>>> println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
7
//Kotlin 中通过 is 检查一个变量是否为一个指定的类型。跟 Java 中的 instanceof 很相似。
// 如果在Java中:你检查了某个指定的类型,则后面需要强转类型来访问其成员,(如果多次使用,可以将强转结果保存到变量中)。
// 但是在Kotlin中:如果你检查了变量的特定类型,后续你不需要执行类型转换。你可以把它当做你检查的目标类型来使用。如上述1 可以省略as Num
// (实际上,编译器为你执行了类型转换,我们把这叫做智能类型转换(smart cast)。)
//当且仅当一个变量在 is 检查之后不再改变时,智能类型转换才会起作用。
//属性必须是一个 val (不可变类型),同时它不能有自定义的访问器。否则,它不能验证每一个属性的访问是否会返回同样的值。
/ 智能转换能否适⽤根据以下规则:
- val 局部变量⸺总是可以;
- val 属性⸺如果属性是 private 或 internal,或者该检查在声明属性的同⼀模块中执⾏。 智能转换不适⽤于 open 的属性或者具有⾃定义 getter 的 属性;
- var 局部变量⸺如果变量在检查和使⽤之间没有修改、并且没有在会修改它的 lambda 中捕获;
- var 属性⸺决不可能(因为该变量可以随时被其他代码修改)。
//Kotlin中并没有三元操作符,因为,与Java不同,if 表达式返回一个值。所以修改上述方法:
fun eval(e: Expr): Int =
if (e is Num) {
e.value
}
else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
>>> println(eval(Sum(Num(1), Num(2))))
3
//如果 if 分支里只有一个表达式,闭合的括号是可选的。
//如果 if 分支是一个代码块,最后的一句表达式作为结果返回。 可以用 when 替代
fun eval(e: Expr): Int =
when (e) {
is Num -> //1 用于检查参数类型的when分支
e.value //2 这里使用了智能类型转换
is Sum -> //1 用于检查参数类型的when分支
eval(e.right) + eval(e.left) //2 这里使用了智能类型转换
else ->
throw IllegalArgumentException("Unknown expression")
}
<br>
“不安全的”转换操作符 as
相当于强制转换
val x: String = y as String
//请注意, null 不能转换为 String 因该类型不是可空的, 即如果 y 为空,上⾯的代码会抛出⼀个异常。
//为了匹配 Java 转换语义,我们必须在转换右边
有可空类型,就像:
val x: String? = y as String?
“安全的”(可空)转换操作符 as?
为了避免抛出异常,可以使⽤ 安 全 转换操作符 as?,它可以在失败时返回 null:
val x: String? = y as? String // as? 的右边是⼀个⾮空类型的 String,但是其转换的结果是可空的。
<br>
二、函数
- 声明 Kotlin 中的函数使⽤ fun 关键字声明
- 用法 1)调⽤函数使⽤传统的⽅法:val result = double(2) 2)调⽤成员函数使⽤点表⽰法:Test().foo() // 创建类 Test 实例并调⽤ foo
- 中缀表示法,当 1)他们是成员函数或扩展函数 2)他们只有⼀个参数 3)他们⽤ infix 关键字标注
// 给 Int 定义扩展
infix fun Int.shl(x: Int): Int { …… }
// ⽤中缀表⽰法调⽤扩展函数
1 shl 2 // 等同于这样 1.shl(2)
- 参数 : 使⽤ Pascal 表⽰法定义,即 name : type 。 参数⽤逗号隔开。 每个参数必须有显式类型。
fun powerOf(number: Int, exponent: Int) { …… }
- 默认参数 = 函数参数可以有默认值,当省略相应的参数时使⽤默认值。与其他语⾔相⽐,这可以减少 重载数量。
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) { …… } 重写⽅法总是使⽤与基类型⽅法相同的默认参数值。 重写方法不能有默认参数值: open class A { open fun foo(i: Int = 10) { …… } } class B : A() { override fun foo(i: Int) { …… } // 不能有默认值 }
- 命名参数 参数名 = 当⼀个函数有⼤量的参数或默认参数时这会⾮常⽅便。
joinToString(collection, prefix =" ") 在调⽤ Java 函数时不能使⽤命名参数语法,因为 Java 字节码并不 总是保留函数参数的名称。
- 返回 Unit 的函数 如果⼀个函数不返回任何有⽤的值,它的返回类型是 Unit。Unit 是⼀种只有⼀个值的类型。 这个 值不需要显式返回
fun printHello(name: String?): Unit { ... //return Unit 或者 return 是可选的 }
- 单表达式函数 当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可
fun double(x: Int): Int = x * 2 //当返回值类型可由编译器推断时,显式声明返回类型Int是可选的
- 显式返回类型 具有块代码体的函数必须始终显式指定返回类型,除⾮他们旨在返回 Unit (可以省略unit)
- 可变数量的参数(Varargs) 函数的参数(通常是最后⼀个)可以⽤ vararg 修饰符标记。如果 vararg 参数不是列表中的最后⼀个参数,可以使⽤ 命名参数语法传递其后的参数的值,或者,如果参数具有 函数类型,则通过在括号外部 传⼀个 lambda。
伸展(spread)操作符
当我们调⽤ vararg 函数时,常用如:asList(1, 2, 3),但是如果我们已经有⼀个数组 并希望将其内容传给该函数,我们使⽤伸展(spread)操作符(在数组前面加*
)
val a = arrayOf(1, 2, 3) val list = asList(-1, 0, *a, 4)
- 局部函数(函数中的函数) Kotlin ⽀持局部函数,即⼀个函数在另⼀个函数内部 局部函数可以访问外部函数(即闭包)的局部变量。
//1:外部函数,供外部调用
fun dfs(a: Int) {
//2:局部函数,可以访问外部函数的 局部变量
fun dfs(current: Int, visited: Set<Int>) {
dfs(1, visited) //调用2,也可以调用1
}
dfs(2, HashSet()) //调用2,也可以调用1
}
- 成员函数 成员函数是在类或对象内部定义的函数
class Test() { fun foo() { print("Foo") } } 成员函数以
点
表⽰法调⽤ Test().foo() // 创建类 Test 实例并调⽤ foo
- 泛型函数 泛型参数,通过在函数名前使⽤尖括号指定。
fun <T> singletonList(item: T): List<T> { //… }
- 内联函数 inline 1.Kotlin标准函数(就是复制代码到引用的位置)
- 扩展函数(见下)
- 尾递归函数 tailrec 这允许⼀些通常⽤循环写的算法改⽤递归函数来写,⽽⽆堆栈溢出的⻛险。
当⼀个函数⽤ tailrec 修 饰符标记并满⾜所需的形式时,编译器会优化该递归,留下⼀个快速⽽⾼效的基于循环的版本。
tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
《==》
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return y
x = y
}
}
//求和
tailrec fun sum(n: Int, result: Int): Int = if (n <= 0) result else sum(n-1,result + n)
要符合 tailrec 修饰符的条件的话,函数必须将其⾃⾝调⽤作为它执⾏的最后⼀个操作。 在递归调⽤后有更多代码时,不能使⽤尾递归,并且不能⽤在 try/catch/finally 块中。 ⽬前尾部递归只在 JVM 后端中⽀持。 18. 高阶函数 与 lambda 表达式 Kotlin简介 3
1.创建集合
Kotlin使用了标准的Java集合类。Kotlin并没有它 自己的集合类。, Kotlin 区分可变集合和不可变集合(lists、 sets、 maps 等)。
val set = setOf(1,3,44) //创建集合 (只读)
set.max()
val list = listOf(1,5,22) //创建列表(只读)
list.last()
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three") //创建映射(只读)
>>> println(set.javaClass) //1 javaClass在Kotlin中等价于Java中getClass()
class java.util.HashSet
>>> println(list.javaClass)
class java.util.ArrayList
>>> println(map.javaClass)
class java.util.HashMap
>>> println(list) //1 调用toString(),因为可以省略
[1,5,22]
//只读 list
val list = listOf("a", "b", "c")
//只读 只读 map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
//-------------------------------(见数组部分)-------------------------------------------
不可变(类型) 可变(只是类型) 创建(只读) 创建(读写) 可变不重复
List<out T> MutableList<T> listOf() mutableListOf()
Set<out T> MutableSet<T> setOf() mutableSetOf() hashSetOf()
Map<K, out V> MutableMap<K, V> mapOf() mutableMapOf() hashMapOf()
//-----------------------------------------------------------------------------------
/在⾮性能关
键代码中创建 map 可以⽤⼀个简单的惯⽤法来完成:mapOf(a to b, c to d)
//------
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers //如果⼀个集合只存在只读引⽤即利用listOf创建的,可以认为该集合完全不可变。
println(numbers) // 输出 "[1, 2, 3]"
numbers.add(4)
println(readOnlyView) // 输出 "[1, 2, 3, 4]" 与numbers指向相同的底层 list 并会随之改变。
readOnlyView.clear() // -> 不能编译
val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)
/
有时你想给调⽤者返回⼀个集合在某个特定时间的⼀个快照, ⼀个保证不会变的:
class Controller {
private val _items = mutableListOf<String>()
val items: List<String> get() = _items.toList() //不是直接=,_items的修改不会影响此处
}
这个 toList 扩展⽅法只是复制列表项,因此返回的 list 保证永远不会改变。
/
集合的 API (标准函数库)
val items = listOf(1, 2, 3, 4)
items.first() == 1
items.last() == 4
items.filter { it % 2 == 0 } //返回 [2, 4]
val rwList = mutableListOf(1, 2, 3)
rwList.requireNoNulls() // 返回 [1, 2, 3]
if (rwList.none { it > 6 }) println("No items above 6") //输出“No items above 6”
val item = rwList.firstOrNull()
/ sort、zip、fold、reduce
/
集合的 API (标准函数库):Kotlin简介 2 <br> <br> 你需要元素用分号隔开,同时被括号隔开而不是使用默认的实现(就像这样):(1;2;3)。Java项目使用了第三方的库,例如 Guava 和Apache Commons,或者在项目内部重新实现这个逻辑。在Kotlin中,这个函数是标准库的一部分。
//使用自己的逻辑
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) //1 在第一个元素之前不添加分隔符
result.append(element)
}
result.append(postfix)
return result.toString()
}
>>> val list = listOf(1,2,3)
>>> println(joinToString(list, ";", "(", ")"))
(1; 2; 3)
<br>
?. 和 ?:
?. :是 if not null 的缩写,list?.size ?: :是 if not null and else 的缩写,list.size ?: 0
val data = ... data?.let{ ... //如果data不为null,会执行到此处,如果为null,则不执行 } <br>
2.函数的调用
1)指定参数名 (命名参数)
当调用Kotlin编写的方法时,你看可以指定一些传递给函数的参数的名字,如果你在某个调用中指定了一个参数,你也应该为后续的所有参数指定名字来避免(造成)困惑。
joinToString(collection, separator =" ", prefix =" ", postfix =".")
2)默认参数值
//自带方法重载
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ", //1 默认的参数值
prefix: String = "",
postfix: String ="") : String {...}
>>> joinToString(list, ", ", "", "")
1, 2, 3
>>> joinToString(list)
1, 2, 3
>>> joinToString(list, "; ")
1; 2; 3
>>> joinToString(list, prefix = "# ") //如果后面参数不使用默认值,则也需要使用命名参数
# 1, 2, 3
Java并没有参数默认值的概念,当你在Java中调用带有参数默认值的Kotlin函数时,你必须显式的指定所有参数的值。你可以用 @JvmOverloads 对它进行标注。它将指示编译器以从最后一个参数开始逐个忽略每一个参数的方式产生重载的Java 函数。
如果你用 @JvmOverloads 标注了 joinToString(),以下的重载将会产生: /* Java */ String joinToString(Collection<T> collection, String separator, String prefix, String postfix); String joinToString(Collection<T> collection, String separator, String prefix); String joinToString(Collection<T> collection, String separator); String joinToString(Collection<T> collection); 每一个重载(函数)都为忽略了签名的参数使用了默认值。 <br>
3)顶层函数
摆脱静态工具类:顶层(top-level)函数。就是工具类的 静态方法。
在Kotlin中,不需要创建无意义的类。可以把这些函数直接放在代码文件的最顶层而不需要在任何的类的内部。这样的函数依然是声明在文件顶部的包 的成员。同时如果你想要从其他包里调用它们,你依然需要导入它们。但是不必要的额外的嵌套层级不复存在了。
让我们把 joinToString 函数直接放进 strings 包内。用下面的内容 创建一个叫 join.kt 的文件:
package strings fun joinToString(...): String { ... }
编译出的Java代码:
/* Java */ package strings; public class JoinKt { //对应前一个例子的文件名,join.kt public static String joinToString(...) { ... } }
Kotlin编译器产生的类的名字跟包含函数的文件的名字一样。文件中所有的顶 层函数都会被编译成这个类的静态方法。 Java中调用:
/* Java */ import strings.JoinKt; ... JoinKt.joinToString(list, ",", "", "");
自命名:@file:JvmName
以上的类名是固定生成的JoinKt,但是你可以自己命名:给文件添加 @JvmName 标注。把它放置在文件的开头位于包名之前的地方: @file:JvmName("StringFunctions") //1 指定类名的标注 package strings //2 包声明跟在文件标注后面 fun joinToString(...): String { ... }
现在函数可以这样调用: /* Java */ import strings.StringFunctions; StringFunctions.joinToString(list, ",", "", ""); <br>
4)顶层属性(TOP-LEVEL Properties)
就像函数那样,属性可以放在文件的顶层。保存类外部的单独的数据块并不常用,但依然非 常有用。 举个例子,你可以使用 var 属性来计算某个操作被执行的次数:
var opCount = 0 //1 包级别的属性声明
fun performOperation() {
opCount++ //2 改变属性的值
//...
}
fun reportOperationCount() {
println("Operation performed $opCount times") //3 读取属性的值
}
这样一个属性的值将会被存储在一个静态字段中。 顶层属性(Top-level properties)也允许 你在你的代码中定义常量:
val UNIX_LINE_SEPARATOR = "\n"
如果想变成 public static final 字段,用 const 关键字。
const val UNIX_LINE_SEPARATOR = "\n" 《=》 public static final String UNIX_LINE_SEPARATOR = "\n";
5)扩展函数
把方法添加到他人的类中:扩展函数和属性 概念上讲,一个扩展函数:它是一个可以作为一个类成员进行调用的函数,但是定义在这个类的外部。
//day02.kt package strings //添加一个方法来计算一个字符串的最后一个 字符 fun String.lastChar(): Char = this.get(this.length - 1) //this可以省略,"this"引用是隐式的
在你的函数的名字之前放置你需要 扩展的类 或者 接口的名字。这个类名叫做接收器类型(receiver type),而你调用的扩展函数的值叫做接收器对象(receiver object)。
this
关键字在扩展函数内部对应到接收者对象
(传过来的在点符号前的对象)
// 为 MutableList<Int> 添加⼀个 swap 函数(交换两个数的位置)
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
//使用
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”得到“list”的值
//泛化
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
你可以使用跟普通类成同样的语法来调用这个函数:
>>> println("Kotlin".lastChar())
n
// String 是接收器类型,同时"Kotlin"是接收器对象。在某种意义上,你已经添加了你的方法到 String 类。
// 在扩展函数中,你可以直接访问你扩展的类的函数和属性,
// 就像在定义在这个类中 的方法那样。
// 注意扩展函数并不允许你打破封装。跟定义在类中的方法不同,扩展函数并不能访问私有或保护访问属性的类成员。
在底层方面,一 个扩展函数是一个接受接收器对象作为第一个参数的 静态方法
。
<br>
导入扩展函数
import strings.lastChar 或 import strings.* /用通配符(* ) 或 import strings.lastChar as last //使用 as 关键词来该改变你所导入的类或者函数的名字 val c = "Kotlin".last()
Java导入并使用:
//一般是直接导入扩展函数所在的文件(同顶层函数),作为类名,当然可以通过 @file:JvmName 进行自定义 /* Java */ import strings.JoinKt char c = JoinKt.lastChar("Java");
上述顶层函数 修改为 扩展函数(可以减少参数一)
//比上面少了参数一
fun <T> Collection<T>.joinToString( // 1 用Collection<T>声明一个扩展函数
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) //1 在第一个元素之前不添加分隔符
result.append(element)
}
result.append(postfix)
return result.toString()
}
>>> val list = arrayListOf(1, 2, 3)
>>> println(list.joinToString(" "))
1 2 3
//指定具体的集合对象
fun Collection<String>.join(separator: String = ",", prefix: String = "", postfix: String = "")
= joinToString(separator, prefix, postfix)
>>> println(listOf("one", "two", "eight").join(" "))
one two eight
>!> listOf(1, 2, 8).join()
Error: Type mismatch: inferred type is List<Int> but Collection<String> was expected.
不可覆盖(overriding)的扩展函数
方法覆盖在Kotlin对平常的成员函数是有效的,但是你不能覆盖一个扩展函数::因为Kotlin以静态解析它们
open class View {
open fun click() = println("View clicked")
}
class Button: View() { //1 Button类继承自View类
override fun click() = println("Button clicked")
}
>>> val view: View = Button()
>>> view.click() //1 Button类的示例的方法被调用。//AU:这个措辞正确吗?好像有点不对。TT
Button clicked
//但是对于扩展函数
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
>>> val view: View = Button()
>>> view.showOff() //1 扩展函数被静态的方式进行解析
I'm a view!
- 【注意】如果类有一个成员函数跟一个扩展函数有着相同的签名,成员函数总是优先的。
class C { fun foo(){ print("member")} } //成员函数总是优先 fun C.foo(){ print("extension")}
但是扩展重载就可以了
fun C.foo(i:Int){ ... } //重载成员函数即可
扩展不具有 多态性:子类是子类,父类是父类
open class Base
class Child: Base()
fun Base.foo() = println("this is from base")
fun Child.foo() = println("this is from child")
fun executeFoo(base: Base) = base.foo()
fun main(args: Array<String>) {
var base = Base()
var child = Child()
executeFoo(base)
executeFoo(child) //全部执行的是 父类的扩展
}
可空接受者
这样的扩展可以在对象变量上调⽤, 即使其值为 null,并且可以在函数体内检测 this == null,这能让你 在 没有检测 null 的时候调⽤ Kotlin 中的toString():检测发⽣在扩展函数的内部。(如果此变量为null则返回“null”,否则返回toString)
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后, “this”会⾃动转换为⾮空类型,所以下⾯的 toString()
// 解析为 Any 类的成员函数
return toString()
}
<br>
6)扩展属性(Extension properties)
扩展属性:通过属性语法进行访问的API来扩展类。而不是函数的语法。尽管它们被叫做属性,它们不能拥有任何的状态:它不可能添加额外的字段到现有的Java对象 实例。
//fun 变为 val 或者 var,名字后面没有括号,(不能有初始化,只能由getter、setter定义)
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1) //1 属性的访问器
set(value: Char) { //2 属性的设置器
this.setCharAt(length - 1, value)
}
val <T> List<T>.lastIndex:Int
get() = size - 1
//val Foo.bar = 1 // 错误:扩展属性不能有初始化器
//访问扩展属性(的方式)跟成员属性完全一样:
>>> println("Kotlin".lastChar)
n
>>> val sb = StringBuilder("Kotlin?")
>>> sb.lastChar = '!'
>>> println(sb)
Kotlin!
>>> println(sb.also {sb.lastChar = 'i'})
Kotlini
//Java中使用,是用 “文件名”.getLastChar("Java")
vararg 关键字,允许你声明一个带有任意个参数的函数
<br>
7)可变参数(Varargs)
:可以接受任意个参数 的函数。
在传递参数时:Kotlin和Java之间的不同点是当你需要 传递的参数已经打包在一个数组里面时的函数调用语法。在Java中,你原样传递数组,然而 Kotlin却要求你显式的对数组进行拆箱(unpack the array)。【在变量前面 添加 *
符号】
如果可变参数不是函数的最后一个参数,后面的参数需要通过命名参数来传值.
val strings = listOf("aaa","bbb") //List<String>类型的
val args: Array<String> = strings.toTypedArray() //Array 类型
val list = listOf("vvv", *args) //把strings添加到 list 中 (需要拆箱操作)
<br>
8)中缀调用(infix call)
为了创建映射,你使用 mapOf() 函数:
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three") 这行代码中 的 to 并不是内置的语法,相反的,而是一个方法的特殊调用,叫做中缀调用(infix call)。 在一个中缀调用,方法的名字 被放在 目标对象名 和 参数 之间,而且没有其他的分隔符。 <br> 下面 的两个调用是等价的: 1.to("one") //1 以常规的方式来调用 1 to "one" //2 使用中缀标记来调用函数 <br> 中缀调用:使用
infix
修饰 infix fun Any.to(other: Any) = Pair(this, other) // to 函数的简化版本 val (number, name) = 1 to "one" 这个特性叫做 析构声明 ( destructuring declaration )。
析构声明这一特性并不局限于元组。你也可以为两个独立的变量 key 和 value 分 配一个映射集合。这一特性对于循环也是有效的,如 withIndex() 后面将会描述声明时候可以析构一个表达式并将其分配给多个变量。
//在扩展函数中使用中缀表达式
infix fun Int.add(i:Int):Int = this + i
infix fun Int.加(i:Int):Int = this + i
fun main(args: Array<String>) {
println(5 add 10)
println(5 加 10)
}
//在成员函数中使用中缀表达式
class Infix2 {
infix fun add(i: Int):Int {
return 5 + i
}
fun printValue() {
println(this add 10)
println(add(10))
}
}
fun main(args: Array<String>) {
var infix2 = Infix2()
infix2.printValue()
}
<br>
9)字符串拆分
文件路径,文件名,文件后缀
fun parsePath(path: String) {
val directory = path.substringBeforeLast("/") //beforeLast:最后一个 / 的之前的部分
val fullName = path.substringAfterLast("/") //afterLast:最后一个 / 的之后的部分
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")
}
>>> parsePath("/Users/yole/kotlin-book/chapter.adoc")
Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc
使用正则
fun parsePathRegexp(path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex()
//val regex = "(.+)/(.+)\\.(.+)".toRegex()
val matchResult = regex.matchEntire(path)
//如果匹配结果为成功(不是 null ),你分配 把它的 destructured 属性值分配给对应的变量。
if (matchResult != null) {
val (directory, filename, extension) = matchResult.destructured
println("Dir: $directory, name: $filename, ext: $extension")
}
}
//正则表达式被写在一个三引号字符串。在这样一个字符串中,你不需要转义 任何字符,包括反斜杠。
//因此,你可以用 \. 来编码点号而不是你在普通字符串中写的 \\.
- 模式匹配开头的任意字符,所以第一组 (.+) 包含了最后一个斜杠前的子字符串。这个子字符串包含了之前所有的斜杠,因为他们匹配了“任意字符”模式。 2.相似的,第二组包含了最后一个点号之前的子字符串。 3.第三组包含了 其余所有的字符串。
<br>
三引号字符串
三引号字符串的目的不仅仅是避免转义字符。这样的一个字符串字面量恶意包含任意的字 符,包括换行符。 但是你不能使用像 \n 这样的特殊字符另一方面,你不需要转义 \ ,所以, windows风格的路径 "C:\Users\yole\kotlin-book" 可以被写成 """C:\Users\yole\kotlinbook""" 。 如果你需要在你的字符串中使用一个美元符号的字面量,你必须使用一个嵌套的 表达式。比如: val price = """${'$'}key""" 。 <br>
10)本地函数(局部函数)和扩展
冗余问题:利用 本地函数
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException("Cannot save user ${user.id}: Name is empty")
}
if (user.address.isEmpty()) { //字段验证是冗余的(代码)
throw IllegalArgumentException("Cannot save user ${user.id}: Address is empty")
}
//...Save user to the database
}
>>> saveUser(User(1, "", ""))
java.lang.IllegalArgumentException: Cannot save user 1: Name is empty
本地函数(局部函数) Kotlin ⽀持局部函数,即⼀个函数在另⼀个函数内部
fun saveUser(user: User) {
//1 声明一个本地函数来验证任意的字段(user参数可以省略)
fun validate(user: User, value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Cannot save user ${user.id}: $fieldName is empty")
}
}
validate(user, user.name, "Name") //2 调用本地函数来验证特定的字段
validate(user, user.address, "Address")
//...Save user to the database
}
验证逻辑没有重复,本地函数已经访问了嵌套函数的 所有参数和变量。所以 user 可以省略。
利用扩展函数-本地函数
fun User.validateBefore() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Cannot save user ${id}: $fieldName is empty")
}
}
validate(name, "Name") //1 你可以直接访问User对象的属性
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave() //2 调用扩展函数
//...Save user to the database
}
伴⽣对象的扩展
可以为伴⽣对象定义 扩展函数和属性:
class MyClass {
companion object { } //将被称为 "Companion"
}
//扩展
fun MyClass.Companion.foo() {
// ……
}
//调用,像伴⽣对象的其他普通成员,只需⽤类名作为限定符去调⽤他们
MyClass.foo()
扩展声明为成员(类中类的扩展)
在⼀个类A内部你可以为另⼀个类B声明扩展。 其中A的对象成员可以⽆需通过限定符访问。 扩展声明所在的类(A)的实例称为 分发接收者 ,扩展⽅法调⽤所在的接收者类型(B)的实例称为 扩展接收者 。
class B {
fun bar() { ... } //扩展接受者
}
class A {
fun baz() { ... } //分发接受者
fun B.foo() {
bar() //调用 B.bar()
baz() //调用 A.baz
}
fun caller(b: B) {
b.foo() //调用扩展函数
}
}
//对于分发接收者 和 扩展接收者的 成员名字冲突的情况,扩展接收者优先。要引⽤分发接收者的成员你可以使⽤ 限定的 this 语法。
class A {
fun B.foo(){
toString() //扩展接收者优先: 调用 B.toString
this@A.toString() //分发接受者 this 限定,调用 A.toString
}
}
<br>
类中扩展的继承
open class D {} //因为D1要继承,所以必须open
class D1:D(){}
open class C{
open fun D.foo(){ println("D.foo in C") } //因为C1中要重写,必须open
open fun D1.foo() { println("D1.foo in C") }
fun caller(d: D) {
d.foo() // 调⽤扩展函数
}
}
class C1 : C() {
override fun D.foo() { println("D.foo in C1") }
override fun D1.foo() { println("D1.foo in C1") }
}
C().caller(D()) // 输出 "D.foo in C"
C().caller(D1()) // 输出 "D.foo in C" —— 扩展接收者静态解析
C1().caller(D()) // 输出 "D.foo in C1" —— 分发接收者虚拟解析
C1().caller(D1()) // 输出 "D.foo in C1"