Kotlin 2.1.0 入门教程(十四)类、构造函数、对象、抽象类

Kotlin 中,类使用 class 关键字进行声明:

kotlin 复制代码
class Person { /*...*/ }

类声明由类名、类头(指定其类型参数、主构造函数以及其他一些内容)和用花括号括起来的类体组成。类头和类体都是可选的;如果类没有类体,则可以省略花括号。

kotlin 复制代码
class Empty

构造函数

Kotlin 中,一个类有一个主构造函数,可能还有一个或多个次构造函数。

主构造函数在类头中声明,它跟在类名和可选的类型参数之后。

kotlin 复制代码
class Person constructor(firstName: String) { /*...*/ }

如果主构造函数没有任何注解或可见性修饰符,那么 constructor 关键字可以省略:

kotlin 复制代码
class Person(firstName: String) { /*...*/ }

主构造函数用于在类头中初始化类的实例及其属性。类头中不能包含任何可执行代码。如果你想在对象创建期间运行一些代码,可以在类体中使用初始化块。初始化块使用 init 关键字声明,后面跟着花括号。你可以将任何想要运行的代码写在花括号内。

在实例初始化期间,初始化块会按照它们在类体中出现的顺序执行,并且会与属性初始化器穿插执行。

kotlin 复制代码
class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints $name")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

fun main() {
    // First property: demo
    // First initializer block that prints demo
    // Second property: 4
    // Second initializer block that prints 4
    InitOrderDemo("demo")
}

主构造函数的参数可以在初始化块中使用。它们也可以在类体中声明的属性初始化器中使用:

kotlin 复制代码
class Customer(name: String) {
    val customerKey = name.uppercase()
}

Kotlin 拥有一种简洁的语法,可用于声明属性并通过主构造函数对其进行初始化:

kotlin 复制代码
class Person(val firstName: String, val lastName: String, var age: Int)

此类声明还可以包含类属性的默认值:

kotlin 复制代码
class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)

和普通属性非常相似,在主构造函数中声明的属性可以是可变的(使用 var),也可以是只读的(使用 val)。

如果构造函数带有注解或可见性修饰符,那么就需要使用 constructor 关键字,并且这些修饰符要放在该关键字之前:

kotlin 复制代码
class Customer public @Inject constructor(name: String) { /*...*/ }

次构造函数

一个类还可以声明次构造函数,次构造函数要以 constructor 作为前缀:

kotlin 复制代码
class Person(val pets: MutableList<Pet> = mutableListOf())

class Pet {
    constructor(owner: Person) {
        owner.pets.add(this)
    }
}

fun main() {
    val person = Person()
    val pet1 = Pet(person)
    val pet2 = Pet(person)
    println(person.pets.size) // 2
}

如果类有主构造函数,每个次构造函数都需要直接或间接地通过其他次构造函数委托给主构造函数。委托给同一个类的其他构造函数使用 this 关键字来完成:

kotlin 复制代码
class Person(val name: String) {
    val children: MutableList<Person> = mutableListOf()

    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

初始化块中的代码实际上会成为主构造函数的一部分。对主构造函数的委托会在访问次构造函数的第一条语句时发生,因此所有初始化块和属性初始化器中的代码会在次构造函数体执行之前执行。

kotlin 复制代码
class InitOrderDemo(name: String) {
    val firstProperty = "111: $name".also(::println)
    
    init {
        println("222: $name")
    }
    
    val secondProperty = "333: ${name.length}".also(::println)
    
    init {
        println("444: ${name.length}")
    }
    
    constructor(i: Int, name: String) : this(name) {
        println("555: $i, $name")
    }
}

fun main() {
    // 111: demo
    // 222: demo
    // 333: 4
    // 444: 4
    // 555: 1, demo
    InitOrderDemo(1, "demo")
    
    // 111: demo
    // 222: demo
    // 333: 4
    // 444: 4
    InitOrderDemo("demo")
}

即使类没有主构造函数,委托仍然会隐式发生,并且初始化块仍然会执行:

kotlin 复制代码
class Demo {
    init {
        println("111")
    }
    
