Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

学点Kotlin

2021/12/02 Kotlin
Word count: 7,574 | Reading time: 31min

Kotlin 基础语法

Kotlin 文件以 .kt 为后缀。

函数定义

函数定义使用关键字 fun,参数格式为:参数 : 类型

1
2
3
fun sum(a: Int, b: Int): Int {   // Int 参数,返回值 Int
return a + b
}

表达式作为函数体,返回类型自动推断:

1
2
3
fun sum(a: Int, b: Int) = a + b

public fun sum(a: Int, b: Int): Int = a + b // public 方法则必须明确写出返回类型

无返回值的函数(类似Java中的void):

1
2
3
4
5
6
7
8
9
fun printSum(a: Int, b: Int): Unit { 
print(a + b)
}


// 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) {
print(a + b)
}

可变长参数函数

函数的变长参数可以用 vararg 关键字进行标识:

1
2
3
4
5
6
7
8
9
10
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}

// 测试
fun main(args: Array<String>) {
vars(1,2,3,4,5) // 输出12345
}

lambda(匿名函数)

lambda表达式使用实例:

1
2
3
4
5
// 测试
fun main(args: Array<String>) {
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
println(sumLambda(1,2)) // 输出 3
}

NULL检查机制

Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理

1
2
3
4
5
6
7
8
//类型后面加?表示可为空
var age: String? = "23"
// 不能为空,否则抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1

当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。

当 str 中的字符串内容不是一个整数时, 返回 null:

1
2
3
fun parseInt(str: String): Int? {
// ...
}

Kotlin 基本数据类型

Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是,字符不属于数值类型,是一个独立的数据类型。

Kotlin 中没有基础数据类型,只有封装的数字类型。你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样可以保证不会出现空指针。数字类型也一样,所以在比较两个数字的时候,就有比较数据大小和比较两个对象是否相同的区别了。

在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小

  • var x = 1..16: [1,16]
  • var x = 1 until 16: [1, 16)

变量和常量

可变变量定义:var 关键字

1
2
var <标识符> : <类型> = <初始化值>
val <标识符> : <类型> = <初始化值>

字符串模板

  • $ 表示一个变量名或者变量值
  • $varName表示变量值
  • ${varName.fun()}表示变量的方法返回值:
1
var a = 1// 模板中的简单名称:val s1 = "a is $a" a = 2// 模板中的任意表达式:val s2 = "${s1.replace("is", "was")}, but now is $a"

Kotlin 条件控制

Kotlin 循环控制

1
// 对任何提供迭代器(iterator)的**对象**进行遍历for (item in collection) print(item)// 通过**索引**遍历一个数组或者一个 listfor (i in array.indices) {    print(array[i])}// 索引+值,相当于Python中的enumeratefor ((index, value) in array.withIndex()) {    println("the element at $index is $value")}
1
// while循环while( 布尔表达式 ) {  //循环内容}// do循环do {       //代码语句}while(布尔表达式);

返回和跳转

Kotlin 有三种结构化跳转表达式:

  • return。默认从最直接包围它的函数或者匿名函数返回。
  • break。终止最直接包围它的循环。
  • continue。继续下一次最直接包围它的循环。

Break 和 Continue 的跳转标签

在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符+跟 @,例如:abc@、fooBar@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可

1
loop@ for (i in 1..100) {    for (j in 1..100) {        if (……) break@loop    }}

标签限制使得 break 跳转到刚好位于该标签指定循环的后面执行点。 continue 继续标签指定的循环的下一次迭代。

标签处返回

Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 标签处返回的最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候:

1
fun foo() {    ints.forEach {        if (it == 0) return        print(it)    }}

这个 return 表达式从最直接包围它的函数即 foo 中返回。 (注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。) 如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制 return。

1
fun foo() {    ints.forEach lit@ {        if (it == 0) return@lit        print(it)    }}

现在,它只会从 lambda 表达式中返回。通常情况下使用隐式标签更方便。 该标签与接受该 lambda 的函数同名。

1
fun foo() {    ints.forEach {        if (it == 0) return@forEach        print(it)    }}

或者,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回

1
fun foo() {    ints.forEach(fun(value: Int) {        if (value == 0) return        print(value)    })}

当要返一个回值的时候,解析器优先选用标签限制的 return,即

1
return@a 1

意为"从标签 @a 返回 1",而不是"返回一个标签标注的表达式 (@a 1)"。

Kotlin 类和对象

类的定义

