当前位置: 首页 > news >正文

Kotlin-类和对象

文章目录

    • 主构造函数
    • 次要构造函数
    • 总结
  • 对象
    • 初始化
  • 类的继承
  • 成员函数
  • 属性覆盖(重写)
    • 智能转换
  • 类的扩展

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 = nameval 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: Intinit {area = width * height}val perimeter: Intinit {perimeter = (width + height) * 2}
}

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

lateinit 修饰符:

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

次要构造函数

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

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

在这里插入图片描述

  • 如果一个类没有主构造函数,那么我们也可以直接在类中编写次要构造函数,:
class Student {var name: Stringvar age: Int//在次要构造函数的参数前使用 var 或者 val 是不被允许的//次要构造函数可以编写自定义的函数体constructor(name: String, age: Int) { //这里的参数不是类属性,仅仅是形参this.name = namethis.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 Studentclass 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 Studentclass ArtStudent: Student()fun main() {val student: Student? = nullstudent as? ArtStudent //当student为null时,不会抛出异常,而是返回null
}

类的扩展

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

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

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

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

open class Shapeclass 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.nameget() = "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)) 
}

在这里插入图片描述

相关文章:

  • TCP首部格式及三次握手四次挥手
  • 【学习笔记】Shell编程---流程控制语句
  • 【用「概率思维」重新理解生活】
  • 深入探讨 Java 性能术语与优化实践
  • 12.1寸工业液晶屏M121XGV20-N10显示单元技术档案
  • ubuntu22.04编译PX4无人机仿真实践
  • Git命令起别名
  • Cursor开发酒店管理系统
  • 【AI论文】健康的大型语言模型(LLMs)?——评估大型语言模型对英国政府公共健康信息的掌握程度
  • 什么是序列化与反序列化
  • Kubernetes 标签和注解
  • Unity
  • [ linux-系统 ] 进程概念与基本操作
  • 【大模型】DeepResearcher:通用智能体通过强化学习探索优化
  • 嵌入式STM32学习——外部中断EXTI与NVIC的基础练习⭐
  • 大便次数与寿命有关?
  • 通过SSRF击穿内网!kali-ssrf靶场实战!
  • 基于MNIST数据集的手写数字识别(简单全连接网络)
  • 蓝桥杯 16. 外卖店优先级
  • 抖音怎么快速涨粉(抖音推流算法研究)
  • 美国明尼苏达州发生山火,过火面积超80平方公里
  • 中国-拉共体论坛第四届部长级会议北京宣言
  • 王毅会见巴西外长维埃拉、总统首席特别顾问阿莫林
  • 警方通报:某博主遭勒索后自杀系自导自演,已立案调查
  • 泽连斯基批准美乌矿产协议
  • “海豚音”依旧,玛丽亚·凯莉本周来沪开唱