Kotlin-类和对象

发布于:2025-05-14 ⋅ 阅读:(11) ⋅ 点赞:(0)

class Student {
}

这是一个类,表示学生,怎么才能给这个类添加一些属性(姓名,年龄…)呢?

主构造函数

我们需要指定类的构造函数。构造函数也是函数的一种,但是它专门用于对象的创建
Kotlin的类可以添加一个主构造函数和一个或多个次要构造函数。主构造函数是类定义的一部分,像下面这样编写:

class Student constructor(name: String, age: Int) {
    
}

如果主构造函数没有任何注释或可见性修饰符,则可以省略constructor关键字,如果类中没有其他内容要写,也可以直接省略{}, 像下面这样:

class Student (name: String, age: Int)

但是,这里仅仅是定义了构造函数的参数,还不是类的属性

仅在对象初始化时使用构造函数参数,并且不想让这些参数以属性的形式被外部访问时,可采用这种写法,那我们要怎么才能定义类的属性呢?

可以为这些参数添加var(可变)或val(不可变)关键字来表示属性:

class Student (var name: String,val age: Int)

这样才算是定义了类的属性,我们也可以给这些属性设置初始值:

class Student (var name: String = "zhangsan",val age: Int = 20)

因为是构造函数, 实例化的时候会传入值, 所以可以给也可以不给初始值

如果将这些属性直接作为类的成员变量写到类里面,必须配初始值,否则无法通过编译,这样我们不用编写主构造函数也能定义属性,这里仍会隐式生成一个无参的构造函数:

class Student  {
    var name: String = "zhangsan"
    val age: Int = 20
}

也可以写成这样:

class Student(name : String, age: Int) {
    var name: String = name
    val age: Int = age
}

这样不是多此一举嘛,直接在括号里(主构造函数)定义属性不就好了,这样的好处就是可以自定义属性的getset方法

class Shape(var width:Int, var height:Int) {
    val area: Int
//      get() {
//          return width * height
//      }
        get() = width * height
}

fun main() {
    var a = Shape(2, 3)
    println("Width: ${a.width}, Height: ${a.height}")
    println("Area: ${a.area}")
}

在这里插入图片描述

class Shape(width:Int, height:Int) {
    val area: Int
        init {
            area = width * height
        }
    val perimeter: Int
        init {
            perimeter = (width + height) * 2
        }
}

当然,如果不希望一开始就有初始值,而是之后某一个时刻(用它之前)去设定初始值, 我们也可以为其添加懒加载(用它之前给属性初始值):

lateinit 修饰符:

  1. 只能用于可变属性(即 var 声明的属性),而不能用于只读属性(使用 val 声明的属性)
  2. 不能用于基本数据类型的属性。
class Student {
    lateinit var name: String
    val age: Int = 0
}

次要构造函数

除了直接使用主构造函数创建对象外,我们也可以添加一些次要构造函数,次要构造函数中的参数仅仅表示传入的参数,不能像主构造函数那样定义属性:

  • 如果该类有一个主构造函数,则每个次要构造函数都需要直接或间接委托给主构造函数。委托到同一个类的另一个构造函数是 this 关键字完成的
class Student(var name: String, var age: Int) {
    //这里的 this 表示当前这个类, this() 就是调用当前类的无参构造函数
    //这里其实是调用主构造函数,并且参数只有name,年龄直接给默认值18
    constructor(name: String) : this(name, 18) {
        println("次要构造函数")
    }
}

fun main() {
    var stu = Student("小明")
}

在这里插入图片描述

  • 如果一个类没有主构造函数,那么我们也可以直接在类中编写次要构造函数,:
class Student {
    var name: String
    var age: Int

    //在次要构造函数的参数前使用 var 或者 val 是不被允许的
    //次要构造函数可以编写自定义的函数体
    constructor(name: String, age: Int) { //这里的参数不是类属性,仅仅是形参
        this.name = name
        this.age = age
    }
}

总结

  • 主构造函数:可以直接定义类属性,使用更方便,但主构造函数只能存在一个,并且无法编写函数体,不过可以用init编写,下面对象初始化有介绍
  • 次要(辅助)构造函数:可以存在多个,并且可以自定义函数体,但是无法像主构造函数那样定义类属性,并且当类具有主构造函数时,所有的次要构造函数必须直接或间接地调用主构造函数

对象

构造函数也是函数,我们可以用类名()的形式创建对象

class Student(var name: String, var age: Int)

fun main() {
    var stu: Student = Student("小明", 18)
    println(stu.name)
    println(stu.age)
    stu.name = "小红"
    println(stu.name)
}

在这里插入图片描述

初始化

在创建对象时,我们可能需要做一些初始化工作,可以使用初始化代码块(使用init关键字)来完成。假如我们希望对象在创建时, 年龄不足18岁就设定为18岁:

注意:我们在创建对象的时候,就会自动执行init里面的代码

