1.基础语法
1.函数
函数传参
返回值:Unit代表无返回,可以忽略
fun sum(a: Int, b: Int): Int {
return a + b
}
2.变量
定义只读局部变量使用关键字 val 定义。只能为其赋值一次
可重新赋值的变量使用 var 关键字:
顶层变量:val PI = 3.14
val 类似常量,引用不可变
var 变量
3.字符串模板
var a = 1
// 模板中的简单名称:
val s1 = "a is $a"
a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"
4.空值与 null 检测
当某个变量的值可以为 null 的时候,必须在声明处的类型后添加 ? 来标识该引用可为空
var str:String? = null
5.类型检测与自动类型转换
is 运算符检测一个表达式是否某类型的一个实例
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}
6.for while when
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${
items[index]}")
index++
}
//类似于switch
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
7.使用区间(range) in
if (x in 1..y+1) {
println("fits in range")
}
2.基本类型
1.数字
Byte
Short
Int
Long
如果初始值超过了其最大值,那么推断为 Long 类型
如需显式指定 Long 型值,请在该值后追加 L 后缀
Float 32
Double 64
2.字面常量
十进制: 123
Long 类型用大写 L 标记: 123L
十六进制: 0x0F
二进制: 0b00001011
3.位运算
shl(bits) – 有符号左移
shr(bits) – 有符号右移
ushr(bits) – 无符号右移
and(bits) – 位与
or(bits) – 位或
xor(bits) – 位异或 ???
inv() – 位非 ?
4.字符
字符用 Char 类型表示
5.数组
使用 Array 类来表示,它定义了 get 与 set 函数(按照运算符重载约定这会转变为 [])以及
size 属性,
数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 Array 赋值给 Array
val array = arrayOf(1, 2, 3)
val array = arrayOfNulls()
6.原生类型数组
Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArray、 ShortArray、IntArray 等等。这些类与 Array 并没有继承关系
// 大小为 5、值为 [0, 0, 0, 0, 0] 的整型数组
val arr = IntArray(5)
7.字符串
字符串用 String 类型表示。字符串是不可变的。 字符串的元素——字符可以使用索引运算符访问: s[i]
for (c in str) {
println(c)
}
3.类与继承
Kotlin 中使用关键字 class 声明类
class Person constructor(firstName: String) {
/*……*/ }
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(firstName: String) {
/*……*/ }
class Person public @Inject constructor(name:String){
...}
1.次构造函数
类也可以声明前缀有 constructor的次构造函数:
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过
别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf()
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
初始化块中的代码实际上会成为主构造函数的一部分
class Constructors {
//优先执行,再执行次构造函数
init {
println("Init block")
}
//再执行
constructor(i: Int) {
println("Constructor")
}
}
在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数
2. init 初始化代码
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的**初始化块(initializer blocks)**中。可以有多个init模块代码,多个init模块会按照先后顺序执行。
class User(name:String){
init{
println("First initializer block that prints ${name}")
}
init{
println("second initializer block that prints ${name}")
}
}
3.声明属性和初始化属性 var val
类似于不仅为主函数构造,且给当前类添加了属性。可以用var val 修饰
class Person(val firstName: String, val lastName: String, var age: Int) { /*……*/ }
4 继承
在 Kotlin 中所有类都有一个共同的超类 Any
Any 有三个方法:equals()、 hashCode() 与 toString()
默认情况下,Kotlin 类是最终(final)的:它们不能被继承。 要使一个类可继承,请用 open 关键字标记它
open class Base // 该类开放继承
open class Base(p: Int)
class Derived(p: Int) : Base(p)
1.覆盖方法 覆盖属性
Circle.draw() 函数上必须加上 override 修饰符
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。 这是允许的,因为一个 val
属性本质上声明了一个 get
方法, 而将其覆盖为 var
只是在子类中额外声明一个 set
方法。
open class Shape {
open val vertexCount: Int = 0
open fun draw() {
/*……*/ }
fun fill() {
/*……*/ }
}
class Circle() : Shape() {
override val vertexCount = 4
override fun draw() {
/*……*/ }
}
你可以在主构造函数中使用 override 关键字作为属性声明的一部分。
interface Shape {
val vertexCount: Int
}
//声明了属性,且覆盖了之前的属性
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
2.覆盖规则
如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)
open class Rectangle {
open fun draw() {
/* …… */ }
}
interface Polygon {
fun draw() {
/* …… */ } // 接口成员默认就是“open”的
}
class Square() : Rectangle(), Polygon {
// 编译器要求覆盖 draw():
override fun draw() {
super<Rectangle>.draw() // 调用 Rectangle.draw()
super<Polygon>.draw() // 调用 Polygon.draw()
}
}
3.派生类初始化执行顺序
在构造派生类的新实例的过程中,先完成其基类的初始化 ,因此发生在派生类的初始化逻辑运行之前
open class Base(val name: String) {
init {
println("Initializing Base") }
open val size: Int =
name.length.also {
println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String,
) : Base(name.capitalize().also {
println("Argument for Base: $it") }) {
init {
println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also {
println("Initializing size in Derived: $it") }
}
fun main() {
println("Constructing Derived(\"hello\", \"world\")")
val d = Derived("hello", "world")
}
4.super 调用其超类的函数与属性访问器
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer
:
5.抽象类
类以及其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open 标注一个抽象类或者函数。我们可以用一个抽象成员覆盖一个非抽象的开放成员
open class Polygon {
open fun draw() {
}
}
abstract class Rectangle : Polygon() {
abstract override fun draw()
}
6.属性
Kotlin 类中的属性既可以用关键字 var 声明为可变的,也可以用关键字 val 声明为只读的。
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
val result = Address() // Kotlin 中没有“new”关键字
result.name = address.name // 将调用访问器
result.street = address.street
1.Getters 与 Setters
属性声明涉及三个点:初始器(initializer)、getter 和 setter 。属性默认情况下都有set和get方法。
var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter
val simple: Int? // 类型 Int、默认 getter、必须在构造函数中初始化
val inferredType = 1 // 类型 Int 、默认 getter
val 因为不可变,所以没有set方法,必须在构造函数初始化
var 必须显式初始化
2. 自定义get方法set方法
var stringRepresentation: String
get() = this.toString() //get方法
set(value) {
//set方法
setDataFromString(value) // 解析字符串并赋值给其他属性
}
3.延迟初始化属性与变量 lateinit
属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便.
该修饰符只能用于在类体中的属性
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
检测一个 lateinit var 是否已初始化
if (foo::bar.isInitialized) {
println(foo.bar)
}
4 幕后字段和幕后属性
https://blog.csdn.net/chentaishan/article/details/120908285?spm=1001.2014.3001.5501
5 编译期常量 const
如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量
6. 委托属性???
4.接口
Kotlin 的接口可以既包含抽象方法的声明也包含实现。接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
//实现接口
class Child : MyInterface {
override fun bar() {
// 方法体
}
}
在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。
1.解决覆盖冲突
interface A {
fun foo() {
print("A") }
fun bar()
}
interface B {
fun foo() {
print("B") }
fun bar() {
print("bar") }
}
class C : A {
override fun bar() {
print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
//接口 A 和 B 都定义了方法 foo() 和 bar()。 两者都实现了 foo(), 但是只有 B 实现了 bar() (bar() 在 A 中没有标记为抽象, 因为在接口中没有方法体时默认为抽象)。因为 C 是一个实现了 A 的具体类,所以必须要重写 bar() 并实现这个抽象方法。
//然而,如果我们从 A 和 B 派生 D,我们需要实现我们从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。
2 函数式(SAM)接口
只有一个抽象方法的接口称为函数式接口或 SAM(单一抽象方法)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
//可以用 fun 修饰符在 Kotlin 中声明一个函数式接口。
fun interface KRunnable {
fun invoke()
}
使用lambda表达式
// 创建一个类的实例
val isEven = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
val isEven = IntPredicate{ i%2==0 }
3.可见性修饰符
private、它只会在声明它的文件内可见;
protected、 不适用于顶层声明。子类可见
internal 它会在相同模块内随处可见
public 随处可见,默认可见
4.扩展函数
Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式.
扩展函数
//扩展声明
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”的值
也可以通过泛型兼容不同类型
扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。
扩展属性
val <T> List<T>.lastIndex: Int
get() = size - 1
1.伴生对象的扩展???
5.数据类
只保存数据的类data
data class User(val name: String, val age: Int)
equals()
/hashCode()
对;toString()
格式是"User(name=John, age=42)"
;componentN()
函数 按声明顺序对应于所有属性;copy()
函数- 主构造函数需要至少有一个参数;
- 主构造函数的所有参数需要标记为
val
或var
; - 数据类不能是抽象、开放、密封或者内部的;
- (在1.1之前)数据类只能实现接口。
1. copy 浅拷贝
在很多情况下,我们需要复制一个对象改变它的一些属性,但其余部分保持不变.
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
2. 数据类与解构声明
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 输出 "Jane, 35 years of age
6.密封类????
密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。
在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
7.泛型
class Box<T>(t: T) {
var value = t
}
型变
声明处型变(declaration-site variance)与类型投影(type projections)
1.声明处型变out in
在 Kotlin 中,有一种方法向编译器解释这种情况。这称为声明处型变:我们可以标注 Source
的类型参数 T
来确保它仅从 Source<T>
成员中返回(生产),并从不被消费。
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
//简而言之,他们说类 C 是在参数 T 上是协变的,或者说 T 是一个协变的类型参数。 你可以认为 C 是 T 的生产者,而不是 T 的消费者
out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。 这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。
除了 out,Kotlin 又补充了一个型变注释:in。它使得一个类型参数逆变:只可以被消费而不可以被生产。逆变类型的一个很好的例子是 Comparable
:
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
val y: Comparable<Double> = x // OK!
}
2.类型投影
fun copy(from: Array<out Any>, to: Array<Any>) { …… }
这里out 称为类型投影:我们说from
不仅仅是一个数组,而是一个受限制的(投影的)数组:我们只可以调用返回类型为类型参数 T
的方法,如上,这意味着我们只能调用 get()
。这就是我们的使用处型变的用法,并且是对应于 Java 的 Array<? extends Object>
、 但使用更简单些的方式。
也可以使用 in 投影一个类型:
fun fill(dest: Array<in String>, value: String) { …… }
Array<in String>
对应于 Java 的 Array<? super String>
,也就是说,你可以传递一个 CharSequence
数组或一个 Object
数组给 fill()
函数。
3.星投影 ????
Kotlin 为此提供了所谓的星投影语法:
- 对于
Foo <out T : TUpper>
,其中T
是一个具有上界TUpper
的协变类型参数,Foo <*>
等价于Foo <out TUpper>
。 这意味着当T
未知时,你可以安全地从Foo <*>
读取TUpper
的值。 - 对于
Foo <in T>
,其中T
是一个逆变类型参数,Foo <*>
等价于Foo <in Nothing>
。 这意味着当T
未知时,没有什么可以以安全的方式写入Foo <*>
。 - 对于
Foo <T : TUpper>
,其中T
是一个具有上界TUpper
的不型变类型参数,Foo<*>
对于读取值时等价于Foo<out TUpper>
而对于写值时等价于Foo<in Nothing>
。
如果泛型类型具有多个类型参数,则每个类型参数都可以单独投影。 例如,如果类型被声明为 interface Function <in T, out U>
,我们可以想象以下星投影:
Function<*, String>
表示Function<in Nothing, String>
;Function<Int, *>
表示Function<Int, out Any?>
;Function<*, *>
表示Function<in Nothing, out Any?>
。
注意:星投影非常像 Java 的原始类型,但是安全。
4、泛型约束
上界
最常见的约束类型是与 Java 的 extends 关键字对应的 上界:
fun <T : Comparable<T>> sort(list: List<T>) {
…… }
冒号之后指定的类型是上界:只有 Comparable<T>
的子类型可以替代 T
。 例如:
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
默认的上界(如果没有声明)是 Any?
。在尖括号中只能指定一个上界。 如果同一类型参数需要多个上界,我们需要一个单独的 where-子句:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter {
it > threshold }.map {
it.toString() }
}
所传递的类型必须同时满足 where
子句的所有条件。在上述示例中,类型 T
必须既实现了 CharSequence
也实现了 Comparable
。
5.类型擦除
Kotlin 为泛型声明用法执行的类型安全检测仅在编译期进行。 运行时泛型类型的实例不保留关于其类型实参的任何信息。 其类型信息称为被擦除。
8 嵌套类与内部类
1.嵌套类
You can also use interfaces with nesting. All combinations of classes and interfaces are possible: You can nest interfaces in classes, classes in interfaces, and interfaces in interfaces.
你可以在类中嵌套接口,在接口中嵌套类,在接口里嵌套接口。
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
interface OuterInterface {
class InnerClass
interface InnerInterface
}
class OuterClass {
class InnerClass
interface InnerInterface
}
2.内部类
标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
3.匿名内部类
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
})
9.枚举类
每个枚举常量都是一个对象。枚举常量用逗号分隔。
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
初始化
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
枚举类中实现接口
一个枚举类可以实现接口(但不能从类继承),可以为所有条目提供统一的接口成员实现,也可以在相应匿名类中为每个条目提供各自的实现
enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
PLUS {
override fun apply(t: Int, u: Int): Int = t + u
},
TIMES {
override fun apply(t: Int, u: Int): Int = t * u
};
override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}
10.对象表达式与对象声明
1 对象表达式
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
/*……*/ }
override fun mouseEntered(e: MouseEvent) {
/*……*/ }
})
如果超类有构造,里面有参数
open class A(x: Int) {
public open val y: Int = x
}
interface B {
/*……*/ }
val ab: A = object : A(1), B {
override val y = 15
}
如果我们只需要“一个对象而已”,并不需要特殊超类型。
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
匿名对象可以用作只在本地和私有作用域中声明的类型。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
//如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问。
2 对象声明
使用object声明单例模式,
对象声明的初始化过程是线程安全的并且在首次访问时进行。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
对象声明也可以有超类型。
注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中
3 .伴生对象???
类内部的对象声明可以用 companion 关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
可通过只使用类名作为限定符来调用
var instance = MyClass.create()
也可以这样
var instance = MyClass.companion
即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员
实现接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
当然,在 JVM 平台,如果使用 @JvmStatic
注解,你可以将伴生对象的成员生成为真正的静态方法和字段
11 类型别名 typealias
类型别名为现有类型提供替代名称
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
??? 待研究
typealias Predicate<T> = (T) -> Boolean
fun foo(p: Predicate<Int>) = p(42)
fun main() {
val f: (Int) -> Boolean = {
it > 0 }
println(foo(f)) // 输出 "true"
val p: Predicate<Int> = {
it > 0 }
println(listOf(1, -2).filter(p)) // 输出 "[1]"
}
12 内联类 inline
为了解决这类问题,Kotlin 引入了一种被称为 内联类
的特殊类,它通过在类的前面定义一个 inline
修饰符来声明:
inline class Password(val value: String)
内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例(关于运行时的内部表达请参阅下文):
// 不存在 'Password' 类的真实实例对象
// 在运行时,'securePassword' 仅仅包含 'String'
val securePassword = Password("Don't try this in production")
1.成员
内联类可以声明属性与函数
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // `greet` 方法会作为一个静态方法被调用
println(name.length) // 属性的 get 方法会作为一个静态方法被调用
}
- 内联类不能含有 init 代码块
- 内联类不能含有幕后字段
2. 内联继承接口
interface Printable {
fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
}
fun main() {
val name = Name("Kotlin")
println(name.prettyPrint()) // 仍然会作为一个静态方法被调用
}
3.表示方式–装箱拆箱
在生成的代码中,Kotlin 编译器为每个内联类保留一个包装器。内联类的实例可以在运行时表示为包装器或者基础类型。这就类似于 Int
可以表示为原生类型 int
或者包装器 Integer
。
为了生成性能最优的代码,Kotlin 编译更倾向于使用基础类型而不是包装器。 然而,有时候使用包装器是必要的。一般来说,只要将内联类用作另一种类型,它们就会被装箱。
interface I
inline class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun <T> id(x: T): T = x
fun main() {
val f = Foo(42)
asInline(f) // 拆箱操作: 用作 Foo 本身
asGeneric(f) // 装箱操作: 用作泛型类型 T
asInterface(f) // 装箱操作: 用作类型 I
asNullable(f) // 装箱操作: 用作不同于 Foo 的可空类型 Foo?
// 在下面这里例子中,'f' 首先会被装箱(当它作为参数传递给 'id' 函数时)然后又被拆箱(当它从'id'函数中被返回时)
// 最后, 'c' 中就包含了被拆箱后的内部表达(也就是 '42'), 和 'f' 一样
val c = id(f)
}
因为内联类既可以表示为基础类型有可以表示为包装器,引用相等对于内联类而言毫无意义,因此这也是被禁止的。
4.名字修饰
由于内联类被编译为其基础类型,因此可能会导致各种模糊的错误,例如意想不到的平台签名冲突:
inline class UInt(val x: Int)
// 在 JVM 平台上被表示为'public final void compute(int x)'
fun compute(x: Int) { }
// 同理,在 JVM 平台上也被表示为'public final void compute(int x)'!
fun compute(x: UInt) { }
为了缓解这种问题,一般会通过在函数名后面拼接一些稳定的哈希码来重命名函数。 因此,fun compute(x: UInt)
将会被表示为 public final void compute-<hashcode>(int x)
,以此来解决冲突的问题。
请注意在 Java 中
-
是一个 无效的 符号,也就是说在 Java 中不能调用使用内联类作为形参的函数。
5.内联类与类型别名
内联类似乎与类型别名非常相似。实际上,两者似乎都引入了一种新的类型,并且都在运行时表示为基础类型。
然而,关键的区别在于类型别名与其基础类型(以及具有相同基础类型的其他类型别名)是 赋值兼容 的,而内联类却不是这样。
内联类引入了一个真实的新类型,与类型别名正好相反,类型别名仅仅是为现有的类型取了个新的替代名称(别名):
13 委托
类似于Java的代理
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
Derived
的超类型列表中的 by-子句表示 b
将会在 Derived
中内部存储, 并且编译器将生成转发给 b
的所有 Base
的方法。
1.委托属性
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好.
-
延迟属性(lazy properties): 其值只在首次访问时计算;
-
可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
-
把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
class Example {
var p: String by Delegate()
}
属性委托需要提供set和get方法,Delegate就是一个对象,内部提供了p的get-set方法。
2 延迟属性 Lazy
lazy()
是接受一个 lambda 并返回一个 Lazy <T>
实例的函数,返回的实例可以作为实现延迟属性的委托
第一次调用 get()
会执行已传递给 lazy()
的 lambda 表达式并记录结果, 后续调用 get()
只是返回记录的结果。
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
默认情况下,lazy是由同步锁的,某一刻只能一个线程在使用。
如果不必须同步锁,将LazyThreadSafetyMode.PUBLICATION
作为参数传递给 lazy()
函数
如果你确定初始化和使用该属性在同一个线程,可以使用 LazyThreadSafetyMode.NONE
模式,他不会有任何线程安全的保证。
3.可观察属性 Observable
//Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
4.委托给另一个属性
从 Kotlin 1.4 开始,一个属性可以把它的 getter 与 setter 委托给另一个属性
- 顶层属性
- 同一个类的成员或扩展属性
- 另一个类的成员或扩展属性
为将一个属性委托给另一个属性,应在委托名称中使用恰当的 ::
限定符
var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
这是很有用的,例如,当想要以一种向后兼容的方式重命名一个属性的时候:引入一个新的属性、 使用 @Deprecated
注解来注解旧的属性、并委托其实现。
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
// 通知:'oldName: Int' is deprecated.
// Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) // 42
}
5.将属性储存在映射中
//一个常见的用例是在一个映射(map)里存储属性的值
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name)
也适用于 var 属性,如果把只读的 Map
换成 MutableMap
6. 局部委托属性
//你可以将局部变量声明为委托属性
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
//memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算
7.属性委托要求
对于一个只读属性(即 val 声明的),委托必须提供一个操作符函数 getValue()
,
thisRef
—— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。property
—— 必须是类型KProperty<*>
或其超类型。
getValue()
必须返回与属性相同的类型(或其子类型)。
class Resource
class Owner {
val valResource: Resource by ResourceDelegate()
}
class ResourceDelegate {
operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
return Resource()
}
}
对于一个可变属性(即 var 声明的),委托必须额外提供一个操作符函数 setValue()
, 该函数具有以下参数:
-
thisRef
—— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。 -
property
—— 必须是类型KProperty<*>
或其超类型。 -
value
— 必须与属性类型相同(或者是其超类型)。class Resource class Owner { var varResource: Resource by ResourceDelegate() } class ResourceDelegate(private var resource: Resource = Resource()) { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return resource } operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) { if (value is Resource) { resource = value } } }
8.翻译规则????
9.提供委托????
5.函数
1.可变数量的参数(Varargs)
函数的参数(通常是最后一个)可以用 vararg
修饰符标记:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
val list = asList(1, 2, 3)
2. 中缀表示法 infix
标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数必须满足以下要求:
infix fun Int.shl(x: Int): Int {
…… }
// 用中缀表示法调用该函数
1 shl 2
// 等同于这样
1.shl(2)
3.函数作用域
a.局部函数
//Kotlin 支持局部函数,即一个函数在另一个函数内部:
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
//局部函数可以访问外部函数(即闭包)的局部变量
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
b.尾递归函数
Kotlin 支持一种称为尾递归的函数式编程风格。一些通常用循环写的算法改用递归函数来写,而无堆栈溢出的风险
当一个函数用 tailrec
修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
//要符合 tailrec 修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。
//在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。目前在 Kotlin for JVM 与 Kotlin/Native 中支持尾递归。
4.高阶函数?????
Kotlin 函数都是头等的,这意味着它们可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回。
高阶函数是将函数用作参数或返回值的函数。
https://www.kotlincn.net/docs/reference/lambdas.html
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
//参数 combine 具有函数类型 (R, T) -> R,因此 fold 接受一个函数作为参数,
//该函数接受类型分别为 R 与 T 的两个参数并返回一个 R 类型的值。 在 for-循环内部调用该函数,然后将其返回值赋值给 accumulator。
1.函数类型
Kotlin 使用类似 (Int) -> String
的一系列函数类型来处理函数的声明
- 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:
(A, B) -> C
表示接受类型分别为A
与B
两个参数并返回一个C
类型值的函数类型。 参数类型列表可以为空,如() -> A
。Unit
返回类型不可省略。 - 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型
A.(B) -> C
表示可以在A
的接收者对象上以一个B
类型参数来调用并返回一个C
类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用。 - 挂起函数属于特殊种类的函数类型,它的表示法中有一个 suspend 修饰符 ,例如
suspend () -> Unit
或者suspend A.(B) -> C
。
可以通过使用类型别名给函数类型起一个别称:
typealias ClickHandler = (Button, ClickEvent) -> Unit
2.函数类型实例化
有几种方法可以获得函数类型的实例:
- lambda 表达式:
{ a, b -> a + b }
, - 匿名函数: fun(s:String):Int { return s.toIntOrNull() ?: 0 }
- 带有接收者的函数字面值可用作带有接收者的函数类型的值。
- 使用实现函数类型接口的自定义类的实例:
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
- 带与不带接收者的函数类型非字面值可以互换,其中接收者可以替代第一个参数,反之亦然。例如,
(A, B) -> C
类型的值可以传给或赋值给期待A.(B) -> C
的地方,反之亦然:
fun main() {
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK
println("result = $result")
}
3.函数类型实例调用
函数类型的值可以通过其 invoke(……)
操作符调用:f.invoke(x)
或者直接 f(x)
。如果该值具有接收者类型,那么应该将接收者对象作为第一个参数传递。 调用带有接收者的函数类型值的另一个方式是在其前面加上接收者对象, 就好比该值是一个扩展函数:
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // 类扩展调用
4.Lambda 表达式与匿名函数
lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递
max(strings, { a, b -> a.length < b.length })
函数 max
是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值,它等价于以下具名函数:
fun compare(a: String, b: String): Boolean = a.length < b.length
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 ->
符号之后。如果推断出的该 lambda 的返回类型不是 Unit
,那么该 lambda 主体中的最后一个(或可能是单个) 表达式会视为返回值。
如果我们把所有可选标注都留下,看起来如下:
val sum = { x: Int, y: Int -> x + y }
5 传递末尾的 lambda 表达式
在 Kotlin 中有一个约定:如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外:
val product = items.fold(1) { acc, e -> acc * e }
这种语法也称为拖尾 lambda 表达式。
6it
:单个参数的隐式名称
如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->
。 该参数会隐式声明为 it
:
ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的
7 从 lambda 表达式中返回一个值
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
8 下划线用于未使用的变量
map.forEach { _, value -> println("$value!") }
9 匿名函数
fun(x: Int, y: Int): Int = x + y
6 内联函数????
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。
通过内联化 lambda 表达式可以消除这类的开销