一、构造函数
1、主构造函数
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。
主构造函数写在类名后面
class Student constructor(name: String){ }
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Student(name: String){ }
不能省略情况,例如:
class Student private constructor(name: String){ }
(这里主构造函数声明为private,相当于这个Student类没有公有构造函数)
主构造函数不能包含任何的代码。初始化的代码可以放到init初始化块中。
主构造的参数可以在init模块中使用,也可以给类中属性初始化时使用:
class Student2(name: String){
val stuName: String = name
init {
println("学生名称:$name")
}
}
实际上,kotlin有简洁的写法来声明属性以及在主构造函数中初始化属性:
class Student(val name: String,var age: Int){ }
这也是常用写法
2、次构造函数
使用constructor声明次构造函数,可以定义多个次构造函数来配置不同的参数组合。
每个次构造函数都要直接或间接委托给主构造函数
class Student(val name: String,var age: Int,var gender: String){
constructor(_name: String,_age: Int) : this(_name,_age,"男"){
}
constructor(_name: String):this(_name,18){
}
}
fun main() {
val student = Student("张三")
println("name: ${student.name},age: ${student.age},gender: ${student.gender}")
}
3、初始化顺序
在实例初始化时,初始化块中的代码和类属性初始化的执行顺序是按照它们在类体中的顺序来依次执行的,次构造函数的初始化代码会在它们之后执行。
class Student(_name: String,_gender: String){
val stuName: String = _name
val stuGender: String = _gender
init {
println("执行init初始化块")
}
var stuAge: Int = 10.also(::println)
constructor(_name: String):this(_name,"男"){
println("次构造函数初始化开始")
}
}
fun main() {
val student = Student("张三")
}
运行结果:
所以在使用初始化块时,一定要保证块中使用到的所有属性都已经初始化了。错误示范:
二、继承
kotlin的类默认是封闭的,要让某个类开放继承,必须使用open关键字修饰它。
无需在代码里显示指定,每一个类都会继承一个叫做Any的超类。
如果子类有一个主构造函数,其父类可以(并且必须) 用派生类主构造函数的参数就地初始化。
open class Animal(name: String){ }
class cat(name: String) : Animal(name){ }
如果子类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其父类类型,或委托给另一个构造函数做到这一点。
open class Animal(name: String){ }
class cat : Animal{
constructor(name: String) : super(name)
}
覆盖属性 与 覆盖方法
open class Animal{
open val isFly: Boolean = false
open fun eat(){ }
}
class Cat : Animal(){
override val isFly: Boolean = true
override fun eat() {
super.eat()
}
}
可以在主构造函数中使用 override 关键字作为属性声明的一部分
class Cat(override val isFly: Boolean = true) : Animal(){
override fun eat() {
super.eat()
}
}
可以用一个var属性覆盖一个val属性,反之则不行。因为val属性本质上只声明了get()方法,var覆盖val相当于在原来的基础上额外添加了一个set()方法。
三、单例类
使用object关键字,你可以定义一个只能产生一个实例的类——单例
object Singleton {
fun singletonTest(){
println("singleton is called.")
}
}
//使用单例类
Singleton.singletonTest()
四、对象表达式
object关键字还有另一个用途
当你只想要创建某个类的简单变体时,且只用一次,对于这种用完就丢的类实例,你可以这么来写:
open class KeyEvent(){
open fun keyDown(){}
}
fun main() {
//创建匿名类对象
val event = object : KeyEvent(){
override fun keyDown() {
super.keyDown()
println("按键被点击")
}
}
event.keyDown()
}
五、伴生对象
使用companion关键字可以在类里面声明对象,也就是声明伴生对象。一个类只能有一个伴生对象。
使用伴生对象可以让你像java调用静态方法的写法一样来调用伴生对象的方法。例如:
class Util {
fun doAction1() {
println("do Action1")
}
companion object {
fun doAction2() {
println("do Action2")
}
}
}
fun main() {
Util.doAction2()
}
虽然这看起来像静态方法的调用方式,但实际上doAction2()
方法并不是静态方法。使用companion object后,Kotlin会在 Util 类内部创建一个伴生类,doAction2()
就是这个类中的实例方法。Kotlin会保证 Util 类始终只会存在一个伴生类对象。所以Util.doAction2()
就是调用Util类中伴生类实例的doAction2()
方法。
六、数据类
数据类是专门设计用来存储数据的类。
使用data关键字来定义一个数据类。Kotlin会根据主构造函数中的参数自动对equals()、hashCode()、toString()等方法进行个性化实现。大大减少了开发的工作量。
//data关键字表明这是一个数据类
//Kotlin会根据主构造函数中的参数自动生成equals(),hashCode(),toString()方法
data class Book (val bookName: String,val price: Int)
使用数据类的条件:
- 数据类必须有至少带一个参数的主构造函数
- 数据类主构造函数的参数必须是val或var
- 数据类不能使用abstract、open、sealed和inner修饰符
七、可见性修饰符
Kotlin中有四种可见性修饰符,分别是public、private、protected和internal。
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。默认的可见性修饰符是public。
各可见性修饰符的含义:
修饰符 | 含义 |
---|---|
public | 所有类可见(默认) |
private | 当前类可见 |
protected | 当前类、子类可见 |
internal | 同一模块中的类可见 |