    constructor(i: Int) {
        println("222")
    }
}

fun main() {
    // 111
    // 222
    Demo(1)
}

如果一个非抽象类没有声明任何构造函数(主构造函数或次构造函数),它将生成一个无参数的主构造函数。该构造函数的可见性将是公共的(public)。

如果你不希望你的类拥有 public 构造函数,可以声明一个具有非默认可见性的空主构造函数,示例如下:

kotlin 复制代码
class DontCreateMe private constructor() { /*...*/ }

在上述示例中,DontCreateMe 类的构造函数被声明为私有(private),这意味着该类的构造函数只能在类的内部被调用,外部代码无法直接创建 DontCreateMe 类的实例。

JVM 平台上,如果主构造函数的所有参数都有默认值,编译器会生成一个额外的无参构造函数,该构造函数会使用这些默认值。这使得 Kotlin 在与像 JacksonJPA 这样通过无参构造函数创建类实例的库一起使用时更加方便。

kotlin 复制代码
class Example(val param1: Int = 0, val param2: String = "")

编译器会额外生成一个无参构造函数,使用 param1 的默认值 0param2 的默认值 "" 来创建 Example 类的实例,这样 JacksonJPA 这类库就能顺利地创建 Example 类的实例了。

创建类的实例

要创建一个类的实例,就像调用普通函数一样调用构造函数。你可以将创建的实例赋值给一个变量:

kotlin 复制代码
val invoice = Invoice()

val customer = Customer("Joe Smith")

Kotlin 没有 new 关键字。

抽象类

可以将类以及它的部分或全部成员声明为抽象的。抽象成员在其所在类中没有具体的实现。而且,不需要使用 open 关键字来标注抽象类或抽象函数。

kotlin 复制代码
abstract class Polygon {
    abstract fun draw()
}

class Rectangle : Polygon() {
    override fun draw() {
        // 绘制矩形的具体实现。
    }
}

在上述代码中,Polygon 是一个抽象类,其中的 draw 方法是抽象方法,没有具体的实现代码。Rectangle 类继承自 Polygon 类,并且必须重写 draw 方法,提供绘制矩形的具体逻辑。

还可以使用抽象成员来重写非抽象的开放成员。

kotlin 复制代码
open class Polygon {
    open fun draw() {
        // 一些默认的多边形绘制方法。
    }
}

abstract class WildShape : Polygon() {
    // 继承自 WildShape 的类需要提供自己的 draw 方法,
    // 而不是使用 Polygon 中的默认方法。
    abstract override fun draw()
}

在这个例子中,Polygon 类有一个开放的 draw 方法,有默认的实现。WildShape 类继承自 Polygon 类,使用 abstract overridedraw 方法进行重写,将其变成抽象方法。这意味着任何继承 WildShape 类的子类都必须提供 draw 方法的具体实现,而不能再使用 Polygon 类里的默认实现了。

相关推荐
crazymaple2131 小时前
Flutter编译运行android问题之JVM版本问题
android·jvm·flutter
新知图书2 小时前
ThinkPHP8视图赋值与渲染
android·ide·android studio
众智创新团队2 小时前
Android的Activity生命周期知识点总结,详情
android·java·开发语言
etcix2 小时前
安卓谷歌地图api一个文件例子(备忘)
android
yzpyzp3 小时前
kotlin-kapt
android·kotlin
B.-3 小时前
Flutter 中的生命周期
android·前端·flutter·ios
xvch4 小时前
Kotlin 2.1.0 入门教程(十六)属性、getter、setter、幕后字段、后备属性、编译时常量、延迟初始化
android·kotlin
闲暇部落4 小时前
kotlin中expect和actual关键字修饰的函数作用
android·开发语言·kotlin
Sruur4 小时前
Kotlin Lambda
java·kotlin