class Student (var name: String, var age: Int){
    init {
        println("我是初始化操作")
        if (age < 18) age = 18
    }
}

fun main() {
    var stu = Student("小明", 2)
    println(stu.age)
}

在这里插入图片描述
初始化操作可以有很多个:

class Student (var name: String, var age: Int){

    //多个初始化操作时,按从上往下的顺序执行
    init {
        if (age < 18) age = 18
    }

    init {
        age = 20
    }
}

fun main() {
    var stu = Student("小明", 2)
    println(stu.age)
}

在这里插入图片描述
如果初始化代码要用成员属性, 那就要在用之前先赋值

class Student {

    init {
        println("name is $name")
        println("age is $age")
    }

    var name: String = "Student"
    var age: Int = 20
}

fun main() {
    var stu = Student()
}

在这里插入图片描述
这里需要注意一下,次要构造函数实际上需要先执行主构造函数,所以会优先执行init

class Student (var name: String, var age: Int){

    init {
       println("我是初始化代码块")
    }
    constructor(name: String) : this(name, 18){
        println("我是次要构造函数的语句")
    }
}

fun main() {
    var stu = Student("张三")
}

在这里插入图片描述

类的继承

在Kotlin中, 默认情况下, 类是"终态"的(不能被任何类继承)。要使类可继承,要用open关键字标记需要被继承的类

在Kotlin中只能单继承。需要注意的是,在对象创建并初始化的时候, 会优先对父类进行初始化,再对子类进行初始化

open class Student {
    init {
        println("父类初始化")
    }
    fun hello() = println("打招呼")
}

class ArtStudent: Student() {
    init {
        println("子类初始化")
    }
    fun draw() = println("我会画画")
}

fun main() {
    val student = ArtStudent()
    student.draw()
    student.hello()
}

在这里插入图片描述

成员函数

现在我们的类有了属性,而对象也可以做出一些行为,我们可以通过定义函数来实现

class Student(var name: String, var age: Int) {

    fun hello() {
        println("大家好, 我是$name")
    }
}

fun main() {
    val student = Student("路飞", 20)
    student.hello()
}

在这里插入图片描述
如果函数中的变量存在歧义,那么优先使用作用域最近的一个

class Student(var name: String, var age: Int) {

    fun hello(name: String)  {
        println("大家好, 我是$name")
    }
}

fun main() {
    Student("路飞", 20).hello("比企谷八幡")
}

在这里插入图片描述
如果我们需要获取的是类中的成员属性,需要使用this关键字来表示

class Student(var name: String, var age: Int) {

    fun hello(name: String)  {
        println("大家好, 我是${this.name}")
    }
}

fun main() {
    Student("路飞", 20).hello("比企谷八幡")
}

在这里插入图片描述

属性覆盖(重写)

有些时候,我们希望子类继承父类的某些属性,但是又希望去修改这些属性的默认实现

我们可以使用 override 关键字表示对一个属性的覆盖(重写)

open class Student {
    // 函数必须添加open关键字才能被子类覆盖
    open fun hello() = println("打招呼")
}

class ArtStudent: Student() {
    // 在子类中重写方法要添加 override 关键字
    override fun hello() {
        println("呀哈喽")
        super.hello()
    }
}

fun main() {
    val artStudent = ArtStudent()
    artStudent.hello()
}

在这里插入图片描述
同样的, 类的某个变量也是可以进行覆盖的

open class Student {
    open val name: String = "小明"
}

class ArtStudent: Student() {
    override val name: String = "小红"
}

fun main() {
    val artStudent = ArtStudent()
    println(artStudent.name)
}

在这里插入图片描述
对于可变的变量,也可以这样不加open

open class Student {
    var name: String = "小明"
}

class ArtStudent: Student() {
    init {
        name = "小红"
    }
}

fun main() {
    val artStudent = ArtStudent()
    println(artStudent.name)
}

在这里插入图片描述
也可以在子类的主构造函数中直接覆盖

open class Student {
    open var name: String = "小明"
}

class ArtStudent(override var name: String): Student()

fun main() {
    val artStudent = ArtStudent("小王")
    println(artStudent.name)
}

在这里插入图片描述

open class Student {
    open var name: String = "小明"
}

class ArtStudent(override var name: String): Student() {
    init {
        name = "小红"
    }
}

fun main() {
    val artStudent = ArtStudent("小王")
    println(artStudent.name)
}

在这里插入图片描述
这种初始化顺序要特别注意

open class Student {
    open var name: String = "小明"
    init {
        println(name.length) // 这里拿到的name其实是还未初始化的子类的name
    }
}

class ArtStudent(override var name: String): Student()

fun main() {
    val artStudent = ArtStudent("小王")
    println(artStudent.name)
}

由于父类初始化在子类之前,此时子类还没有初始化,其覆盖的属性此时没有值,在JVM平台下,没有初始化的对象引用默认为null,那么这里就会出现空指针异常

