** 1.
** 2.
** 3. 函数的定义和调用
*** 5
1. [ ]
2. [ ]
3. [ ] 多行三重引号的字符串
1. [ ] val price =“““$ {'$'} 99.9 ””” 使用嵌入式表达式。
2. [ ] 使用 trimMargin 函数更好的格式化
*** 6 局部函数和扩展
1. [ ] 局部函数
1. [ ] 在局部函数中访问外层函数的参数
2. [ ] 提取逻辑到扩展函数
3.
*** 7 小结
1. [ ] Kotlin 没有定义自己的集合类,而是在 Java 集合类的基础上提供了更丰富的 API
2. [ ] Kotlin 可以给函数定义默认值,这样大大大降低了重载函数的必要性,而且命名参
数让多参数函数的调用更加易读
3. [ ] Kotlin 允许更灵活的代码结构:函数和属性都可以直接在文件中声明 ,而不仅仅
是在类中作为成员
4. [ ] Kotlin 可以用扩展函数和属性来扩展任何类的 API,包括在外部库中定义的类,而不需
要修改其源代码,也没有运行时开销
5. [ ] 中缀调用提供了处理单个参数的,类似调用运算符方法的简明语法
6. [ ] Kotlin 为普通字符串和正则表达式提供了大量的方便字符串处理的函数
7. [ ] 三重引号的字符串提供了一种简洁的方式,解决了原本在 Java 中需要进行大量啰
嗦的转义和字符串连接的问题
8. [ ] 局部函数帮助你保持代码整洁的同时,避免重复
** 4. 类,对象和接口
*** 1. 类和接口
1. [ ] Kotlin 的声明默认是 final 和 public ,嵌套的类默认并不是内部类:它们并没
有包含对其外部类的隐式引用
2. [ ] Kotlin 的接口可以包含抽象方法的定义以及非抽象方法的实现,但它们不能包含任
何状态
1. [ ] 使用 interface 关键字而不是 class 来声明一个 Kotlin 的接口
interface Clickable {
fun click()
}
2. [ ] 实现一个接口
class Button : Clickable {
override fun click() = println("I was clicke")
}
3. [ ] Kotlin 在类名后面使用冒号来代替了 Java 中的 extends 和 implements 关键
字。和 Java 一样,一个类可以实现任意多个接口,但是只能继承一个类
4. [ ] 在 Kotlin 中使用 override 修饰符是强制要求的,这会避免先写出实现方法再
添加抽象方法造成的意外重写:你的代码将不能编译,除非你显示的将这个方法标注
为 override 或者重名它
5. [ ] 接口的方法中可以有一个默认的实现。与 Java8 不同的是,Java8 中需要你在
这样的实现上标注 default 关键字,Kotlin 没有特殊的注解:只需要提供一个方法
体。让我们 来给 Clickable 接口添加一个带默认实现的方法
interface Clickable {
fun click()
fun showOff() = println("I'm clickable ")
}
6. [ ] 如果你在你的类中实现了两个接口,这两个接口有一个共同的函数名,并且都是
默认实现的方法,在实现类中,将会使用哪个?答案是,任何一个都不会使用。取而
代之的是,如果你没有显示实现共同方法 (例如showOff),你会得到如下的编译错误:
7. [ ] override fun showOff() {
/**
使用尖括号加上父类型名字 的 super 表明了你想要调用哪一个父类的方法
*/
super<Clickable> .showoff()
super<Focusable>.showoff()
}
或者
override fun showOff() = super<Clickable>.showOff()
8.
3. [ ] open ,final 和 abstract 修饰符:默认为 final
1. [ ] 子类重写父类的 open 函数,则该函数在子类中也是 open
如果该方法不允许后面的子类重写,需要加上 final 来修饰 override
2.
4. [ ] abstract 抽象类始终是 open,抽象类不能被实例化,抽象成员始终是 open 的,不
要显式的使用 open 修饰符
1. [ ] 抽象类中的非抽象函数并不是默认是 open 的,但是可以标注为 open
2. [ ]
5. [ ] 如果你想要确保你的类不被其他代码实例化,必须把构造方法标记为 private。下
面就是你该怎样把主构造方法标记为 private.
class Secretive private constructor() { }
6. [ ] 构造方法:用不同的方式 来初始化父类
open class View {
constructor(ctx: Context) {
//some code
}
constructor(ctx: Context, attr: AttributeSet) {
//some code
}
}
这个类没有声明主构造方法(因为在类头部的类名后面并没有括号),但是它声明了两
个从构造方法。从构造方法使用 constructor 关键字引用。只要需要,可以声明任意多
个从构造方法。
class MyButton : View {
constructor(ctx: Context): super(ctx) {
//***
}
constructor(ctx: Context, attr: Attributeset) {
//***
}
//以上两个构造方法都调用了父类的构造方法
}
使用不用的父类构造方法
class MyButton : View {
constructor(ctx: Context) : this(ctx, MY_STYLE) {
//委托给这个类的另一个构造方法
//...
}
constructor(ctx: Context, attr: Attributeset) : super(ctx, attr) {
//...
}
}
7. [ ] 修改访问器的可见性
1. [ ] 访问器的可见性默认与属性的可见性相同。但是如果需要可以通过在 get 和
set 关键字前放置可见性修饰符的方式来修改它。要想知道怎么使用它,我们来看一
下例子。
class LengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}
*** 2. 非默认属性和构造方法
*** 3. 数据类
1. [ ] 编译器生成的方法: 数据类和类委托
1. [ ] 通用对象方法
1. [ ] 字符串表示:toString()
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name = $name, postalCode=$postalCode)"
}
2. [ ] equals()
在 Kotlin 中 , == 检查对象是否相等,而不是比较引用 ,这里会编译成调
用 “equals”
== 表示相等性
在 Java 中,== 比较的是基本数据类型和引用类型。如果是基本类型比较的是
值,如果是引用类型比较的是引用。
在 Kotlin 中, == 运算符是比较两个对象的默认方式:本质上说它就是通过调
用 equals 来比较两个值的。因此,如果 equals 在你的类中被重写了,你能够
很安全地使用 == 来比较实例。要想进行引用比较,可以使用 === 运算符,这与
Java 中的 == 比较对象引用的效果一模一样
class Client(val name: String, val postalCode: Int) {
override fun equals(other: Any?): Boolean {
if (other == null || other !is Client)
return false;
return name == other.name && postalCode == other.postalCode
}
override fun toString() = "Client (name=$name, postalCode=$postalCode)"
}
3. [ ] Hash 容器: hashCode()
hashCode 方法通常与 equals 一起被重写。
>>> val processed = hashSetOf(Client(Client("Alice",342562)))
>>> printlnprocessed.contains(Client("Alice",. 342562))()
false
false 的原因是 Client 类缺少了 hashCode 方法。因此它违反了通用的
hashCode 契约:如果两个对象相等,它们必须有着相同的 hash 值。 processed
set 是一个 HashSet。在 HashSet 中值以一种优化过得方式来比较:首先比较它
们的 hash 值,然后只有当它们相等时才会去比较真正的值。前一个例子中
Client 类的两个不同的实例有着不同的 hash 值,所以 set 认为它不包含第二
个对象,即使 equals 会返回 true。因此,如果不遵循规则,HashSet 不能在这
样的对象上正常工作。要修复这个问题,可以向类中添加 HashCode 的实现
class Client (val name: String, val postalCode: Int ) {
...
override fun hashCode():Int = name.hashCode * 31 + postalCode
}
2. [ ] 自动生成通用方法的实现
1. [ ] 数据类 Client
data class Client(val name: String, val postalcode: Int)
现在就得到了一个重写了所有标准 Java 方法的类
equals 用来比较实例
hashCode 用来作为例如 HashMap 这种基于哈希容器的键
toString 用来为类生成按声明顺序排序的所有字段的字符串表达形式
3. [ ] 数据类和不可变性:copy() 方法
class Client(val name: String, val postalcode: Int) {
...
fun copy(name: String = this.name, postalcode: Int = this.postalcode) =
Client(name, postalcode)
}
*** 4. 类委托
1. [ ] 类委托:使用 “by” 关键字
class CountingSet<T> (val innerSet: MutableCollection<T> = HashSet<T>()):
MutbleCollection<T> by innerSet{
var objectAdded = 0
override fun add(element: T): Boolean {
objectAdded++
return innerSet.add(element)
}
override fun addAll(c: Collection<T>): Boolean {
objectAdded += c.size
return innerSet.addAll(c)
}
}
*** 5. 使用 object 关键字
1. [ ] 对象声明: 创建单例易如反掌
object PayRoll{
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
...
}
}
对象声明通过 ojbect 关键字引入。一个对象可以非常高效地以一句话来定义一个类和
一个该类的变量
与类一样,一个对象声明也可以包括属性,方法,初始化语块等的声明。唯一不允许的
就是构造方法(包括主构造方法和从构造方法)。与普通类的实例不同,对象声明在定
义的时候就立即创建了,不需要在代码的其他地方调用构造方法。因此,为对象声明定
义一个构造方法是没有意义的。
与变量一样,对象声明允许你使用对象名加.字符的方式来调用方法和访问属性:
Payroll.allEmployee.add(Perso(...))
Payroll.calculateSalary()
使用对象来实现 Comparator
object CaseInsensitiveFileComparator: Comparator<File> {
override fun compare(file1: File, file2: File): Int {
return file1.path.compareTo(fil2.path, ignoreCase = true)
}
}
2. [ ] 伴生对象:工厂方法和静态方法的地盘
class A {
companior object {
fun bat() {
println("Companion object called")
}
}
}
>>> A.bar()
Companion object called
1. [ ] 使用工厂方法来替代从构造方法
class User private constructor(val nickname: String) {
//将主构造方法标记为私有
companion object {
//声明伴生对象
fun newSubscribingUser(email: String) = User(email.substringBefore('@'))
//用工厂方法通过 Facebook 账号来创建一个新用户
fun newFacebookUser(accountId: Int) = User(getFacebookName(accountId))
}
}
2. [ ] 作为普通对象使用的伴生对象
伴生对象是一个声明在类中的普通对象,它可以有名字,实现一个接口或者有扩展函
数或属性。
class Person(val name: String) {
companion ojbect Loader{
fun fromJSON(jsonText: String): Person = ...
}
}
>>> person = Person.Loader.fromJSON("(name: 'Dmitry')")
>>> person.name
Dmitry
>>> person2 = Person.fromJson("(name:‘Brent’)")//没有Loader
>>> person2.name
Brent
3. [ ] 在伴生对象中实现接口
interface JSONFactory<T> {
fun fromJSON(jsonText: String): T
}
class Person(val name: String) {
companion ojbect: JSONFactory<Person> {
override fun fromJSON(jsonText: String): Person = ...
//实现接口的伴生对象
}
}
这时,如果你有一个函数使用抽象方法来加载实体,可以传给它 Person 对象。
fun loadFromJSON<T> (factory: JSONFactory<T>): T {
...
}
loadFromJSON(Person) //将伴生对象的实例传入函数中
注意,Person 类的名的被当做 JSONFactory 的实例
4. [ ] Kotlin 的伴生对象和静态成员
类的伴生对象会同样被编译成常规对象:类中的一个引用了它的实例的静态字段。如
果伴生对象没有命名,在 Java 代码中它可以通过 Companion 引用来 访问:
/* Java */
Person.Companion.fromJSON("...");
如果伴生对象有名字,那就用这个名字替代 Companion
但是你也许要和这样的 Java 代码一起工作,它需要类中的成员时静态的。可以在对
应的成员上使用 @JvmStatic 注解来达到目的。如果你想声明一个 static 字段,可
以再在一个顶层属性或者声明在 object 中的属性上使用 @JvmFiled 注解。这些功
能专门为互操作性而存在,严格地讲,并不是核心语言的一部分。
5. [ ] 为伴生对象定义一个扩展函数
//business logic module
class Person(val firstName: String, val lastName: STring) {
companion {
//声明一个空的伴生对象
}
}
//client/server communication module
fun Person.Companion.fromJSON(json: String): Person {
//声明一个扩展函数
...
}
val p = Person.fromJSON(json)
你调用 fromJson 就好像它是一个伴生对象定义的方法一样,但是实际上它是作为扩
展函数在外部定义的。正如之前的扩展函数一样,看起来像是一个成员,但实际并不
是。但是请注意,为了能够为你的类定义扩展,必须在其中声明一个伴生对象,即使
是一个空的。
3. [ ] 对象表达式:改变写法的匿名内部类
ojbect 关键字不仅仅能用来声明单例式的对象,还能用来声明匿名对象。匿名对象替代
了 Java 中匿名内部类的用法。
1. [ ] 使用匿名对象来实现事件监听器
window.addMouseListener{
object : MouseAdapter() {//声明一个继承 MouseAdapter 的匿名对象
override fun mouseClicked(e: MouseEvent) {
//...
}
override fun mouseEnetered(e: MouseEvent) {
//...
}
//重写了 MouseAdapter 方法
}
}
除了去掉 对象的名字外,语法是与对象声明相同的。对象表达式声明了一个类并创
建了该类的一个实例,但是并没有给这个类或是实例分配一个名字。通常来说,它们
都是不需要名字的,因为你会将这个对象用作一个函数调用的参数。如果你需要给对
象分配一个名字,可以将其存储到一个变量中。
val listener = object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {...}
override fun mouseEntered(e: MouseEvent) {...}
}
与 Java 匿名内部类只能扩展一个类或实现一个接口不同,Kotlin 的匿名对象可以
实现多个接口或者不实现接口
注意:与对象声明不同,匿名对象不是单例的。每次对象表达式被执行都会创建一个
新的对象实例
从匿名对象访问局部变量
fun countClicks(window: Window) {
var clickCount = 0//声明局部变量非final
window.addMouseListener(object : MouseAdapter) {
override fun mouseClicked(e: MouseEvent) {
clickCount++//更新变量值
}
}
}
*** 6. 总结
1. Kotlin 的接口与 Java 的相似,但是可以包含默认实现(Java 从第8版才开始支持)和
属性
2. 所有的声明默认都是 final 和 public 的
3. 要想使声明不是 final 的,将其标记为 open
4. internal 声明在同一个模块可见
5. 嵌套类默认不是内部类,使用 inner 关键字来存储外部类的引用
6. sealed 类的子类只能嵌套在自身的声明中(Kotlin1.1 允许将子类放置在同一个文件的
任意地方)
7. 初始化语句块和从构造方法为初始化类实例提供了灵活性
8. 使用 field 标识符在访问器方法体中引用属性的支持字段
9. 数据类提供了编译器生成的 equals,hashCode, toString, Copy 和其他方法
10. 类委托帮助避免在代码中出现许多相似的委托方法
11. 对象声明是 Kotlin 中定义单例类的方法
12. 伴生对象(与包级别函数和属性一起)替代了 Java 静态方法和字段定义
13. 伴生对象和其他对象一样,可以实现接口,也可以拥有扩展函数和属性。
14. 对象表达式是 Kotlin 中针对 Java 匿名 内部类的替代品,并增加了诸如实现多个接
口的能力和修改在创建对象的作用域中定义的变量的能力等功能
** 5. Lambda 编程、(第130页)
*** 1. Lambda 表达式和成员的引用
*** 2. 以函数式风格使用集合
*** 3. 序列:惰性地执行集合操作
*** 4. 在 Kotlin 中使用 Java 函数式接口
*** 5. 使用带接收者的 lambda
** 6. Kotlin 的类型系统
*** 1. 可空性
1. [ ] 可空性和 Java
1. [ ] 为什么需要平台类型
1. 对 Kotlin 来说,把来自 Java 的所有的值都当成可空的是不是更安全?这种设
计也许可行,但是需要对永远不为空的值做大量冗余的 null 检查,因为 Kotlin
编译器无法了解到这些信息。
2. 在 Kotlin 中不能声明一个平台类型的变量,这些类型只能来自 Java 代码。
val i: Int = person.name
ERROR: Type mismatch: interred type is String! but Int was expected
String! 表示法被 Kotlin 编译器用来表示来自 Java 代码的平台类型。你不能
在自己的代码中使用这种语法。而已感叹号通常与问题的来源无关,所以通常可
以忽略它。它只是强调类型的可空性是未知的。
正确赋值可空性和非空
val a: String? = person.name
val s1: String = person.name
2. [ ] 继承
1. 在 Kotlin 中重写 Java 的方法时,可以选择把参数 和返回类型定义成可空的,
也可以选择把它们定义成非空的。例如 ,我们来看一个 Java 中
StringProcessor 接口。
/** Java
*/
interface StringProcessor {
void process(String value);
}
class StringPrinter : StringProcessor {
override fun process(value: String) {
println(value)
}
class NullableStringPrinter : StringProcessor {
override fun process(value: String?) {
if (value != null) {
println(value)
}
}
}
2. 在实现 Java 类或者接口的方法是一定要搞清楚它的可空性。因为犯法的实现可
以在非 Kotlin 的代码中被调用, Kotlin 编译器会为你声明一个声明的每一个
非空的参数生成非空断言。如果 Java 代码传给这个方法一个 null 值,断言将
会触发,你会得到一个异常,即便你从没有在你的实现中访问过这个参数的值
3. [ ] 总结
1. 安全操作的运算符(安全调用运算符?. Elvis 运算符?:和安全转换运算符
as?),还有非安全解析的运算符(非空断言!!)。你了解了库函数 let 怎样
帮助你完成简洁的的非空检查,也了解了可空类型的扩展能帮你把非空检查移动
到函数内部。我们也讨论了 Kotlin 中表示 Java 类型的平台类型。
*** 2. 基本数据类型和其他基本类型
1. [ ] Int, Boolean 及其他
1. [ ] Java 把基本数据类型和引用类型做了区分。一个基本数据类型(如 int)变量
直接存储了它的值,而一个引用类型(如 String)的变量存储的是指向包含该对象
的内存地址的引用。
Kotlin 并不区分基本数据类型和包装类型,你使用的永远是同一个类型(比如: Int):
val i: Int = 1
val list: List<Int> = ListOf(1, 2, 3)
list被 Kontlin 转换成 List<java.lang.Integer>
2. [ ] 可空的基本数据类型: Int?, Boolean? 及其他
1. [ ] Kontlin 中的可空类型不能用 Java 的基本数据类型表示,因为 null 只能存储
在 Java 的引用类型的变量中。这意味着任何使用只要使用了基本数据类型的可空版
本,它就会被编译成对应的包装类型。
3. [ ] 数字转换
1. [ ] Kotlin 不会自动地把数字从一种类型转换成另外一种,即便是转换成范围更大
的类型。例如 Kontlin 中下面这段代码不会编译
val i = 1
val l: Long = i//错误,类型不匹配
相反,必须显示的进行转换
val i = 1
val l: Long = i.toLong()
每一种基本数据类型(Boolean 除外)都定义有转换函数: toByte(), toShort(),
toChar()等,这些函数支持双向转换:既可以把小范围的类型扩展到大范围,比如
Int.toLong(), 也可以把大范围的类型截取到小范围。比如 Long.toInt().
比较两个装箱值的 equals 方法不仅会检查它们存储的值,还要比较装箱类型,所以
在 Java 中 new Integer(42).equals(new Long(42)) 会返回false
val x = 1
val list = listOf(1L, 2L, 3L)
x in list 假设 Kotlin 支持隐式转换的话仍是 false
2. 基本数据类型字面值
Kotlin 除了支持简单的十进制数字之外,还支持下面这些在代码中书写字面值的方
式
1. 使用后缀 L 表示 Long 类型(长整型)字面值 123L
2. 使用标准浮点数表示 Double(双精度浮点数)字面值:0.12, 2.0 1.2e10, 1.2e-10。
3. 使用后缀 F 表示 Float 类型(浮点数)字面值: 123.4f, .456F, le3f。
4. 使用前缀 0x 或者 0X 表示十六进制字面值: 0xCAFEBABE 或者 0xbcdL
5. 使用前缀 0b 或者 0B 表示二进制字面值:0b000000101
6. 字符串转换
Kotlin 标准库提供了一套相似的扩展方法,用来把字符串转换成基本数据类型
(toInt, toByte, toBoolean 等)
>>> println("42".toInt())
4. [ ] "Any" 和 "Any?" : 根类型
1. [ ] 和 Object 作为 Java 类层级结构的根差不多, Any 类型是 Kotlin 所有非空
类型的超类型(非空类型的根)。但是在 Java 中, Object 只是所有引用类型的超
类型(引用类型的根),而基本数据类型并不是类型层级结构中的一部分。
在 Kotlin 中, Any 是所有类型的超类型(所有类型的根),包括像 Int 这样的基
本数据类型
和 Java 一样,把基本数据类型的值赋给 Any 类型的变量时会自动装箱:
val answer: Any = 42 Any 是引用类型,所以值 42 会被装箱
在底层 Any 类型对应 java.lang.Object。
所有的 Kotlin 类都包含下面三个方法:toString, equals 和 hashCode 。这些方
法都继承自 Any。Any 并不能使用其他 Java.lang.Object的方法(比如 wait 和
notify),但是可以通过手动地把值转换成 java.lang.Object 来调用这些方法
2. [ ] Unit 类型: Kotlin 的 "void"
Kotlin 中的 Unit 类型完成了 Java 中的 void 一样的功能。当函数没什么有意思
的结果返回时,它可以用作函数的返回类型
fun f(): Unit {...} 语法上,这和写一个带有代码块体但不带类型声明的函数没有
什么不同:
fun f() {...} 显示的 Unit 声明被省略了
Kotlin 的 Unit 和 Java 的 void 到底有什么不一样呢?Unit 是一个完备的类型,
可以作为类型参数,而 void 却不行。只存在一个值是 Unit 类型,这个值也叫作
Unit,并且(在函数中)会被隐式地返回。当你在重写返回泛型参数的函数时这非常有
用,只需要让方法返回 Unit 类型的值。
interface Processor<T> {
fun process(): T
}
class NoResultProcessor: Processor<Unit> {
override fun process() { //返回Unit, 但可以省略类型说明
//do stuff 这里不需要显示的 return
}
}
接口签名要求 process 函数返回一个值: 而且,因为 Unit 类型确实有值,所以从
方法中返回它并没有问题。然而你不需要再 NoResultProcessor.process 函数中写
上显式的 return 语句,因为编译器会隐式加上 return Unit。
3. [ ] Nothing 类型: "这个函数永不返回"
fun fail(messaage: String): Nothing {
throw illegalSateException(message)
}
返回 Nothing 的函数可以放在 Elvis 运算符的右边来做先决条件检查:
val address = company.address ?: fail("No addreess")
println(adress.city)
*** 3. 集合和数组
1. [ ] 可空性和集合
1. [ ] val list = ArrayList<Int?>()
val validList = list.filterNotNull()
2. [ ] 只读集合与可变集合
1. [ ] MutableCollection 可变集合
add()
remove()
clear()
[ ] Collections
size
iterator()
contains()
3. [ ] Kotlin 和 Java
1. [ ]
4. [ ] 作为 平台类型的集合
1. [ ]
5. [ ] 对象和基本数据类型的数组
1. [ ] arrayOf
arrayOfNulls
fun main(args: Array<String>) {
for (i in args.indices) {//使扩展属性 array.indices 在下标的范围内迭代
println("Argument $i is: ${args[i]}")
}
|
2. 创建字符数组
val letters = Array<String>(26) { i -> ('a' + i).toString()}
println(letters.joinToString(""))
3. vararg 方法传递集合
val strings = listOf("a", "b", "c")
println("%s/%s/%s".format("strings.toTypedArray()"))//期望 vararg 参数时使
用展开运算符(*)传递数组
a/b/c
4. 基本数据类型数组的特殊类
IntArray, ByteArray, CharArray, BooleanArray
int[] , byte[] , char[]
没有使用装箱
val fiveZeros = IntArray(5)
val fiveZerosToo = intArrayOf(0, 0, 0, 0, 0)
val squars = IntArray(5) { i -> (i + 1) * (i + 1)}
6. [ ]
*** 4. 小结
1. [ ] Kotlin 对可空类型的支持,可以帮助我们在编译期,检查出潜在的
NullPointerException 错误。
2. [ ] Kotlin 提供了像安全调用(?.), Elvis 运算符(?:),非空断言(!!)及 let 函
数这样的工具来简洁地处理可空类型
3. [ ] as? 运算符提供了一种简单的方式来把值转换成一个类型,以及处理当它拥有不同
类型时的情况
4. [ ] Java 中的类型在 Kotlin 中被解释为平台类型,允许开发者把它们当做可空或者非
空来对待
5. [ ] 表示基本数字的类型(如 Int)看起来用起来都像普通的类,但通常会被编译成
Java 基本数据类型
6. [ ] 可空的基本数据类型(如 Int?) 对应着 Java 中的装箱基本数据类型(如 java.lang.Integer)。
7. [ ] Any 类型时所有其他类型的超类型 ,类似于 Java 的 Object。而 Unit 类比于 void
8. [ ] 不会正常终止的函数使用 Nothing 类型作为返回类型
9. [ ] Kotlin 使用标准 Java 集合类,并通过区分只读和可变集合来增强它们
10. [ ] 当你在 Kotlin 中继承 Java 类或者实现 Java 接口时,你需要考虑参数的可空性
和可变性
11. [ ] Kotlin 的 Array 类就像普通的泛型类,但它会被编译成 Java 数组
12. 基本数据类型的数组使用像 IntArray 这样的特殊类来表示
** 7. 运算符重载及其他约定(204页)
*** 1. 运算符重载
1. [ ] 重载二元运算符
1. [ ] 定义一个 plus 运算符
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x , y + other.y)
}
}
val p1 = Point(10, 20)
val p2 = Point(30, 40)
println(p1 + p2)
Point(x=40, y=60)
a+b ----> a.plus(b)
2. [ ] 将运算符定义为扩展函数
operator fun Point.plus(other, Point): Point {
return Point(x + other.x, y + other.y)
}
3. [ ] 可重载的二元算术运算符
表达式 函数名
a * b times
a / b div
a % b mod
a + b plus
a - b minus
4. [ ] 定义一个运算类型不同的的运算符
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt())
}
>> val p = Point(10, 20)
>> println(p * 1.5)
Point(x = 15, y = 30)
Kotlin 运算符不会自动支持交换性(交换 运算符的左右两边)。如果希望用户能
够使用 1.5 * p 以外,还能使用 p * 1.5 ,你需要为它定义一个单独的运算符:
operator fun Double.times(p: Point): Point
5. [ ] 定义一个返回结果不同的运算符
operator fun Char.times(count: Int): String {
return toString().repeat(count)
}
>>> println('a' * 3)
aaa
6. [ ] 没有永远位运算的特殊运算符
shl --- 带符号左移
shr --- 带符号右移
ushr --- 无符号右移
and --- 按位与
or --- 按位或
xor --- 按位异或
inv --- 按位取反
2. [ ] 重载复合赋值运算符
1. [ ] >>> val numbers = ArrayList<Int>()
>>> numbers += 42
>>> println(numbers[0])
42
2. [ ]
a += b 等价于 a = a.plus(b)
a += b 等价于 a.pluseAssign(b)
运算符可以被转换为 plus 或者 plusAssign 函数的调用
3. [ ] Kotlin 标准库支持集合的这两种方法。+ 和 - 运算符总是返回一个新的集合
+= 和 -= 运算符 用于可变集合时,始终在一个地方修改它们;而它们用于只读集合
时,会返回一个修改过的副本(这意味着只有当引用只读集合的变量被声明为 var
的时候,才能使用 += 和 -= )。作为它们的运算数,可以使用单个元素,也可以使
用元素类型一致的其他集合
3. [ ] 重载一元运算符
1. [ ] 可重载的一元运算符
表达式 函数名
+a unaryPlus
-a unaryMinus
!a not
++a,a++ inc
--a,a-- dec
2. [ ] 定义一个自增运算符
operator fun BigDecimal.inc() = this + Bigdecimal.ONE
>>> var bd = BigDecimal.ZERO
>>> println(bd++)
0
>>> println(++bd)
2
4. [ ] 重载比较运算符
1. [ ] 等号运算符 "equals"
和其他运算符不同的是, == 和 != 可以用于可空运算数
因为这些运算符事实上会检查运算数是否为 null 。比较 a == b 会检查 a是否为
null,如果不是,就调用 a.equals(b); 否则,只有两个参数都是空引用,结果才是
true
a == b ----->> a?.equals(b) ?: (b == null)
2. [ ] 恒等运算符 === 与 Java 中的 == 运算符是完全相同的:检查两个参数是否是
同一个对象的引用(如果是基本数据类型,检查它们是否是相同的值)。在实现了
equals(方法)之后,通常会使用这个运算符 来优化调用的代码。注意,=== 运算符
不能被重载
3. [ ] 排序运算符 compareTo
1. [ ] a >=b ---> a.compareTo(b) >=0
5. [ ] 集合与区间的约定
1. [ ] 通过下标来访问元素: “get” 和 “set”
val value = map[key]
mutableMap[key] = newValue
2. [ ] operator fun Point.get(index: Int): Int {
return when(index) {
0 -> x
1 -> y
else ->
throw IndexOutOfBoundsException("Invalid coordinate $Index")
}
}
>>> val p = Point(10, 20)
>>>println(p[1])
20
你只需要定义一个名为 get 的函数,并标记 operator。之后,像 p[1] 这样的表达
式,其中 p 具有类型 Point,将被转换为 get 方法的调用,
x[a,b] ---> x.get(a, b)
方括号的访问会被转换成 get 函数的调用
3. [ ]
*** 2. 约定:支持各种运算的特殊命名函数
1. [ ] "in" 的约定
1. [ ] 检查某一个对象是否属于集合
2. [ ] until 函数构建一个开区间,
a in c ---> c.contains(a)
开区间是不包括最后一个点的区间。如果用10..20构建一个普通的区间(闭区间)包
括10到20的所有数字,包括20。开区间 10 until 20包括10到19的数字,但不包括20。
矩形类通常定义成这样,它的底部和右侧坐标不是矩形的一部分,因此在这里使用开
区间是合适的
2. [ ] rangeTo 的约定
1. [ ] start..end ---> start.rangeTo(end)
2. [ ] 处理日期的区间
>>> val now = LocalDate.now()
>>> val vacation = now..now.plueDays(10)//构建一个从今天开始的10天的区间
>>> println(now.plusWeeks(1) in vacation)//检查一个特定的日期是否属于这个
区间
true
3. [ ] >>> val n = 9
>>> (0..).forEach{ print(it) }
0123456789
3. [ ] 在 “for” 循环中使用 "iterator" 的约定
实现日期区间的迭代器
operator fun ClosedRange<LocalDate>.iterator(): Interator<LocalDate> =
object : Iterator<LocalDate> {
var current = start
override fun hasNext() =
current <= endInclusive
override fun next() = current.apply(current= plusDays(1))
}
>>> val newYear = LocalDate.ofYearDay(2017, 1)
>>> val daysOff = newYear.minusDays(1).. newYear
>>> for (dayOff in daysOff) { println(dayOff) }
对应的 iterator 函数实现后,遍历 daysOff
2016-12-31
2017-01-01
4. [ ] 解构声明和组件函数
1. [ ] >>> p = Point(10, 20)
>>> val (x,y) = p
>>> println(x)
10
>>> println(y)
20
一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量
val (a, b) = p ---> val a = p.component1() val b = p.component2()
使用解构声明来返回多个值
2. 用解构声明来遍历 map
fun printEntries(map: Map<String,String>) {
for ((key, value) in map) {
println("$key -> $value")
}
}
>>> val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
>>> printEntries(map)
Oracle -> Java
JetBrains -> Kotlin
for (entry in map.Entries) {
val key = entry.component1()
val value = entry.component2()
//...
}
*** 3. 委托属性
1. [ ] 基本语法
1. [ ] class Foo {
var p: Type by Delegate()
}
2. [ ] class Foo {
private val delegate = Delegate()
var p: Type
set(value: Type) = delegate.setValue(..., vlaue)
get() = delegate.getValue(...)
"p" 的访问都会调用对应的 delegate 的 getValue 和 setValue 方法
]
按照约定,Delegate 类必须具有 getValue 和 setValue 方法(后者仅适用于可变
属性)。像往常一样,它们可以是成员函数,也可以是扩展函数,为了让例子看起来
更简洁,这里我们省略掉了参数。
class Delegate {
operator fun getValue(...) { ... }
operator fun setValue(..., value: Type) {...}
class Foo
var p: Type by Delegate()
}
>>> val foo = Foo()
>>> val oldValue = foo.p//调用delegate.getValue(...) 来实现属性的修改
>>> foo.p = newValue //通过调用 delegate.setValue(...,newValue) 来实现属性
的修改
2. [ ] 惰性初始化和 “by lazy()”
1. [ ] class Email { /* ... */}
fun loadEmails (person: Person): List<Email> {
println("Load emails for ${person.name}")
return listOf(/*...*/)
}
2. [ ] 使用支持属性来实现惰性初始化
class Person (val name: String) {
private var _emails: List<Email>? = null
val emails: List<Email>
get() {
if (_email == null) {
_emails = loadEmails(this)
}
return _emails!!
}
}
>>> val p = Person("Alice")
>>> p.emails
Load emails for Alice
>>> p.emails
3. [ ] 用委托属性来实现惰性初始化
class Person(val name: String) {
val emails by lazy { loadEmails(this)}
}
lazy 函数返回一个对象,该对象具有一个名为 getValue 且签名正确的方法,因此
可以把它与 by 关键字一起使用来创建一个委托属性。lazy 的参数是一个 lambda,
可以调用它来初始化这个值。默认情况下,lazy 函数的线程是安全的,如果需要,
可以设置其他选项来告诉它要使用哪个锁,或者完全避开同步,如果该类永远不会再
多线程环境中使用。
3. [ ] 实现委托属性
1. [ ] PropertyChangeSupport 的工具类
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangListener(listener: PropertyChangeListener) {
changesupport.addPropertyChangeListener(listener)
}
}
fun removePropertyChanngeListener(listener: PropertyChangeListener) {
changesupport.removeProperyChangeListener(listener)
}
2. [ ] 手工实现属性修改的通知
class Person (val name: String, age: Int, salary: Int) :
PropertyChangeAware() {
var age: Int = age
set(newValue) {
val oldValue == field
field = newValue
changeSupport.firePropertyChange("age", oldValue, newValue)
}
var salary: Int = salary
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange("salary", oldValue, newValue)
}
}
>>> val p = Person("Dmitry", 34, 2000)
>>> p.addPropertyChangeListener {
PropertyChangeListener {
PropertyChangeListener {
event -> println("Property ${event.propertyName} changed " +
" from ${event.oldValue} to ${event.newDate}*)
}
}
}
>>> p.age = 35
Property age changed from 34 to 35
>>> p.salary = 2100
Property salary changed from 2000 to 2100
3. [ ] 通过辅助类来实现属性变化的通知
class ObservableProperty {
val propName: String, var propValue:Int,
val changeSupport: PropertyChangeSupport
} {
fun getValue(): Int = propValue
fun setValue(newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSuppport.firePropertyChange(propName, oldValue, newValue)
}
}
class Person (
val name: String, age: Int, salary: Int
) : PropertyChangeAware () {
val _age = ObservableProperty("age",age, changeSupport)
var age: Int
get() = _age.getValue()
set(value) { _age.setValue(value){
var _salary = ObservableProperty("salary", salary, changeSupport)
var salary: Int
get() = _salary.getValue()
set(value) { _salary.setValue(value)
}
4. [ ] ObservableProperty 作为属性委托
class ObservableProperty (
var propValue: Int, val changeSupport: PropertyChangeSupport
) {
operator fun getValue(p: Person, prop: KProperty<*>: Int = propValue)
operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
class Person (
val name: String, age: Int, salary: Int
) : PropertyChangeAware() {
var age: Int by ObservableProperty(age, changeSupport)
var salary: Int by ObservableProperty(salary, changeSupport)
}
5. [ ] 使用 Delegates.observable 来实现属性修改的通知
class Person (
val name: String, age: Int, salary: Int
) : ProppertyChangeAware() {
private val observable = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(age, observable)
var salary: Int by Delegates.observable(salary, observable)
}
4. [ ] 委托属性的变化规则
1. [ ] class C {
var prop: Type by MyDelegate()
}
val c = C()
MyDelegate 实例会被保存到一个隐藏的属性中,它被称为 <delegate>。编译器也将
用一个 KProperty 类型的对象来代表这个属性,它被称为 <Property>
编译器生成的代码如下:
class C {
private val <delegate> = MyDelegate()
var prop: Type
get() = <delegate>.getValue(this, <property>)
set(value: Type) = <delegate>.setValue(this, <property>, value)
}
val x= c.prop ---> val x= <delegate>.getValue(c, <property>)
c.prop = x ---> <delegate>.setValue(c, <property>, x)
当访问属性时,会调用 <delegate> 的 getValue 和 setValue 函数
2. [ ] 在 map 中保存属性值
class Person {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes(attrname) = value
}
val name: String
get() = _attributes("name")
}
>>> val p = Person()
>>> val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
>>> for ((attrName, value) in data) {
p.setAttribute(attrName, value)
}
>>> println(attrName, value)
>>> println(p.name)
Dmitry
5. [ ] 框架中的委托属性
1.[ ] object Users : IdTable() {
val name = varchar("name", length = 50).index()
val age = integer("age")
}
class User(id: EntityID): Entity(id) {
var name: String by Users.name
var age: Int by Users.age
}
Users 对象描述数据库的一个表: 它被声明为一个对象 ,因为它对应整个表,所以只
需要一个实例,对象的属性表示数据表的列
User 的基类 Entity,包含了实体的数据库列与值的隐射。特定 User 的属性拥有这个用
户在数据库中指定的值 name 和 age
object Users: IdTable() {
val name: Column<String> = varchar("name", 50).index()
val age: Column<Int> = integer("age")
}
至于 Column 类,框架已经定义了 getValue 和 setValue 方法,满足 Kotlin 的委托
约定:
operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T {
//从数据库中获取值
}
operator fun <T> Column<T>.setValue(o: entity,desc: KProperty<*>, value: T>{
//更新值到数据库
}
*** 4. 小结
1. [ ] Kotlin 允许使用对应名称的函数来重载一些标准的数学运算,但是不能定义自己的
运算符
2. [ ] 比较运算符映射为 equals 和 compareTo 方法的调用
3. [ ] 通过定义名为 get, set 和 contains 的函数,就可以让你自己的类与 Kotlin 的
集合一样,使用 [ ] 和 in 运算符
4. [ ] 可以通过约定来创建区间,以及迭代集合和数组
5. [ ] 解构声明可以展开单个对象用来初始化多个变量,这可以方便地用来从函数返回多
个值。它们可以自动处理数据类,可以通过给自己的类定义名为 componentN 的函数来
支持
6. 委托属性可以用来重用逻辑,这些逻辑控制如何存储,初始化,访问和修改属性,这是
用来构建框架的一个强大的工具
7. [ ] lazy 标准库函数提供了一种实现惰性初始化属性的简单方法
8. [ ] Delegates.observable 函数可以用来添加属性更改的观察者
9. [ ] 委托属性可以使用任意 map 来作为属性委托,来灵活来处理具有可变属性集的对象
** 8. 高阶函数:Lambda 作为形参和返回值(232)
*** 1. 函数类型
1. [ ] 声明高阶函数
1. [ ] 高阶函数就是以另一个函数作为参数或者返回值的函数。
标准库中的 filter 函数将一个判断式函数作为参数,因此它就是一个高阶函数
list.filter{ x > 0 }
2. [ ] 函数类型
val sum = { x: Int, y: Int -> x + Y}
val action = {println(42)}
变量的显式声明
val sum: {Int, Int} -> Int = { x, y -> x + y}//有两个 Int 型参数和 Int 型
返回值的函数
val action: () -> Unit = { println(42)}//没有参数和返回值的函数
参数类型 返回类型
(Int, String) -> Unit
函数类型的返回值也可以标记为可空类型
var canReturnNull: (Int, Int) -> Int? = {null}
函数类型 的可空变量,为了明确表示是变量本身可空,而不是函数类型的返回类型
可空,你需要将整个函数类型的定义包括在括号内并在括号后面添加一个问号
var funOrNull: ((Int, Int) -> Int)? = null
函数类型的参数名
可以为函数类型声明中的参数指定名字:
fun performRequest (
url: String,
callBack: (code: Int, content: String) -> Unit //函数类型的参数现在有了名
字
) {
/* *** */
}
>>> val url = "http://kotl.in"
>>> performRequest(url) { code, content -> /* */ }// 可以使用 API 中提供的
参数名字作为 lambda 参数的名字
>>> performRequest(url) { code, page -> /* */ }//或者你可以改变参数的名字
参数名不会影响类型的匹配,当你声明一个 lambda 时,不必使用和函数类型声明中
一模一样的参数名称,但命名会提升代码可读性并且能用于 IDE 的代码补全
3. [ ] 调用作为参数的函数
1. [ ] 定义一个简单的高阶函数
fun twoAndThree(operation: (Int, Int) -> Int) {//定义一个函数类型的参数
val result = operation(2, 3)
println("The result is $result")
}
>>> twoAndThree{a, b -> a + b}
The result is 5
>>> twoAndThree{ a, b -> a * b }
The result is 6
接受者类型 参数类型 函数类型参数
fun String.filter(predicate: (Char) -> Boolean): String
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if(predicate(element))
sb.append(element)
}
return sb.toString()
}
>>> println("ab1c".filter{ it in 'a'..'z'})
filter 函数的实现非常简单明了。它检查每一个字符是否满足判断式,如果满足
就将字段添加到包含结果的 StringBuilder 中
4. [ ] 在 Java 中使用函数类
1. [ ] fun processsTheAnswer(f: (Int) -> Int) {
println(f(42))
}
/* Java */
>>> processTheAnswer(number -> number + 1);
旧版 Java
processTheAnswer(new Function1<Integer, Integer>(){
@Override
public Integer invoke(Integer number) {
return number + 1;
}
});
2. [ ] /* Java */
>>> List<String> strings = new ArrayList();
>>> strings.add("42")
>>> CollectionsKt.forEach(strings, s -> {System.out.println(s);
return Unit.INSTANCE;//必须要显示地返回一个 Unit 类型的值
});
在 Java 中,函数或者 lambda 可以退出 Unit。但因为在 Kotlin 中 Unit 类
型是有一个值的,所以需要显式地返回它。一个返回 void 的 lambda 不能作为
返回 Unit 的函数类型的实参,就像之前的例子中(String) -> Unit
5. [ ] 函数类型的参数默认值和 null 值
1. [ ] 使用了硬编码 toString 转换的 joinToString 函数
fun <T> Collection<T>.joinToString(seperator: String = ", ",
prefix: String = "",
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(seperator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
2. [ ] 给函数类型的参数指定默认值
fun <T> Collection<T>.joinToString(seperator: String =", ",
prefix: String = "",
postfix: String = "",
transform: (T) -> String = {it.toString()}):String {
val result = StringBuilder(prefix)
for ((index,element) in this.withIndex()) {
if (index > 0) { result.append(seperator)
}
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
>>> val letters = listOf("Alpha", "Beta")
>>> println(letters.joinString())
Alpha, Beta
>>> println(letters.joinToString{ it.toLowerCase()})
alpha, beta
>>> println(letters.joinToString(seperator = "! ", postfix = "! ",
transform = {it.toUpperCase()}))
ALPHA! BETA!
3. [ ] 使用函数类型的可空参数
fun <T> Collection<T>.joinToString(seperator: String = ", ", prefix:
String = "", postfix: String = "", transform: ((T) -> String)? = null): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(seperator)
val str = transform?.invoke(element)?: element.toString()
result.append(str)
}
result.append(postfix)
return result.toString()
}
6. [ ] 返回函数的函数
1. [ ] 定义一个返回函数的函数
enum class Delivery { STANDARD, EXPEDITED }
class Order(val itemCount: Int)
fun getShippingCostCalculator (delivery: Delivery): (Order) -> Double{
if (delivery == Delivery.EXPEDITED) {
return { order -> 6 + 2.1 * order.itemCount}
}
return { order -> 1.2 * order.itemCount }
}
7. [ ] 通过 lambda 去除重复代码
1. [ ] 定义站点访问数据
data class SiteVisit {
val path: String,
val duration: Double,
val os: OS
}
enum class OS { WINDOW, LINUX, MAC, IOS, ANDROID }
val log = listOf(SiteVisit("/", 34.0, OS.WINDOW), SiteVisit("/",22.0,
OS.MAC), SiteVisit("/login", 12.0, OS.WINDOWS), SiteVisit("/", 8.0,
OS>IOS),SiteVisit("/", 16.3, OS.ANDROID))
)
2. [ ]
*** 2. 高阶函数及其在组织代码过程中的应用
*** 3. 内联函数
1. [ ] 内联函数: 消除 lambda 带来的运行时开销
1. [ ] 内联函数如何运作
1. [ ]
2. [ ] 内联函数的限制
鉴于内联函数的运作方式,不是所有使用 lambda 的函数都可以被内联。但函数被
内联的时候,作为参数的了lambda 表达式的函数体会被直接替换到最终生成的代码
中。这将限制函数体中对应的(lambda)参数的使用。如果(lambda) 参数被调用,这
样的代码能被容易的内联。但如果(lambda)参数在某个地方被保存起来,以便后面可
以继续使用,lambda 表达式的代码将不能被内联,因为必须要有一个包含折线代码
的对象存在。
一般来说,参数如果被直接调用或者作为参数传递给另外一个 inline 函数,它是可
以被内联的。否则,编译器会禁止参数被内联并给出错误信息 "Illegel usage of
inline-parameter" (非法使用内联参数)
3. [ ] noinline 非内联
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
}
2. [ ] 内联集合操作
1. [ ] 有大量元素需要处理,中间集合的运行开销将成为不可忽视的问题,这时可以在
调用链后加上一个 asSequence 调用,用序列来替代集合。但正如你在前一节中看到
的,用来处理序列的 lambda 没有被内联。每一个中间序列被表示成把 lambda 保存
在其字段中的对象,而末端操作会导致每一个中间序列调用组成的调用链被执行。因
此,即便序列上的操作是惰性的,你不应该总是试图在集合操作的调用链后加上
asSequence。这只在处理大量数据的集合时有用,小的集合可以用普通的集合操作处
理
2. [ ] 决定何时将函数声明为内联
1. [ ] 使用 inline 关键字只能提高带有 lambda 参数的函数的性能,其他的情况
需要额外的度量和研究
对应普通的函数调用,JVM 已经提供了强大的内联支持,它会分析代码的执行,
并在任何通过内联能够带来好处的时候将函数调用内联。
2. [ ] 另一方面,将带有 lambda 参数的函数内联能带来好处。首先,通过内联避
免运行时开销更明显了。不仅节约了函数调用的开销,而且节约了为 lambda 创
建匿名类,以及创建 lambda 实例对象的开销。其次,JVM 目前并没有聪明到总
是能将函数调用内联。最后,内联使得我们可以使用一些不可能被普通 lambda
使用的特性,比如非局部返回。
3. [ ] 使用内联 lambda 管理资源
1. [ ] Lambda 可以去除重复代码的一个常见模式 是资源管理:先获取一个资源,完成
一个操作,然后释放资源。这里的资源可以表示很多不同的东西:一个文件,一个锁,
一个数据库事务等。实现这个模式的标准做法是使用 try/finally语句。资源在 try
代码块之前被获取,在 finally 代码块中被释放。
val l: Lock = ...
l.withLock {
//accesss the resource protected by this lock
}
这是 Kotlin 库中 withLock 函数的定义和调用
fun<T> Lock.withLock(action: () -> T): T {
lock()
try {
return action()
} finally {
unlock()
}
}
文件是另一种可以使用这种模式的常见资源类型,Java 7 甚至为这种模式引入了特
殊的语法: try-with-resource 语句。下面的代码清单展示了一个使用这个语句来
读取文件第一行的 Java 方法
2. [ ] 在 Java 中使用 try-with-resource 语句
/* Java */
static String readFirstLineFromFile(String path) throws IOExceptiono{
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
3. [ ] 使用 User 函数作资源管理
fun readFirstLineFromFile(path: String): String {
BufferedReader(FileReader(path)).user { br ->
return br.readLine()
}
}
use 函数是一个扩展函数,并用来操作可关闭的资源,它接收一个 lambda 作为参数。
这个方法调用 lambda 并且确保资源被关闭无论 lambda 正常执行还是抛出了异常。
当然,use 函数时内联函数,所以使用它并不会引发任何性能开销。
注意,在 lambda 的函数体中,使用了非局部 return 从 readFirstLineFromFile
函数中返回了一个值。
4. [ ] 高阶函数中的控制流
1. [ ] lambda 中的返回语句:从一个封闭的函数返回
1. [ ] 在一个普通循环中使用 return
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
for (person in people) {
if (people.name === "Alice") {
println("Found!")
return
}
}
println("Alice not found")
}
>>> lookForAlice(people)
Found!
2. [ ] 在传递给 forEach 的 lambda 中使用 return
fun lookForAlice(people: List<People>) {
people.forEach{
if (it.name == "Alice") {
println("Found!")
}
}
println("Alice is not found")
}
3. [ ] 只有在以 lambda 作为参数的函数是内联函数的时候才能从更外层的函数返
回。在一个非内联函数的 lambda 中使用 return 表达式是不允许的。一个非内
联函数可以把传给它的 lambda 保存在变量中,以便在函数返回以后可以继续使
用,这个时候 lambda 想要去影响函数的返回以及太晚了。
4. [ ] 从 lambda 返回:使用标签返回
也可以在 lambda 表达式中使用局部返回。lambda 中的局部返回跟 for 循环中
的 break 表达式相似。它会终止 lambda 的执行,并接着从调用 lambda 的代码
执行。要区分局部返回和非局部返回,要用到标签。
1. [ ] 用一个标签实现局部返回
fun lookForAlick(people: List<Person>) {
people.forEach label@{
if (it.name == "Alice") return@label
}
println("Alice might be somewhere");
}
>>> lookForAlice(people)
Alice might be somewhere
要标记一个 lambda 表达式,在 lambda 的花括号之前放一个标签名(可以是
任何标识符),接着放一个@符号。要从一个 lambda 返回,在 return 关键
字后放一个 @ 符号 ,接着放标签名
2. [ ] 用函数名作为 return 标签
fun lookForAlice(people: List<People>) {
people.forEach {
if (it.name == "Alice") return@forEach
}
println("Alice might be somewhere")
}
如果你显式的指定了 lambda 表达式的标签,再使用函数名作为标签没有任何
效果。一个 lambda 表达式的标签数量不能多于一个
带标签的 "this" 表达式
>>> println(StringBuilder().apply sb@{
listOf(1, 2, 3).apply {
this@sb.append(this.toString())
}
})
局部返回的语法相当冗长,如果一个 lambda 包含多个返回语句会变得更加笨
重。解决方案是,可以使用另一种可选的语法来传递代码块:匿名函数
2. [ ] 匿名函数:默认使用局部返回
1. [ ] 匿名函数是一种不同的用于编写传递给函数的代码块的方式。
fun lookForAlice(people: List<Person>) {
people.forEach(fun (person) {
if (person.name == "Alice") {
return
}
println("${person.name} is not Alice");
})
}
>>> lookForAlice(people)
Bob is not Alice
匿名函数看起来跟普通函数很相似,除了它的名字和参数类型被省略了外,这里
面有另外一个例子
2. [ ] 在 filter 中使用匿名函数
people.filter(fun (person): Boolean {
return person.age < 30
|)
匿名函数和普通函数有相同的指定返回值类型的规则。代码块体匿名函数需要显
式地指定返回类型,如果使用表达式函数体,就可以省略返回类型
3. [ ] 使用表达式体匿名函数
people.filter(fun (person) = person.age < 30)
在匿名函数中,不带标签的 return 表达式会从匿名函数返回,而不是从包含匿
名函数的函数返回。这条规则很简单: return 从最近的使用 fun 关键字声明的
函数返回。lambda 表达式没有使用 fun 关键字,所以 lambda 中的 return 从
最外层的函数返回。匿名函数使用了 fun,因此,在前一个例子中匿名函数是最近
的符合规则的函数。所以,return 表达式从匿名函数返回,而不是从最外层的函
数返回。
return 表达式从使用 fun 关键字声明的函数返回
尽管匿名函数看起来跟普通函数很相似,但它其实是 lambda 表达式的另一种语
法形式而已。关于 lambda 表达式如何实现,以及在内联函数中如何被内联的讨
论同样适用于匿名函数
3. [ ]
4. [ ]
*** 4. 非局部返回和标签
*** 5. 匿名函数
*** 6. 小结
1. [ ] 函数类型可以让你声明一个持有函数引用的变量,参数或者函数返回值
2. [ ] 高阶函数以其他函数作为参数或者返回值。可以用函数类型作为函数参数或者返回
值的类型来创建这样的函数
3. [ ] 内联函数被编译以后,它的字节码连同传递给它的 lambdaa 的字节码会被插入到调
用到函数的代码中,这使得函数调用相比于直接编写相同的代码,不会产生额外的运行
时开销
4. [ ] 高阶函数促进了一个组件的不同部分的代码重用,也可以让你的构建功能强大的通
用库
5. [ ] 内联函数可以让你使用非局部返回 --- 在 lambda 中从包含函数返回的返回表达式
6. [ ] 匿名函数给 lambda 表达式提供了另一种可选的语法,用不同的规则来解析 return
表达式。可以在需要编写有多个退出点的代码块的时候使用它们。
** 9. 泛型
*** 1. 声明泛型函数和类
1. [ ] 泛型类型参数
1. [ ] val readers: MutableList<String> = mutableListOf()
val readers = mutableListOf<String>()
两种声明是等价的
和 Java 不同,Kotlin 始终要求类型实参要么被显示地说明,要么能被编译器
推导出来。
2. [ ] 泛型函数和属性
1. [ ] fun <T> List<T>.slice(indices: IntRange): List<T>
类型形参说明 接收者和返回类型使用了类型形参
slice 泛型函数的类型形参为 T
接收者和返回类型用到了函数的类型形参T,它们的类型都是 List<T>。当你
在一个具体的列表上调用这个函数时,可以显式地指定类型实参。
2. [ ] 调用泛型函数
>>>val letters = ('a'..'z').toList()
>>>println(letters.slice<Char>(0..2))
{a, b, c}
>>> println(letters.slice(10..13))
{k, l, m, n}
3. [ ] 调用泛型的高阶函数
val auhtors = listOf("Dimtry", "Svetlana")
val readers = mutableListOf<String>(/* ... */)
fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>
>>> readers.filter{it !in authors}
这个例子中自动生成的 lambda 参数 it 的类型是 String, 编译器必须把它推导
出来:毕竟,在函数声明中 lambda 参数是泛型 T (即 (T) -> Boolean 函数
的参数类型 T)。编译器推断 T 就是 String, 因为它知道函数应该在 List<T>
上调用,而它的接收者 readers 的真实类型时 List<String>
可以给类或接口的方法、顶层函数,以及扩展函数声明类型参数。
4. [ ] 泛型的扩展属性
val <T> List<T>.penultimate: T//这个泛型扩展属性能在任何种类元素的列表
上调用
get() = this(size - 2)
不能声明泛型非扩展属性
普通(即非扩展)属性不能拥有类型参数,不能在一个类的属性中存储多个不同
的类型的值,因此声明泛型 非扩展函数没有任何意义。你可以尝试一下
编译器会报告错误:
>>> val <T> x: T = TODO()
ERROR: type parameter of a property must be used in its receiver type
3. [ ] 声明泛型类
1. [ ]
interface List<T> {//List 接口定义了类型参数 T
operator fun get(index: Int): T //在接口或类的内部,T可以当做普通类型使
用
}
class StringList: List<String> {
override fun get(index: Int): String = ...
}
class ArrayList<T> : List<T> {
override fun get(index: Int): T = ...
}
interface Comparable<T> {
fun compareTo(other: T): Int
}
class String: Comparable<String> {
override fun compareTo(other: String): Int = /* .. */
}
4. [ ] 类型参数约束
1. [ ] 类型参数约束可以限制为(泛型)类和(泛型)函数的类型实参的类型。以
计算列表元素之和的函数为例。它可以用在 List<Int> 和 List<Double> 上,但
不可以用在 List<String> 这样的列表上。可以定义一个类型参数约束,说明
sum 的类型形参是数字,来表达这个限制。
< T extends Number> T sum(List<T> list)
/** Number 就是上界 */
fun <T : Number> List<T>.sum(): T
通过在类型参数后指定上界来定义约束
>>> println(listOf(1, 2, 3)).sum()
6
一旦制定了类型形参 T 的上界,你就可以把类型 T 的值当作它的上界(类型)
的值使用。例如,可以调用定义在这个上界类中的方法
fun <T : Number> oneHalf(value : T) :Double {
return value.toDouble() /2.0
}
>>> println(oneHalf(3))
1.5
2. [ ] 声明带类型参数约束的函数体
fun <T: Comparable<T>> max(first: T, second: T): T {
return if(first > second) first else second
}
>>> printl(max("kotlin", 42))
kotlin
T 的上界是泛型类型 Comparable<T>。前面已经看到了, String 类继承了
Comparable<String>, 这样使得 String 变成了 max 函数的有效类型。
3. [ ] 为一个类型参数指定多个约束
fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T :
Appendable {
if (!seq.endsWith('.')) {
seq.append('.')
}
}
>>> val helloworld = StringBuilder("Hello World")
>>> ensureTrailingPeriod(helloworld)
>>> println(helloworld)
Hello, World.
这种情况下,可以说明作为类型实参的类型必须实现 CharSequence 和
Appendable 两个接口。这意味着该类型的值 可以使用访问数据(endsWith) 和
修改数据 (append) 两种操作
4. [ ] 让类型形参非空
如果你声明的是泛类型或者泛型函数,任何类型实参,包括那些可空的类型实参,
都可以替换它的类型形参。事实上,没有指定上界的类型形参将会使用 Any? 这
个默认的上界。
class Processor<T> {
fun process(value: T) {
value?.hashCode()//value 是可空的,所以要用安全调用
}
}
process 函数中,参数 value 是可空的,尽管 T 并没有使用问号标记。
val nullableStringProcessor = Processor<String?>()
nullablestringprocessor.processs(null)
clsss Processor<T : Any> {
fun process(value: T) {
value.hashcode()//类型 T 的值现在是非空的
}
}
约束 <T : Any> 确保了类型 T 永远都是非空类型。编译器不会接收代码
Processor<String?>, 因为类型实参 String? 不是 Any 的子类型(它是 Any?
的子类,一种更普通的类型)
*** 2. 类型擦除和实例化类型参数
1. [ ] 运行时的泛型:擦除和实化类型参数(263)
1. [ ] 运行时的泛型:类型检查和转换
对泛型类型做类型转换
fun printSum(c: Collection<*>) {
val intList = c as? List<Int> ?: throw IllegalArgumentException("List is
expected")
println(intList.sum())
}
>>> println(listOf(1, 2, 3))
6
对已知类做类型转换
fun printSum(c: Collection<Int>) {
if (c is List<Int>) {
println(c.sum())
}
}
2. [ ] 声明带实化类型参数的函数
1. [ ] fun <T> isA(value: Any) = value is T
Error: Cannot checkfor instance of erased type: T
只有一种例外可以避免这种限制:内联函数。内联函数的类型形参能够被实化,意味
着你可以在运行时引用实际的类型实参
2. [ ] inline fun <reified T> isA(value: Any) = value is T
>>> println(isA<String>("abc"))
true
>>> println(isA<String>(123));
false
3. [ ] 使用标准库函数 filterIsInstance
>>> val items = listOf("one", 2, "three")
>>> println(items.filterIsInstance<String>())
[one, three]
通过指定 <String> 作为函数的类型实参,你表明感兴趣的只是字符串,因此函数的
返回类型是 List<String>。这种情况下,类型 实参在运行时是已知的,函数
filterIsInstance 使用它来检查列表中的值是不是指定为该类型实参的类的实例
filterIsInstance 的简化实现
inline fun <refied T> Iterable<*>.filterIsInstance(): List<T> { // reified
声明了类型参数不会在运行时被擦除
val destination = mutableListOf<T>()
for (element in this) {
if (element is T) {//可以检查元素是不是指定为类型实参的类的实例
destination.add(element)
}
}
return destination
}
为什么实化只对内联函数有效
这是什么原理?为什么在 inline 函数中允许这样写 element is T,而普通的类或函
数却不行?
编译器把实现内联函数的字节码插入每一次调用发生的地方。每次你调用带实化类型
参数的函数时,编译器都知道这次特定调用中用作类型实参的确切类型。因此,编译
器可以生成引用作为类型实参的具体类的字节码。
上述代码等价于
for (element in this) {
if (element is String) {
destination.add(element)
}
}
因为生成的字节码引用了具体类,而不是类型参数,它不会被运行时发生的类型参数
擦除影响。注意,带 reified 类型参数的 inline 函数不能在 Java 代码中调用。
普通的内联函数可以像常规函数那样在 Java 中调用---它们可以被调用而不能被内
联。带实化类型参数的函数需要额外的处理,来把类型实参的值替换到字节码中,所
以它们必须永远是内联的。这样它们不可能用 Java 那样的普通的方式调用
一个内联函数可以有多个实化类型参数,也可以同时拥有非实化类型参数和实化类型
参数。注意,filterIsInstance 函数虽然能被标记成 inline ,而它并不期望
lambda 作为实参。在这个例子中,并不是因为性能的原因才把函数标记成 inline,
这里这样做是为了能够使用实化类型参数。
为了保证良好的性能,你仍然需要跟踪了解标记为 inline 的函数大小。如果函数
变得庞大,最好把不依赖实化类型参数的代码抽取到单独的非内联函数中
3. [ ] 使用实化类型参数代替类引用
1. [ ] 通过下面的调用来使用标准的 ServiceLoader.load(Service::class.java)
::class.java 的语法展现了如何获取 java.lang.Class 对应的 Kotlin 类。这和
Java 中的 Service.class 是完全等同的。我们在 10.2 节中讨论反射的时候会更深
入地涉及这个话题
val serviceImpl = loadService<Service>()
inline fun <reified T> loadService() {//类型参数标记成了 "reified"
return ServiceLoader.load(T::class.java)//把 T:class 当成类型形参的类访问
}
这种用在普通类上的 ::class.java 语法也可以同样在实化类型参数上使用这种语法
会产生对应到指定为类型参数的类的 java.lang.Class
简化 android 上的 startActivity 函数
inline fun <reified T: Activity> Context.startActivity() {//类型参数标记成
了 reified
val intent = Intent(this, T::class.java)//把 T::class 当成类型参数的类访问
startActivity(intent)
}
startActivity<DetailActivity>() 调用方法显示 Activity
4. [ ] 实化类型参数的限制
1. [ ] 实化类型参数的方式
1. [ ] 用在类型检查和类型转换中(is, !is, as, as?)
2. [ ] 使用 Kotlin 反射 API,我们将在第 10 章讨论(::class)
3. [ ] 获取相应的 java.lang.Class(::class.java)
4. [ ] 作为调用其他函数的类型参数
2. [ ] 不能做下面这些事情
1. [ ] 创建指定为类型参数的类的实例
2. [ ] 调用参数类型的伴生对象的方法
3. [ ] 调用带实化类型参数函数的时候使用非实化类型形参作为类型实参
4. [ ] 把类 ,属性或者非内联函数的类型形参记成 reified
*** 3. 声明点变型和使用点变型
1. [ ] 变型:泛型和子类型化
1. [ ] 变型的概念描述了拥有相同基础类型和不同类型实参的(泛型)类型之间是如何关联的:例如,
List<String> 和 List<Any> 之间如何关联。
2. 为什么存在变型:给函数传递实参
2. [ ] 类,类型和子类型
1. [ ] 变量的类型规定了该变量的可能值。有时候我们会把类型和类当成同样的概念使用,但它们不一样,现
在是时候看看它们的区别了。
最简单的例子就是非泛型类,类的名称可以直接当做类型使用。例如,如果你这样写 var x: String,
就是声明了一个可以保存 String 类的实例的变量。但是注意,同样的类名称也可以用来声明可空类
型: var x: String?.这意味着每一个 Kotlin 类都可以用于构造至少两种类型
泛类型的情况就变得更复杂了。要得到一个合法的类型,需要用一个作为类型实参的具体类型替换
(泛型)类的类型形参。List 不是一个类型(它是一个类),但是下面列举出来的所有替代品都是合
法的类型:List<Int>, List<String?>, List<List<String>> 等。每一个泛型类都可能生成潜在的无
限数量的类型。
2. [ ] 术语超类型是子类型的反义词。如果 A 是 B 的子类型,那么 B 就是 A 的超类型
检查一个类型是否是另一个的子类型
fun test(i: Int) {
val n: Number + i // 编译通过,因为 Int 是 Number 的 子类型
fun f(s: String) {/* ... */ }
f(i) //不能编译,因为 Int 不是 String 的子类型
}
只有值的类型时变量类型的子类型时,才允许变量存储该值。只有当表达式的类型时函数参数的类型
的子类型时,才允许把该表达式传给你函数。
子类型和子类本质上意味着一样的事物。
可空类型提供了一个例子,说明子类型和子类不是同一个事物
非空类型 A 是可空的 A? 的子类型,但是反过来却不是
3. [ ] 协变:保留子类型关系
1. [ ] 一个协变类是一个泛型类,如果 A 是 B 的子类型,那么 Producer<A> 就是 Producer<B> 的子
类型。我们说子类型化被保留了。
在 Kotlin 中,要声明类在某个类型参数上是可以协变的,在该类型参数的名称前加上 out 关键字即
可
interface Producer<out T> {//类被声明成在 T 上协变
fun produce(): T
}
将一个类的类型参数标记为协变的,在该类型实参没有精确匹配到函数中定义的类型形参时,可以让
该类的值作为这些函数的实参传递,也可以作为这些函数的返回值。
你不能把任何类都变成协变的:这样不安全。让类在某个类型参数变为协变,限制了该类中对该类型
参数使用的可能性。要保证类型安全,它只能用在所谓的 out 位置,意味着这个类只能生产类型 T
的值而不能消费它们。
函数参数的类型叫作 in 位置,而函数返回类型叫作 out 位置
类的类型参数前的 out 关键字要求所有使用 T 的方法只能把 T 放在 out 位置而不能放在 in 位置,
这个关键字约束了使用 T 的可能性,这保证了对应子类型关系的安全性
class Herd<out T : Animal> {
val size: Int get() = ...
operator fun get(i: Int): T {...}//把 T 作为返回类型使用
}
这是一个 out 位置,可以安全地把类声明成协变的。如果 Herd<Animal> 类的 get 方法返回的是
Cat, 任何调用该方法的代码都可以正常工作,因为 Cat 是 Animal 的子类型
重申一下,类型参数 T 上的关键字 out 有两层含义
1. 子类型化会保留(Producer<Cat> 是 Producer<Animal> 的子类型)
2. T 只能在 out 的位置
现在我们看看 List<Interface> 接口。Kotlin 的 List 是只读的,所以它只有一个返回类型为 T 的
元素的方法 get,而没有定义任何把类型为 T 的元素存储到列表中的方法。因此,它也是协变的。
interface List<out T> : Collection<T> {
oeprator fun get(index: Int) : T//只读接口只定义了返回 T 的方法(所以 在 "out" 位置
}
注意,类型形参不光可以直接当作参数类型或返回类型使用,还可以当作另一个类型的类型实参。例如,
List 接口就包含了一个返回 List<T> 的subList 方法。
interface List<out T> : Collection<T> {
fun subList<fromIndex: Int. toIndex: Int): List<T>//这里 T 也是 out 位置
}
注意,不能把 MutableList<T> 在它的类型参数上声明成协变的,因为它既含有接收类型为 T 的值作
为参数的方法,也含有返回这种值的方法(因此,T 出现在 in 和 out 两种位置上)
interface MutableList<T> : List<T>, MutableCollection<T> {//MutableList 不能在 T 上声明
协变的
override fun add(element: T): Boolean //因为 T 用在了 "in" 的位置
}
编译器强制实施了这种限制。如果这个类被声明成协变的,编译器会报告错误:Type Parameter T is
declared as 'out' but occurs in 'in' position (类型参数 T 声明为 "out" 但出现在 "in" 位
置)
注意,构造方法的参数既不在 in 位置,也不在 out 位置。即使类型参数声明成了 out,仍然可以在
构造方法参数的声明中使用它
class Herd<out T: Animal>(vararg animals: T) {...}
然而,如果你在构造方法的参数上是用来关键字 val 和 var,同时就会声明一个 getter 和一个
settter(如果属性时可变的)。因此,对只读属性来说,类型参数用在了 out 位置,而可变属性在
out 位置和 in 位置都使用了它
class Herd<T: Animal(var leadAnimal:T, vararg animals:T) {...}
上面这个例子中, T 不能用 out 标记,因为类包含属性 leadAnimal 的 setter,它在 in 位置用到
了 T。
还需要注意的,位置规则值覆盖了类外部的可见的(public, protected 和 internal)API。私有方
法的参数既不在 in 位置也不在 out 位置。变型规则只会防止外部使用者的误用但不会对类自己的实
现起作用。
class Herd<out T:Animal>(private var leadAnimal:T, vararg animals:T) {...}
现在可以安全地让 Herd 在 T 上协变,因为属性 leadAnimal 变成了私有的。
4. [ ] 逆变:反转子类型化关系
1. [ ] 逆变的概念可以被看成是协变的镜像:对一个逆变类来说,它的子类型化关系与用作类型实参的
类的子类型化关系是相反的。
interface Comparator< in T> {
fun compare(e1: T, e2: T): Int {...}//在 "in" 位置使用 T
}
>>> val anyComparator = Comparator<Any> {
e1, e2 -> e1.hashCode() - e2.hashCode()
}
>>> val strings: List<String> = ...
>>> strings.sortedWith(anyComparator)//可以使用任意对象的比较器比较具体对象,比如字符串
sortedWith 函数期望一个 Comparator<String>(一个可以比较字符串的比较强),传给它一个能比较更
一般的类型的比较器是安全的。如果你要在特定类型的对象上执行比较,可以使用能处理该类型或者
他的超类型的比较器。这说明 Comparator<Any> 是 Comparator<String> 的子类型,其中 Any 是
String 的超类型,不同类型之间的子类型关系和这些类型的比较器之间的子类型化关系截然相反
一个在类型参数逆变的类是这样的一个泛型类(我们以Consumer<T> 为例),对这种类来说,下面的
描述是成立的:如果 B 是 A 的子类型,那么 Consumer<A> 就是 Consumer<B> 的子类型,类型参数A
和 类型参数B 交换了位置,所以我们说子类型被反转了。例如,Consumer<Animal> 就是
Consumer<Cat> 的子类型
Animal <- Cat
Producer<Animal> <----(协变) Producer<Cat>
Consumer<Animal> ---->(逆变) Consumer<Cat>
对协变类型 Producer<T>来说,子类型化保留了,但对逆变类型 Consumer<T>来说,子类型化反转了
协变的,逆变的和不变型的类
协变 逆变 不变型
Producer<out T> Consumer<in T> MutableList<T>
类的子类型化保留了:Producer<Cat> 子类型化反转了: Consumer<Animal> 没有子类型化
是 Producer<Animal> 的子类型 是 Consumer<Cat> 的子类型 T 可以在任何类型
T 只能在 out 位置 T 只能在 in 位置 T 可以在任何位置
一个类可以在一个类型参数上协变,同时在另外一个类型参数上逆变。Function 接口就是一个经典的
例子 。下面是一个单个参数的 Function 的声明:
interface Function1<in P, out R> {
operator fun invoke(p: P):R
}
Kotlin 的表示法(P)-> R 是表达 Function<P, R> 的另一种更具可读性的形式。可以发现用 in 关
键字标记的 P (参数类型) 只用在 in 位置,而用 out 关键字标记的 R(返回类型)只用在 out 位
置。这意味着对这个函数类型的第一个类型参数来说,子类型反转了,而对于第二个类型参数来说,
子类型化保留了。
fun enumerateCats(f: (Cat) -> Number) {...}
fun Animal.getIndex(): Int = ...
>>> enumerateCats(Animal::getIndex) 在 Kotlin 中这段代码是合法的。Animal 是 Cat 的超类型,
而 Int 是 Number 的子类型
函数 (P)-> R 在它的参数类型上逆变而在返回类型上协变
5. [ ] 使用点变型:在类型出现的地方指定变型
1. [ ] java 中的点变型
public interface Stream<T> {
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
}
2. [ ] 带不变型类型参数的数据拷贝函数
fun <T> copyData(source: MutableList<T>, destination: MutableList<T>) {
for (item in source){
destination.add(item)
}
}
3. [ ] 带不变类型参数的数据拷贝函数
fun <T: R, R> copyData(source: MutableList<T>, destination: MutableList<R>) {
for (item in source){
destination.add(item)
}
}
>>> val ints = mutableListOf(1, 2, 3)
>>> val anyItems = mutableListOf<Any>()
>>> copyData(ints. anyItems)
>>> println(anyItems);
[1, 2, 3]
4. [ ] 带 out 投影类型参数的数据拷贝函数
fun <T> copyData(source: MutableList<out T>, destination: MutableList<T>) {//可以给类型的
用法加上 "out" 关键字:没有使用那些用在 "in" 位置的方法
for (item in source) {
destination.add(item)
}
}
5. [ ] >>> val list:MutableList<out Number> = ...
>>> list.add(42)
Error: Out-projected type 'MutableList<out Number> prohibits' the use of 'fun
add(element: E): Boolean'
6. [ ] 带 in 投影类型参数的数据拷贝函数
fun <T> copyData(source: MutableList<T>, destination: MutableList<in T>) {
for (item int source) {
destination.add(item)
}
}
Kotlin 的使用点变型直接对应 Java 的限界通配符。Kotlin 中的 MutableList<out> 和 Java 中的
MutableList<? extends T> 是一个意思。 in 投影的 MutableList<in T> 对应到Java 的
MutableList<? super T> 。
7. [ ]
6. [ ] 星号投影:使用 * 代替类型参数(282)
7. [ ]
8. [ ]
9. [ ]
*** 4. 小结
1. [ ] Kotlin 的泛型和 Java 相当接近:它们使用同样的方式声明泛型函数和泛型类。
2. [ ] 和 Java 一样,泛型类型的类型实参只能在编译期存在
3. [ ] 不能把带类型实参的类型和 is 运算符一起使用,因为类型实参在运行时将被擦除。
4. [ ] 内联函数的类型参数可以标记成实化的,允许你在运行时对它们使用 is 检查,以
及获得 java.lang.Class 实例
5. [ ] 变型是一种说明两种拥有相同基础类型和不同类型参数的泛型类型之间子类型化关
系的方式,它说明了如果其中一个泛型类型的类型参数是另一个的类型参数的子类型,
这个泛型类型就是另外一个泛型类型的子类型或者超类型。
6. [ ] 可以声明一个类在某个类型参数上是协变的,如果该参数只是用在 out 位置。
7. [ ] 逆变的情况正好相反:可以声明一个类在某个类型参数上是逆变的,如果该参数只
是用在 in 位置
8. [ ] Kotlin 中的只读接口 List 声明成了协变的,这意味着 List<String> 是 List
<Any> 的子类型。
9. [ ] 函数接口声明成了在第一个类型参数上逆变而在第二个类型参数上协变,是
(Animal) -> Int 成为 (Cat) -> Number 的子类型
10. [ ] 在 Kotlin 中既可以为整个泛型类指定变型(声明点变型),也可以为泛型类型特
定的使用指定变型(使用点变型)
11. [ ] 当确切的类型实参是未知的或者不重要的时候,可以使用星号投影语法
** 10. 注解与反射
*** 1. 应用和定义注解
1. [ ]
2. [ ]
3. [ ]
4. [ ]
5. [ ]
*** 2. 在运行时使用反射对类进行自省
1. [ ]
2. [ ]
1. [ ]
2. [ ]
3. [ ] 用注解定制序列化
4. [ ] JSON 解析和对象反序列化
3. [ ]
4. [ ]
*** 3. 一个真正的 Kotlin 项目实例
*** 4. 小结
1. [ ] Kotlin 中应用注解的语法和 Java 几乎一模一样
2. [ ] 在 Kotlin 中可以让你应用注解的目标的范围比 Java 更广,其中包括了文件和表
达式。
3. [ ] 一个注解的参数可以是一个 基本数据类型,一个字符串,一个枚举,一个类引用,
一个其他注解类的实例 ,或者前面这些元素组成的数组
4. [ ] 如果单个 Kotlin 声明产生了多个字节码元素,像@get:Rule 这样指定一个注解的
使用点目标,允许你选择注解如何应用
5. [ ] 注解类的声明是这样的,它是一个拥有主构造方法且没有类主体的类,其构造方法
中所有的参数都被标记成 val 属性
6. [ ] 元注解可以用来指定(使用点)目标,保留期模式和其他注解的特性。
7. [ ] 反射 API 然你在运行时动态地列举和访问一个对象的方法和属性。它拥有许多接口
来表示不同种类的声明。例如类(KClass),函数(KFunction)等。
8. [ ] 要获取一个 KClass 的实例,如果类是静态已知的,可以使用 ClassName::class;
否则,使用 obj.javaClass.kotlin 从对象实例中取得类
9. [ ] KFunction 接口和 KProperty 接口都继承了 KCallable,它提供了一个通用的 call
方法
10. [ ] KCallable.callBy 方法能用来调用带默认参数值的方法
11. [ ] KFunction0, KFunction1 等不同参数数量的函数可以使用 invoke 方法调用
12. [ ] KProperty0 和 KProperty1 是接收者数量不同的属性,支持用 get 方法取回值。
KMutableProperty0 和 KMutableProperty1 继承了这些接口,支持通过 set 方法来改
变属性的值。
** 11. DSL 构建
*** 1. 构建领域特定语言
1. [ ]
1. [ ]
2. [ ]
3. [ ]
4. [ ] 使用内部 DSL 构建 HTML
5. [ ]
2. [ ] 构建结构h化的API: DSL 中带接收者的 lambda
1. [ ] 带接收者的 lambda 是 Kotlin 的一个强大特性,它可以让你使用一个结构来构
建 API。就像我们已经讨论过的,拥有结构是你区分 DSL和 普通 API 的关键特征。
让我们来仔细研究一下这个特性,看看一些用到它的 DSL
2. [ ] 带接收者的 lambda 和扩展函数类型
接收者类型 参数类型 返回类型
String.(Int, Int) -> Unit
3. [ ] 在 HTML 构建器中使用带接收者的 lambda (329)
3. [ ]
4. [ ]
5. [ ]
*** 2. 使用带接收者的 lambda
1. [ ]
2. [ ]
3. [ ]
*** 3. 应用 invoke 约定
1. [ ]
2. [ ]
3. [ ]
4. [ ]
*** 4. 已有的 Kotlin DSL 示例
1. [ ]
2. [ ]
3. [ ]
4. [ ]