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 = name val age: Int = age }
这样不是多此一举嘛,直接在括号里(主构造函数)定义属性不就好了,这样的好处就是可以自定义属性的get和set方法
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 修饰符:
- 只能用于可变属性(即 var 声明的属性),而不能用于只读属性(使用 val 声明的属性)
- 不能用于基本数据类型的属性。
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
初始化操作可以有很多个:
class Student (var name: String, var age: Int){ //多个初始化操作时,按从上往下的顺序执行 init { if (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)) }
- 如果一个类没有主构造函数,那么我们也可以直接在类中编写次要构造函数,: