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
}
这样不是多此一举嘛,直接在括号里(主构造函数)定义属性不就好了,这样的好处就是可以自定义属性的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: Intinit {area = width * height}val perimeter: Intinit {perimeter = (width + height) * 2}
}
当然,如果不希望一开始就有初始值,而是之后某一个时刻(用它之前)去设定初始值, 我们也可以为其添加懒加载(用它之前给属性初始值):
lateinit 修饰符:
- 只能用于可变属性(即 var 声明的属性),而不能用于只读属性(使用 val 声明的属性)
- 不能用于基本数据类型的属性。
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))
}