1
class Runoob constructor(name: String) {    fun foo() { print("Foo") } // 成员函数    var url: String = ……            // 次构造函数    constructor (name: String, alexa: Int) : this(name) {        println("Alexa 排名 $alexa")    }    var lastName: String = "zhang"        get() = field.toUpperCase()   // 将变量赋值后转换为大写        set    var no: Int = 100        get() = field                // 后端变量        set(value) {            if (value < 10) {       // 如果传入的值小于 10 返回该值                field = value            } else {                field = -1         // 如果传入的值大于等于 10 返回 -1            }        }    var heiht: Float = 145.4f        private set    }

对象的声明

1
val site = Runoob() // Kotlin 中没有 new 关键字site.name           // 使用 . 号来引用site.url

构造函数

Koltin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后:

1
class Person constructor(firstName: String) {    init {        println("FirstName is $firstName")    }}

如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。

1
class Person(firstName: String) {}

次构造函数

类也可以有二级构造函数,需要加前缀 constructor:

1
class Person {     constructor(parent: Person) {        parent.children.add(this)     }}

如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:

1
class Person(val name: String) {    constructor (name: String, age:Int) : this(name) {        // 初始化...    }}

如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:

1
class DontCreateMe private constructor () {}

getter 和 setter

1
var <propertyName>[: <PropertyType>] [= <property_initializer>]    [<getter>]    [<setter>]// var变量可设置getter 和 setter, 都是可选// 如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法var initialized = 1    // 类型为 Int, 默认实现了 getter 和 setterval simple: Int?       // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化val inferredType = 1   // 类型为 Int 类型,默认实现 getter

抽象类

抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。

注意:无需对抽象类或抽象成员标注open注解。

1
open class Base {    open fun f() {}}abstract class Derived : Base() {    override abstract fun f()}

嵌套类

我们可以把类嵌套在其他类中,看以下实例:

1
class Outer {                  // 外部类    private val bar: Int = 1    class Nested {             // 嵌套类        fun foo() = 2    }}

内部类

内部类使用 inner 关键字来表示。

内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。

1
class Outer {    private val bar: Int = 1    var v = "成员属性"    /**嵌套内部类**/    inner class Inner {        fun foo() = bar  // 访问外部类成员        fun innerTest() {            var o = this@Outer //获取外部类的成员变量            println("内部类可以引用外部类的成员,例如:" + o.v)        }    }}

匿名内部类

使用对象表达式来创建匿名内部类:

1
class Test {    var v = "成员属性"    fun setInterFace(test: TestInterFace) {        test.test()    }}/** * 定义接口 */interface TestInterFace {    fun test()}fun main(args: Array<String>) {    var test = Test()    /**     * 采用对象表达式来创建接口对象,即匿名内部类的实例。     */    test.setInterFace(object : TestInterFace {        override fun test() {            println("对象表达式创建匿名内部类的实例")        }    })}

类的修饰符

类的修饰符包括 classModifier 和_accessModifier_:

  • classModifier: 类属性修饰符,标示类本身特性。

    1
    abstract    // 抽象类  final       // 类不可继承,默认属性enum        // 枚举类open        // 类可继承,类默认是final的annotation  // 注解类
  • accessModifier: 访问权限修饰符

    1
    private    // 仅在同一个文件中可见protected  // 同一个文件中或子类可见public     // 所有调用的地方都可见internal   // 同一个模块中可见

Kotlin 继承

Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:

Any 默认提供了三个函数:

