与java不同的特性
1.包导入
import bar.Bar as bBar // bBar 代表“bar.Bar”
2.控制流
if的分支可以是代码块,最后的表达式作为该块的值:
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
3.返回和跳转
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
4.类与继承
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。
主构造
class InitOrderDemo(name: String) {
init {
println("First initializer block that prints ${name}")
}
}
次构造
class Person {
var children: MutableList = mutableListOf();
constructor(parent: Person) {
parent.children.add(this)
}
}
继承
在 Kotlin 中所有类都有一个共同的超类 Any
open和override
在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。
open class Base {
open fun v() { ... }
fun nv() { ... }
}
class Derived() : Base() {
override fun v() { ... }
}
5.属性与字段
var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter
val simple: Int? // 类型 Int、默认 getter、必须在构造函数中初始化
val inferredType = 1 // 类型 Int 、默认 getter
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
可见性
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // 默认 public
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a 不可见
// b、c、d 可见
// Nested 和 e 可见
override val b = 5 // “b”为 protected
}
class Unrelated(o: Outer) {
// o.a、o.b 不可见
// o.c 和 o.d 可见(相同模块)
// Outer.Nested 不可见,Nested::e 也不可见
}
6.泛型
型变
interface Source {
fun nextT(): T
}
fun demo(strs: Source) {
val objects: Source = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。 这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。
interface Comparable {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,我们可以将 x 赋给类型为 Comparable 的变量
val y: Comparable = x // OK!
}
in。它使得一个类型参数逆变:只可以被消费而不可以被生产。
类型投影,星投影,上界
//TODO
7.嵌套类与内部类
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
类可以标记为 inner 以便能够访问外部类的成员。内部类会带有一个对外部类的对象的引用
8.对象表达式与对象声明
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
}
用对象表示一个子类继承
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”
}
}
用对象实现单列模式(线程安全)
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection
get() = // ……
}
DataProviderManager.registerDataProvider(……)
伴生对象
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
8.函数
1.如果一个函数不返回任何有用的值,它的返回类型是 Unit
2.覆盖方法,必须省略默认参数值
open class A {
open fun foo(i: Int = 10) { …… }
}
class B : A() {
override fun foo(i: Int) { …… } // 不能有默认值
}
3.如果在默认参数之后的最后一个参数是 lambda 表达式
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { …… }
foo(1) { println("hello") } // 使用默认值 baz = 1
foo(qux = { println("hello") }) // 使用两个默认值 bar = 0 与 baz = 1
foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
4.可以通过使用星号操作符将可变数量参数(vararg) 以命名形式传入
fun foo(vararg strings: String) { …… }
foo(strings = *arrayOf("a", "b", "c"))
5.可变数量的参数(Varargs)
6.中缀表示法(忽略该调用的点与圆括号)
infix fun Int.shl(x: Int): Int { …… }
// 用中缀表示法调用该函数
1 shl 2
// 等同于这样
1.shl(2)
7.局部函数
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
8.高阶函数:将函数用作参数或返回值的函数
fun Collection.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
}
9.函数类型
函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。
挂起函数属于特殊种类的函数类型,它的表示法中有一个 suspend 修饰符 ,例如 suspend () -> Unit
或者 suspend A.(B) -> C
。
lambda 表达式: { a, b -> a + b }
,
匿名函数: fun(s: String): Int { return s.toIntOrNull() ?: 0 }
顶层、局部、成员、扩展:::isOdd
、 String::toInt
顶层、成员、扩展:List
构造函数:::Regex
//TODO
10.内联函数
//TODO
9.解构声明
//TODO
10.相等性
结构相等
结构相等由 ==(以及其否定形式 !=)操作判断
引用相等
两个引用指向同一对象
引用相等由 ===(以及其否定形式 !==)操作判断。
11.空安全
var b: String? = "abc"
b = null // ok
print(b)
声明一个变量为可空字符串
bob?.department?.head?.name
如果任意一个属性(环节)为空,这个链式调用就会返回 null。
如果要只对非空值执行某个操作,安全调用操作符可以与 let
一起使用
val listWithNulls: List = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 Kotlin 并忽略 null
}
当我们有一个可空的引用 r 时,我们可以说“如果 r 非空,我使用它;否则使用某个非空的值 x”:
val l = b?.length ?: -1
操作符!!
非空断言运算符(!!)将任何值转换为非空类型,若该值为空则抛出异常
java没有的特性
1.属性与字段
幕后字段
//TODO
幕后属性
//TODO
延迟初始化属性与变量
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
2.扩展
扩展函数
fun MutableList.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
swap()是MutableList的扩展函数,这样就不必要专门写子类继承
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
这个例子会输出 "c",因为调用的扩展函数只取决于参数 c 的声明类型,该类型是 C 类。
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
如果我们调用 C 类型 c的 c.foo(),它将输出“member”,而不是“extension”。
扩展属性
val List.lastIndex: Int
get() = size - 1
扩展属性不能有初始化器
class C {
fun D.foo() {
toString() // 调用 D.toString()
[email protected]() // 调用 C.toString()
}
}
对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 this语法
3.数据类
data class User(val name: String = "", val age: Int = 0)
如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值
4.密封类
在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
5.类型别名
typealias NodeSet = Set
typealias FileTable = MutableMap>
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate = (T) -> Boolean
例子:
typealias Predicate = (T) -> Boolean
fun foo(p: Predicate) = p(42)
fun main() {
val f: (Int) -> Boolean = { it > 0 }
println(foo(f)) // 输出 "true"
val p: Predicate = { it > 0 }
println(listOf(1, -2).filter(p)) // 输出 "[1]"
}
6.内联类
inline class Password(val value: String)
// 不存在 'Password' 类的真实实例对象
// 在运行时,'securePassword' 仅仅包含 'String'
val securePassword = Password("Don't try this in production")
内联类不能含有 init 代码块
内联类不能含有幕后字段
内联类不能继承其他的类而且必须是 final
7.委托
委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。 Derived
类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base
:
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()
}
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
以上代码输出为abc10
interface Base {
val message: String
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}
class Derived(b: Base) : Base by b {
// 在 b 的 `print` 实现中不会访问到这个属性
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}
以上代码输出为
BaseImpl: x = 10
Message of Derived
委托属性
//TODO
8.操作符重载
//TODO
协程
桥接阻塞与非阻塞的世界
非阻塞的 delay(……) 与 阻塞的 Thread.sleep(……):
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}
调用了 runBlocking 的主线程会一直 阻塞 直到 runBlocking 内部的协程执行完毕:
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主线程中的代码会立即执行
runBlocking { // 但是这个表达式阻塞了主线程
delay(2000L) // ……我们延迟 2 秒来保证 JVM 的存活
}
}
import kotlinx.coroutines.*
fun main() = runBlocking { // 开始执行主协程,直到执行完毕再执行协程
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主协程在这里会立即执行
delay(2000L) // 延迟 2 秒来保证 JVM 存活
}
作用域构建器coroutineScope
它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // 创建一个协程作用域
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出
}
println("Coroutine scope is over") // 这一行在内嵌 launch 执行完毕后才输出
}
提取函数重构
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
// 这是你的第一个挂起函数
suspend fun doWorld() {
delay(1000L)
println("World!")
}
通道
val channel = Channel()
launch {
// 这里可能是消耗大量 CPU 运算的异步逻辑,我们将仅仅做 5 次整数的平方并发送
for (x in 1..5) channel.send(x * x)
}
// 这里我们打印了 5 次被接收的整数:
repeat(5) { println(channel.receive()) }
println("Done!")