在这里插入图片描述
name明明是一个不可空的String类型,还会出现空指针异常。因此,对于使用了open关键字的属性只要是在初始化函数、构造函数中使用,要额外小心

智能转换

编译器可以根据当前的语境自动进行类型转换
在这里插入图片描述
不仅仅是if判断的场景,还包括when、while 以及 && || 等

open class Student

class ArtStudent: Student() {
    fun draw(): Boolean = true
}

fun main() {
    val student: Student = ArtStudent()
    while (student is ArtStudent) student.draw()
    // 很明显如果前面为真,那么肯定是 ArtStudent 类型, 后面可以智能转换
    if (student is ArtStudent && student.draw()) ;
}

可空类型同样支持这样的智能转换

class Student {
    fun hello() = println("Hello World")
}

fun main() {
    val student: Student? = Student()
    student?.hello()
    if (student != null) {
        student.hello() //根据语境将student从Student?智能转换为Student
    }
}

在处理可空类型时,为了防止出现异常,我们可以使用更加安全的as?运算符

open class Student

class ArtStudent: Student()

fun main() {
    val student: Student? = null
    student as? ArtStudent //当student为null时,不会抛出异常,而是返回null
}

类的扩展

Kotlin提供了扩展类或接口的操作来为其添加额外的函数或属性,无需通过类继承或者使用装饰器等设计模式

比如我们想为String类型添加一个自定义操作

fun String.test() = "hello world"

fun main() {
    val str = ""
    println(str.test())
}

在这里插入图片描述
注意,类的扩展是静态的,实际上并不会修改原本的类,也不会将新成员插入到类中,仅仅是将我们定义的功能变得可调用,像真的有一样。同时,在编译时也会明确具体调用的扩展函数:

open class Shape

class Rectangle : Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printShape(shape: Shape) {
    println(shape.getName())
}

fun printRectangle(rectangle: Rectangle) {
    println(rectangle.getName())
}

fun main() {
    printShape(Rectangle())
    printRectangle(Rectangle())
}

在这里插入图片描述

如果类本身就具有同名同参的函数,那么扩展函数将失效:

class Test {
    fun hello() = println("你干嘛")
}

fun Test.hello() = println("哎呦")

fun main() {
    Test().hello()
}

在这里插入图片描述
不过,重载是没问题的

class Test {
    fun hello() = println("你干嘛")
}

fun Test.hello(str: String) = println(str)

fun main() {
    Test().hello("哎呦")
}

在这里插入图片描述
同样的,类的属性也是可以通过这种形式来扩展的,但是有一些小小的要求
在这里插入图片描述
可以看到直接扩展属性是不允许的,扩展并不是真的往类中添加属性。因此, 扩展属性本质上也不会真的插入一个成员字段到类的定义中,这就导致并没有变量去存储我们的数据, 我们只能明确定义get和set来创建扩展属性

class Test 

val Test.name
    get() = "666"

fun main() {
    println(Test().name)
}

在这里插入图片描述

由于扩展属性并没有存储真正的变量,而是使用get和set函数,所以,像field这样的后备字段就无法使用了

还有需要注意的是,我们定义的扩展属性,同样受到访问权限控制

在这里插入图片描述
除了直接在顶层定义类的扩展外,我们也可以在类中定义其他类的扩展,并且在定义时可以直接使用其他类提供的属性

class A {
    val name = "张三"
}

class B {
    //像这种扩展,由于是在类中定义,因此也仅限于类内部使用
    fun A.test() = println(this.name)

    fun test() = A().test()
}

fun main() = B().test()

在这里插入图片描述
在函数名发生冲突的时候,需要特别处理

class A {
    fun hello() = println("Hello A")
}

class B {
    //像这种扩展,由于是在类中定义,因此也仅限于类内部使用
    fun A.test() {
        hello() //优先匹配被扩展类里的函数
        this.hello()
        this@B.hello()
    }

    fun hello() = println("Hello B")

    fun test() = A().test()
}

fun main() = B().test()

在这里插入图片描述
定义在类中的扩展也可以跟随类的继承结构,进行重写

open class A {
    open fun A.test() = "AAA"

    fun hello() = println(test())
}

class B:A() {
    override fun A.test() = "BBB" //对父类定义的扩展函数进行重写
}

fun main() {
    A().hello()
    B().hello()
}

在这里插入图片描述

我们可以在某个函数里面编写扩展,但作用域仅限于当前函数

fun main() {
    fun String.print() = println("此剑斩穹,不破不休")
    "".print()
}

还可以将一个扩展函数作为参数给到一个函数类型变量

fun main() {
	// func就是扩展函数名
    val func: String.(Int) -> String = { it.toString() + this }
    println("出击!".func(123))
    //如果是直接调用,那就必须传入对应类型的对象作为首个参数,此时this就指向我们传入的参数
    println(func("撤退!", 321)) 
}

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到