kotlin学习笔记

与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<Person> = mutableListOf<Person>();
    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<out T> {
    fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
    // ……
}

out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。 这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。

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!
}

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<DataProvider>
        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<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

8.高阶函数:将函数用作参数或返回值的函数

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
}

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 }
顶层、局部、成员、扩展:::isOddString::toInt
顶层、成员、扩展:List<Int>::size
构造函数:::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<String?> = 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<Int>.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 <T> List<T>.lastIndex: Int
    get() = size - 1

扩展属性不能有初始化器

class C {
    fun D.foo() {
        toString()         // 调用 D.toString()
        this@C.toString()  // 调用 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<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

例子:

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]"
}

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<Unit> { // 开始执行主协程,直到执行完毕再执行协程
    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<Int>()
launch {
    // 这里可能是消耗大量 CPU 运算的异步逻辑,我们将仅仅做 5 次整数的平方并发送
    for (x in 1..5) channel.send(x * x)
}
// 这里我们打印了 5 次被接收的整数:
repeat(5) { println(channel.receive()) }
println("Done!")

一些知识点

1.==、===和equal的区别?

==和equal的作用相同,===比较内存地址

2.中缀函数

中缀函数需要是用infix关键字修饰

infix fun <T> List<T>.jiaoji(other: List<T>): List<T> {
    val result = ArrayList<T>()
    forEach {
        if (other.contains(it)) {
            result.add(it)
        }
    }
    return result
}

调用:
var result = list1.toList() jiaoji (list2.toList()) // 4, 5, 6,

3.扩展函数原理

原理: 传入了一个扩展类的对象,内部实际上是用实例对象去访问扩展类的方法和属性

fun View.shutOff(){
     println("view shut off")
}
//实际上是传入view对象
public final void shutOff(@NotNull View $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      String var2 = "view shut off";
      System.out.println(var2);
}

4.类型协变

假设我们有一个泛型接口Source< in T, out R >, 其中T由协变注解in修饰,R由协变注解Out修饰.

internal interface Source<in T, out R> {
    // in 函数,可以当做参数使用,消费,但是不能作为返回值
    fun mapT(t: T): Unit
    
    // out 函数,不能用来当参数,不能消费,但是可以作为返回值
    fun nextR(): R
}

in T:     来确保Source的成员函数只能消费T类型,而不能返回T类型
out R:  来确保Source的成员函数只能返回R类型,而不能消费R类型

5.序列

fun main(args: Array<String>) {
    val list = mutableListOf<String>("1","2","3","4","5","6","7","8","9")
    val l = list.asSequence()
            .filter { it.toCharArray()[0] < '4' }
            .map { it.toInt() * it.toInt() }
            .toList()
}

对于上述序列中的"1",它会先执行filter,再执行map,之后再对"2"重复操作。除此以外,序列中所有的中间操作都是惰性的。

集合类:map和filter方法是内联,不会生成匿名类的实例,但每次进行map和filter都会生成新的集合,当数据量大的时候,消耗的内存也比较大。
序列:map和fitler非内联,会生成匿名类实例,但不需要创建额外的集合保存中间操作的结果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352