Kotlin语法手册(四)

Kotlin语法手册(四)

在使用kotlin时,由于掌握的不够牢靠,好多时候也还是Java编程的习惯,浪费了kotlin提供的语言特性,方便性,间接性,在阅读一些Android开源库的时候,由于好多都是kotlin语法编写的,看的比较费劲,还得去查阅kotlin的语法,比较不方便,故把kotlin的语法记录下来,方便查阅温故,巩固自己的基础知识。

异常

kotlin 中异常处理的基本形式和 Java 类似,和 Java 不同的是,kotlin 中 throw 结构是一个表达式,可以作为另一个表达式的一部分来使用

//如果条件不满足,则将抛出异常,status 变量也不会初始化
val status = if (index in 0..10) index else throw IllegalArgumentException("参数错误")

在 Java 中对于受检异常必须显式地处理,通过 try/catch 语句捕获异常或者是抛给其调用者来处理。而 kotlin 不区分受检异常和未受检异常,不用指定函数抛出的异常,可以处理也可以不处理异常。

在 kotlin 中 ,try 关键字引入了一个表达式,从而可以把表达式的值赋给一个变量。如果一个 try 代码块执行正常,代码块中最后一个表达式就是结果,如果捕获到了一个异常,则相应 catch 代码块中最后一个表达式就是结果。

fun main() {
    compute(5)   //fun end : true
    compute(100) //fun end : null
}

fun compute(index: Int) {
    val status = try {
        if (index in 0..10) true else throw IllegalArgumentException("参数错误")
    } catch (e: Exception) {
        null
    }
    println("fun end : " + status)
}

如果在 catch 语句中使用 return 结束了 compute 函数,则没有任何输出

中缀调用与解构声明

中缀调用

fun main() {
    val maps = mapOf(1 to "leavesC", 2 to "ye", 3 to "https://juejin.cn/user/923245496518439")
    maps.forEach { key, value -> println("key is : $key , value is : $value") }
}

使用 “to” 来声明 map 的 key 与 value 之间的对应关系,这种形式的函数调用被称为中缀调用。

kotlin 标准库中对 to 函数的声明如下所示,其作为扩展函数存在,且是一个泛型函数,返回值 Pair 最终再通过解构声明分别将 key 和 value 传给 Map

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

中缀调用只能与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。中缀符号需要通过 infix 修饰符来进行标记

解构声明

把一个对象拆解成多个变量的需求,在 kotlin 中这种语法称为解构声明

data class Person(val name: String, val age: Int)

fun main() {
    val (name, age) = Person("leavesC", 24)
    println("Name: $name , age: $age")
    //Name: leavesC , age: 24
}

例子中将 Person 变量结构为了两个新变量:name 和 age
一个解构声明会被编译成以下代码:

val name = person.component1()
val age = person.component2()

需要注意的是,componentN() 函数需要用 operator 关键字标记,以允许在解构声明中使用它们,对于数据类来说,其自动生成了 componentN() 函数,而对非数据类,为了使用解构声明,需要我们自己来手动声明函数

class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

fun main() {
    val point = Point(100, 200)
    val (x, y) = point
    println("x: $x , y: $y")
    //x: 100 , y: 200
}

此外,解构声明也可以用在 for 循环中

val list = listOf(Person("leavesC", 24), Person("leavesC", 25))
for ((name, age) in list) {
     println("Name: $name , age: $age")
}

对于遍历 map 同样适用

val map = mapOf("leavesC" to 24, "ye" to 25)
for ((name, age) in map) {
     println("Name: $name , age: $age")
}

同样也适用于lambda表达式

val map = mapOf("leavesC" to 24, "ye" to 25)
map.mapKeys { (key, value) -> println("key : $key , value : $value") }

如果在解构声明中不需要某个变量,那么可以用下划线取代其名称,此时不会调用相应的 componentN() 操作符函数

val map = mapOf("leavesC" to 24, "ye" to 25)
for ((_, age) in map) {
    println("age: $age")
}

Object 关键字

对象声明

在 kotlin 的世界中,可以通过对象声明这一功能来实现 Java 中的单例模式,将类声明与该类的单一实例声明结合到一起。与类一样,一个对象声明可以包含属性、方法、初始化语句块等的声明,且可以继承类和实现接口,唯一不被允许的是构造方法。与普通类的实例不同,对象声明在定义的时候就被立即创建了,不需要在代码的其它地方调用构造方法,因此为对象声明定义构造方法是没有意义的。

interface Fly {

    fun fly()

}

open class Eat {