  • equals()
  • hashCode()
  • toString()

注意:Any 不是 java.lang.Object。

如果一个类要被继承,可以使用 open 关键字进行修饰。

1
open class Base(p: Int)           // 定义基类class Derived(p: Int) : Base(p)

构造函数

子类有主构造函数

如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。

1
open class Person(var name : String, var age : Int){// 基类}class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {}// 测试fun main(args: Array<String>) {    val s =  Student("Runoob", 18, "S12346", 89)    println("学生名: ${s.name}")    println("年龄: ${s.age}")    println("学生号: ${s.no}")    println("成绩: ${s.score}")}

输出结果:

1
学生名: Runoob年龄: 18学生号: S12346成绩: 89

子类没有主构造函数

如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。

1
class Student : Person {    constructor(ctx: Context) : super(ctx) {    }     constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {    }}

重写

在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词:

1
/**用户基类**/open class Person{    open fun study(){       // 允许子类重写        println("我毕业了")    }}/**子类继承 Person 类**/class Student : Person() {    override fun study(){    // 重写方法        println("我在读大学")    }}fun main(args: Array<String>) {    val s =  Student()    s.study();}

如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现。

1
open class A {    open fun f () { print("A") }    fun a() { print("a") }}interface B {    fun f() { print("B") } //接口的成员变量默认是 open 的    fun b() { print("b") }}class C() : A() , B{    override fun f() {        super<A>.f()//调用 A.f()        super<B>.f()//调用 B.f()    }}fun main(args: Array<String>) {    val c =  C()    c.f();}

C 继承自 a() 或 b(), C 不仅可以从 A 或则 B 中继承函数,而且 C 可以继承 A()、B() 中共有的函数。此时该函数在中只有一个实现,为了消除歧义,该函数必须调用A()和B()中该函数的实现,并提供自己的实现。

输出结果为:

1
AB

属性重写

属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写:

1
open class Foo {    open val x: Int get { …… }}class Bar1 : Foo() {    override val x: Int = ……}

注:可以用一个var属性重写一个val属性,但是反过来不行。

Kotlin 接口

接口定义

Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现:

1
interface MyInterface {    fun bar()    // 未实现    fun foo() {  //已实现      // 可选的方法体      println("foo")    }    // 接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性:    var name:String //name 属性, 抽象的}

实现接口

一个类或者对象可以实现一个或多个接口。

1
class Child : MyInterface {    override fun bar() {        // 方法体    }}

Kotlin 扩展

Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。

扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

将函数扩展

扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:

  • receiverType:表示函数的接收者,也就是函数扩展的对象
  • functionName:扩展函数的名称
  • params:扩展函数的参数,可以为NULL
1
// 函数扩展原型fun receiverType.functionName(params){    body}class User(var name:String)/**扩展函数**/fun User.Print(){    print("用户名 $name")}

扩展函数是静态解析的

扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:

1
open class Cclass D: C()fun C.foo() = "c"   // 扩展函数 foofun D.foo() = "d"   // 扩展函数 foofun printFoo(c: C) {    println(c.foo())  // 类型是 C 类}fun main(arg:Array<String>){    printFoo(D())}// 输出c

若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。

扩展一个空对象

在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:

1
fun Any?.toString(): String {    if (this == null) return "null"    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()    // 解析为 Any 类的成员函数    return toString()}fun main(arg:Array<String>){    var t = null    println(t.toString())}

实例执行输出结果为:

1
null

扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。

1
val Foo.bar = 1 // 错误:扩展属性不能有初始化器

扩展属性只能被声明为 val。

伴生对象的扩展

伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。

如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。

伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:

1
class MyClass {    companion object { }  // 将被称为 "Companion"}fun MyClass.Companion.foo() {    println("伴随对象的扩展函数")}val MyClass.Companion.no: Int    get() = 10fun main(args: Array<String>) {    println("no:${MyClass.no}")    MyClass.foo()}

实例执行输出结果为:

1
no:10伴随对象的扩展函数

注:对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:

  • (1)类内扩展的伴随对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;
  • (2)当类内扩展的伴随对象函数和类外扩展的伴随对象同名时,类内的其它函数优先引用类内扩展的伴随对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;
  • (3)类内扩展的伴随对象函数只能被类内的函数引用,不能被类外的函数和伴随对象内的函数引用;
  • (4)类外扩展的伴随对象函数可以被伴随对象内的函数引用,;

Kotlin 对象表达式和对象声明

对象表达式

通过对象表达式实现一个匿名内部类的对象用于方法的参数中:

1
window.addMouseListener(object : MouseAdapter() {    override fun mouseClicked(e: MouseEvent) {        // ...    }    override fun mouseEntered(e: MouseEvent) {        // ...    }})

对象声明

Kotlin 使用 object 关键字来声明一个对象。

Kotlin 中我们可以方便的通过对象object声明来获得一个单例。

当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量。

1
class Site {    var name = "菜鸟教程"    object DeskTop{        var url = "www.runoob.com"        fun showName(){            print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量        }    }}fun main(args: Array<String>) {    var site = Site()    site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象    Site.DeskTop.url // 正确, 类似静态内部类,这个类是属于外部类的,而不是某个实例}

伴生对象

类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素。

对象表达式和对象声明之间的语义差异

对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行的
  • 对象声明是在第一次被访问到时延迟初始化的
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配

kotlin 委托

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

类委托

1
// 创建接口interface Base {       fun print()}// 实现此接口的被委托的类class BaseImpl(val x: Int) : Base {    override fun print() { print(x) }}// 通过关键字 by 建立委托类class Derived(b: Base) : Base by bfun main(args: Array<String>) {    val b = BaseImpl(10)    Derived(b).print() // 输出 10}

在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

1
import kotlin.reflect.KProperty// 定义包含属性委托的类class Example {    var p: String by Delegate()    // val/var <属性名>: <类型> by <表达式>}// 委托的类class Delegate {    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {        return "$thisRef, 这里委托了 ${property.name} 属性"    }    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {        println("$thisRef 的 ${property.name} 属性赋值为 $value")    }}fun main(args: Array<String>) {    val e = Example()    println(e.p)     // 访问该属性,调用 getValue() 函数    e.p = "Runoob"   // 调用 setValue() 函数    println(e.p)}

标准委托

Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。

延迟属性 Lazy

1
val lazyValue: String by lazy {    println("computed!")     // 第一次调用输出,第二次调用不执行    "Hello"}fun main(args: Array<String>) {    println(lazyValue)   // 第一次执行,执行两次输出表达式    println(lazyValue)   // 第二次执行,只输出返回值}/*computed!HelloHello*/

可观察属性 Observable

observable 可以用于实现观察者模式。

Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。

在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:

1
import kotlin.properties.Delegatesclass User {    var name: String by Delegates.observable("初始值") {        prop, old, new ->        println("旧值:$old -> 新值:$new")    }}fun main(args: Array<String>) {    val user = User()    user.name = "第一次赋值"    user.name = "第二次赋值"}/*旧值:初始值 -> 新值:第一次赋值旧值:第一次赋值 -> 新值:第二次赋值*/

把属性储存在映射中

Not Null

notNull 适用于那些无法在初始化阶段就确定属性值的场合。

1
class Foo {    var notNullBar: String by Delegates.notNull<String>()}foo.notNullBar = "bar"println(foo.notNullBar)

需要注意,如果属性在赋值前就被访问的话则会抛出异常。

局部委托属性

你可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:

1
fun example(computeFoo: () -> Foo) {    val memoizedFoo by lazy(computeFoo)    if (someCondition && memoizedFoo.isValid()) {        memoizedFoo.doSomething()    }}

memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。

属性委托要求

对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:

  • thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型

这个函数必须返回与属性相同的类型(或其子类型)。

对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:

property —— 必须是类型 KProperty<*> 或其超类型new value —— 必须和属性同类型或者是它的超类型。

提供委托

附:

Kotlin

构造函数

Kotlin 中构造函数分为主构造函数和**次构造函数,**主构造函数只能有一个,次构造函数个数不限制,可以有一个或者多个。

  • 主构造函数就是类后的小括号中定义的,会定义类有哪些数据成员并初始化。——然而那一些初始化的方法该怎么调用呢==> 在init{}中编写需要在主构造函数中完成的业务
  • 而次构造函数需要用constructor关键字来声明。

基础概念:

进阶:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Person(var name: String, var age: Int) {
// 次构造函数中传入了主构造函数中没有的数据成员的时候,需要在这里定义
var sex: Boolean?=null;

init {
println("main constructor init ${name}")
}

constructor(name:String, age: Int, sexy: Boolean): this(name, age){
println("constructor")
this.sex = sexy
println(this.sex)
}
}

class DataClassTest {
@Test
fun test2(){
val person = Person(name = "a", age = 12)
println(person.age)
println("________________________")
val person2 = Person(name = "b", age = 14, sexy = false)
println(person2.age)
}
}
/**
main constructor init a
12
________________________
main constructor init b
constructor
12
*/
  1. 默认情况下class是有不带参数的主构造函数, 当class具有有参的构造函数时, 无参的就会失效, 除非参数全有默认值。如果在定义了上述构造函数的情况下, 输入Person(), idea会提示报错,并提示如下Person类有如下构造函数。

img

但如果将主构造函数的参数全部设置为null,即class Person(var name: String? =null, var age: Int? = null), 那么就可以直接创建不带参数的对象了, var person0 = Person();

  1. 主构造函数的参数如果使用了var或val修饰符,就相当于在类中声明了对应名称的属性。

  2. Kotlin 中规定,当一个类既有主构造函数又有次构造函数时,所有次构造函数都必须使用this关键字直接或间接的调用主构造函数(间接指多层调用次构造函数):

  3. 调用顺序: ( 伴生对象成员变量初始化 -> 伴生对象的init )-> ( 主构造函数的参数赋值 -> init 代码块 ) -> 次构造函数代码块

Throwable kotlin demo:

1
2
3
4
5
6
7
public open class Throwable(open val message: String?, open val cause: Throwable?) {
constructor(message: String?) : this(message, null)

constructor(cause: Throwable?) : this(cause?.toString(), cause)

constructor() : this(null, null)
}

data class数据类

在 Kotlin 中,不需要自己动手去写一个 JavaBean,可以直接使用 DataClass,使用 DataClass 编译器会默默地帮我们生成以下函数

  • equals()
  • hashCode()
  • toString()
  • componentN()
  • copy()

定义一个: dataclassCountry(var id: Int,var name: String,var continent: String)

如何申明一个简单的数据类? 有一下几点要求:

  • 主构造函数必须要至少有一个参数

  • 在主构造函数中的所有参数必须被标记为val或者var (var就表示可读写,val就表示只读)

  • 数据类不能有以下修饰符:abstract,inner ,open, sealed ==》 数据类本身是不能被继承的 final

  • data class只能实现接口(Kotlin1.1以前的规则),现在也可以继承其它类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Document("daf_FiledDetail")
@ApiModel("字段类型表")
data class FiledDetail(
@ApiModelProperty("字段名称")
val name: String,

@ApiModelProperty("字段类型")
val type: FieldTypeEnum,

@ApiModelProperty("选项")
val choices: String,

@ApiModelProperty("字段分组")
val fieldGroup: String
): MongoTableBean()

class DataClassTest {
@Test
fun test(){
val filedDetail = FiledDetail("name", FieldTypeEnum.ADMINISTRATIVE, "YES", "NO")
println(filedDetail)
}
}

序列化

  • 序列化和反序列化其实就是方便传输对象.
  • 这里是需要在几个系统 Service 里通过 Bundle 传递数据.
  • Parcelable 全部都在内存,效率高,需要实现部分多,使用繁琐.(在 kotlin 不成立)
  • Serializable 实现简单,但是是基于反射实现的,故有性能损失,但是是最通用的.

Kotlin 中class、data class、object、companion object区别

data class:

kotlin中的数据类,只保存一些数据字段,类似于java bean,oc中的model。

1
2
3
4
5
data class yourClassName(			// 这边是小括号
val name: String,
val age: Int,
val sex: Boolean
)

object:

kotlin中使用"object"修饰静态类,可用于util工具类中。

  • 对象声明(object declaration)
    • 将类的声明和定义该类的单例对象结合在一起(即通过object就实现了单例模式)
    • 对象声明中不能包含构造器(包括主构造器和次级构造器)
  • 伴生对象(companion object)
  • 对象表达式(object expression)
1
2
3
4
5
6
object UserUtil {
val guoji = "中国"
fun getName(): String {
return "小红"
}
}

伴生对象(companion object)

因为在kotlin中是没有static关键字的,也就意味着没有静态方法和静态成员。那么在kotlin中如果想要表达这种概念,可以使用包级别函数(package-level funcation)和伴生对象(companion object)。

伴生对象语法形式

1
2
3
4
5
6
class ClassName {
// 伴生对象名可以省略,默认为Companion
companion object 伴生对象名 {
// define field and method
}
}

相应结果封装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.sucsoft.dispatch.utils

import com.sucsoft.daf.enums.OperationEnum

/**
* @Author: Mrli
* @Description: 结果返回封装类
* @Date: 2021/8/6 13:43
* @Version: 1.0
*/
class Result(var data: Any? = null, var code: Int? = null, var message: String? =null) {
companion object{
fun of(data: Any, code: Int, message: String): Result {
return Result(data, code, message)
}

fun make(data: Any?, op: OperationEnum): Result {
return if (null == data) this.err(op.cn) else this.ok(data)
}

fun ok(data: Any): Result {
return of(data, 200, OperationEnum.SUCCESS.cn)
}

fun err(msg: String): Result {
return of({}, 500, msg)
}

fun badRequest(): Result {
return of({}, 400, OperationEnum.PARAM_ERROR.cn)
}

fun serverError(): Result {
return of({}, 500, OperationEnum.SERVER_ERROR.cn)
}
}
}

踩坑:

  • swagger里面不填时,对应传的值为空, 因此需要对应接口的参数类型上是否能接受空?, (即比在写swagger(required)上比java对写个对类型的限制),

image-20210817152123083

  • data class 中字段的定义, 如果需要默认值则sortNum : Int = 0, 而对于String的话,如果是不可为空写成 id :String,即可,如果为空则定义为id: String?, 至于之后的默认值还是根据需求来看。

?. 和?:的区分

code1?.code2 翻译为 :if code1 Not Null 执行 code2;
code1?:code2 翻译为:if code1 Null 执行 code2;
?. 翻译为: If Not Null
?: 翻译为: If Null

注意点:

【?.】后面的方法是前者的相关调用
【?:】后面的方法与前者的无关调用

?.表示当前对象如果为空则不执行,
!!.表示当前对象如果为空也执行,然后会抛出空异常

open

在 Java 中,一个类除了被手动加上 final 关键字以外,它总能能被任意一个类继承并重写它的非 final 方法,这就可能会导致某些子类出现不符合其父类的设计初衷,特别是在多人协作的开发环境下。

这类问题被 Kotlin 语言设计者注意到了并切引起了他们的重视,因此,在 Kotlin 中的类和方法默认都是 final 的,如果要继承或者重写一个类和其方法时,必须将他们都显式地声明为 open , 成员变量不需要,会自动认为是open 的。

1
2
3
4
5
6
7
8
9
10
11
12
open class Person2{
var name : String = ""
constructor(name: String){
this.name = name
}
constructor(){
}

open fun saySomething(){
println("yes")
}
}

抛弃 Java 改用 Kotlin 的六个月后,我后悔了——Kotlin特性有哪些?

Author: Mrli

Link: https://nymrli.top/2021/09/13/学点Kotlin/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
ZJU云原生技术及应用-课程笔记
NextPost >
ZJU开学摸底考试——操作系统复习
CATALOG
  1. 1. Kotlin 基础语法
    1. 1.1. 函数定义
      1. 1.1.1. 可变长参数函数
      2. 1.1.2. lambda(匿名函数)
    2. 1.2. NULL检查机制
  2. 2. Kotlin 基本数据类型
    1. 2.1. 变量和常量
    2. 2.2. 字符串模板
  3. 3. Kotlin 条件控制
  4. 4. Kotlin 循环控制
    1. 4.1. 返回和跳转
    2. 4.2. Break 和 Continue 的跳转标签
      1. 4.2.1. 标签处返回
  5. 5. Kotlin 类和对象
    1. 5.1. 类的定义
    2. 5.2. 对象的声明
    3. 5.3. 构造函数
    4. 5.4. getter 和 setter
    5. 5.5. 抽象类
    6. 5.6. 嵌套类
      1. 5.6.1. 内部类
      2. 5.6.2. 匿名内部类
    7. 5.7. 类的修饰符
  6. 6. Kotlin 继承
  7. 7. 构造函数
    1. 7.1. 子类有主构造函数
    2. 7.2. 子类没有主构造函数
    3. 7.3. 重写
    4. 7.4. 属性重写
  8. 8. Kotlin 接口
    1. 8.1. 接口定义
    2. 8.2. 实现接口
  9. 9. Kotlin 扩展
    1. 9.1. 将函数扩展
      1. 9.1.1. 扩展函数是静态解析的
    2. 9.2. 扩展一个空对象
    3. 9.3. 伴生对象的扩展
  10. 10. Kotlin 对象表达式和对象声明
  11. 11. 对象表达式
    1. 11.1. 对象声明
    2. 11.2. 伴生对象
    3. 11.3. 对象表达式和对象声明之间的语义差异
  12. 12. kotlin 委托
    1. 12.1. 类委托
    2. 12.2. 属性委托
    3. 12.3. 标准委托
      1. 12.3.1. 延迟属性 Lazy
      2. 12.3.2. 可观察属性 Observable
      3. 12.3.3. 把属性储存在映射中
      4. 12.3.4. Not Null
      5. 12.3.5. 局部委托属性
    4. 12.4. 属性委托要求
    5. 12.5. 提供委托
  • 附:
    1. 1. Kotlin
      1. 1.1. 构造函数
        1. 1.1.1. Throwable kotlin demo:
      2. 1.2. data class数据类
    2. 2. 序列化
  • Kotlin 中class、data class、object、companion object区别
    1. 0.1. data class:
    2. 0.2. object:
    3. 0.3. 伴生对象(companion object)
    4. 0.4. 相应结果封装类:
    5. 0.5. 踩坑:
    6. 0.6. ?. 和?:的区分
  • 1. open
  • 抛弃 Java 改用 Kotlin 的六个月后,我后悔了——Kotlin特性有哪些?