    fun eat() {
        println("eat")
    }

}

object Animal : Eat(), Fly {

    override fun fly() {
        println("fly")
    }

}

fun main() {
    Animal.fly()
    Animal.eat()
}

kotlin 中的对象声明被编译成了通过静态字段来持有它的单一实例的类,这个字段名字始终都是 INSTANCE

伴生对象

如果需要一个可以在没有类实例的情况下调用但是需要访问类内部的函数(类似于 Java 中的静态变量/静态函数),可以将其写成那个类中的对象声明的成员

通过关键字 companion ,就可以获得通过容器类名称来访问这个对象的方法和属性的能力,不再需要显式地指明对象的名称

class Test {

    companion object {

        const val NAME = ""

        fun testFun() {

        }
    }

}

fun main() {
    Test.NAME
    Test.testFun()
}

工厂模式

可以利用伴生对象来实现工厂模式

private class User private constructor(val name: String) {

    companion object {
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    //构造函数私有,无法创建
    //val user1 = User("leavesC")
    val user2 = User.newById(10)
    val user3 = User.newByDouble(1.3)
}

实现接口

伴生对象也可以实现接口,且可以直接将包含它的类的名字当做实现了该接口的对象实例来使用

private class User private constructor(val name: String) {

    companion object UserLoader : Runnable {

        override fun run() {

        }
    }

}

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    //User 会直接被当做 Runnable 的实例
    val thread = newThread(User)
    val thread2 = newThread(User.UserLoader)
}

委托

委托模式

委托模式是一种基本的设计模式,该模式下有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。kotlin 原生支持委托模式,可以零样板代码来实现,通过关键字 by 实现委托

interface Printer {

    fun print()
    
}

class DefaultPrinter : Printer {

    override fun print() {
         println("DefaultPrinter print")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer

fun main() {
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter print
}

CustomPrinter 的 by 子句表示将会在 CustomPrinter 中存储 printer 变量,并且编译器将为 CustomPrinter 隐式生成 Printer 接口的所有抽象方法,并将这些方法的调用操作转发给 printer

此外,CustomPrinter 也可以决定自己实现部分方法或全部自己实现,但重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现

interface Printer {

    val message: String

    fun print()

    fun reprint()

}

class DefaultPrinter : Printer {

    override val message: String = "DefaultPrinter message"

    override fun print() {
        println(message)
    }

    override fun reprint() {
        println("DefaultPrinter reprint")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer {

    override val message: String = "CustomPrinter message"

    override fun reprint() {
        println("CustomPrinter reprint")
    }

}

fun main() {
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter message
    printer.reprint() //CustomPrinter reprint
}

属性委托

kotlin 支持通过委托属性将对一个属性的访问操作委托给另外一个对象来完成,对应的语法格式是:

val/var <属性名>: <类型> by <表达式>

by 后面的表达式是该 委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue()setValue() 方法。
属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(与 setValue()——对于 var 属性)。

class Delegate {

    private var message: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("${thisRef?.javaClass?.name}, thank you for delegating '${property.name}' to me!")
        return message ?: "null value"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {\
        println("$value has been assigned to '${property.name}' in ${thisRef?.javaClass?.name}.")
        message = value
    }
}

class Example {
    var strValue: String by Delegate()
}

    fun main() {
    val example = Example()
    println(example.strValue)
     //    test.Example, thank you for delegating 'strValue' to me!
    //    null value
    example.strValue = "leaveC"
    //    leaveC has been assigned to 'strValue' in test.Example.
    println(example.strValue)
    //    test.Example, thank you for delegating 'strValue' to me!
    //    leaveC
}

延迟属性

lazy() 是接受一个 lambda 并返回一个 Lazy < T > 实例的函数,返回的实例可以作为实现延迟属性的委托,第一次调用 get() 会执行已传递给 lazy() 函数的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果

class Example {

    val lazyValue1: String by lazy {
        println("lazyValue1 computed!")
        "Hello"
    }

    val lazyValue2: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        println("lazyValue2 computed!")
        computeLazyValue()
    }

    private fun computeLazyValue() = "leavesC"

}

fun main() {
    val example = Example()
    println(example.lazyValue1) //lazyValue1 computed!     Hello
    println(example.lazyValue1) //Hello
    println(example.lazyValue2) //lazyValue2 computed! leavesC
}

默认情况下,对于 lazy 属性的求值是带同步锁的(synchronized),即带有 LazyThreadSafetyMode.SYNCHRONIZED 参数,此时该值只允许同一时刻只能有一个线程对其进行初始化,并且所有线程会看到相同的初始化值。如果初始化委托的同步锁不是必需的,即如果允许多个线程同时执行,那么可以将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么可以使用 LazyThreadSafetyMode.NONE 模式, 此时不会有任何线程安全的保证以及相关的资源开销

可观察属性

Delegates.observable() 接受两个参数:初始值以及修改属性值时的回调函数。当为属性赋值后就会调用该回调函数,该回调函数包含三个参数:被赋值的属性、旧值与新值

fun main() {
    val example = Example()
    example.age = 24 //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = 27 //kProperty.name: age , oldValue: 24 , newValue: 27
}

class Example {
    var age: Int by Delegates.observable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    }
}

如果想要拦截一个赋值操作并判断是否进行否决,可以使用 vetoable() 函数,通过返回一个布尔值来决定是否进行拦截,该判断逻辑是在属性被赋新值生效之前进行

fun main() {
    val example = Example()
    example.age = 24  //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = -10 //kProperty.name: age , oldValue: 24 , newValue: -10
    example.age = 30  //kProperty.name: age , oldValue: 24 , newValue: 30 (oldValue 依然是 24,说明第二次的赋值操作被否决了)
}

class Example {
    var age: Int by Delegates.vetoable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
        age <= 0 //返回true 则表示拦截该赋值操作
    }
}

把属性储存在映射中

可以在一个 map 映射里存储属性的值,然后把属性的存取操作委托给 map 进行管理

fun main() {
    val student = Student(
        mapOf(
            "name" to "leavesC",
            "age" to 24
        )
    )
    println(student.name)
    println(student.age)
}

class Student(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

在上述示例中,属性 name 和 age 都是不可变的(val),因此 map 的类型也是 Map 而非 MutableMap(MutableMap 在赋值后可以修改),因此如果为了支持 var 属性,可以将只读的 Map 换成 MutableMap

局部委托属性

可以将局部变量声明为委托属性

class Printer {

    fun print() {
        println("temp.Printer print")
    }

}

fun getPrinter(): Printer {
    println("temp.Printer getPrinter")
    return Printer()
}

//局部委托
fun example(getPrinter: () -> Printer) {
    val lPrinter by lazy(getPrinter)
    val valid = true
    if (valid) {
        lPrinter.print()
    }
}

fun main() {
    example { getPrinter() }
    //temp.Printer getPrinter
    //temp.Printer print
}

委托变量只会在第一次访问时才会进行初始化,因此如果 valid 为 false 的话,getPrinter() 方法就不会被调用

注解

注解是将元数据附加到代码元素上的一种方式,附件的元数据就可以在编译后的类文件或者运行时被相关的源代码工具访问
注解的语法格式如下所示:

annotation class AnnotationName()

注解的附加属性可以通过用元注解标注注解类来指定:

  • @Target 指定该注解标注的允许范围(类、函数、属性等)
  • @Retention 指定该注解是否要存储在编译后的 class 文件中,如果要保存,则在运行时可以通过反射来获取到该注解值
  • @Repeatable 标明允许在单个元素上多次使用相同的该注解
  • @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中
    @Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
    @Retention(AnnotationRetention.RUNTIME)
    @Repeatable
    @MustBeDocumented
    annotation class AnnotationName()

注解可以声明包含有参数的构造函数

annotation class OnClick(val viewId: Long)

允许的参数类型有:

  • 原生数据类型,对应 Java 原生的 int 、long、char 等
  • 字符串
  • class 对象
  • 枚举
  • 其他注解
  • 以上类型的数组
    注解参数不能包含有可空类型,因为 JVM 不支持将 null 作为注解属性的值来存储
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class OnClick(val viewId: Long)

class AnnotationsTest {

    @OnClick(200300)
    fun onClickButton() {
        println("Clicked")
    }

}

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

推荐阅读更多精彩内容

  • 一、类 1.1 类声明 Kotin中使用关键字class声明类,且默认是public。如果一个类没有类体,可以省略...
    者文_阅读 1,271评论 0 1
  • Kotlin语法手册(三) 在使用kotlin时,由于掌握的不够牢靠,好多时候也还是Java编程的习惯,浪费了ko...
    69451dd36574阅读 244评论 0 0
  • kotlin的基础语法 函数定义 函数定义使用关键字 fun,参数格式为:参数 : 类型 可变长参数函数 函数的变...
    不怕天黑_0819阅读 8,561评论 0 5
  • 简介 Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。Kotlin可以编译成Jav...
    面包石头阅读 5,368评论 0 52
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,142评论 9 118