Kotlin-面向对象之构造函数、实例化和初始化
构造函数
主构造函数:
主构造函数直接在class头部用小括号定义
class Player(_name:String, _healthPoints:Int, _isBlessed:Boolean, _isImmortal:Boolean=false)
{}
变量下划线前缀用来表示临时变量
主构造函数支持直接定义属性
class Player(name:String, var healthPoints:Int, val isBlessed:Boolean, private val isImmortal:Boolean)
构造函数内定义的属性,只能使用默认getter和setter,无法自定义
次构造函数:
次构造函数对应主构造函数
词构造函数要么直接调用主构造函数(传入它需要的值参),要么通过调用另一个次构造函数简介调用主构造函数
class Player(_name:String, _healthPoints:Int, _isBlessed:Boolean, _isImmortal:Boolean=false)
{
constructor(name:String) : this(
name,
_healthPoints = 100,
_isBlessed = true,
// 如果主构造函数定义的属性有默认值,那么次构造函数可以不用给它传值
//_isImmortal=false // 这里与默认值相同,所以可以省略
) // 定义次构造函数
}
一个class允许多个次构造函数
次构造函数需要满足主构造函数的条件
词构造函数支持定义初始化代码逻辑
比如山本作为boss,血条应该很满
class Player(_name:String, _healthPoints:Int, _isBlessed:Boolean, _isImmortal:Boolean=false)
{
constructor(name:String) : this(
name,
_healthPoints = 100,
_isBlessed = true,
// 如果主构造函数定义的属性有默认值,那么次构造函数可以不用给它传值
//_isImmortal=false // 这里与默认值相同,所以可以省略
){
if(name.uppercase()=="SHANBEN") healthPoints=300
}
}
次构造函数不能像主构造函数那样定义雷属性,
实例化:
调用一个类的构造函数,就创建了一个该类的实例,这个过程叫做实例化
kotlin实例化一个class kotlin实例化类不需要new关键字
val player = Player("YunLong Li", 88, true, false)
因为在class中定义了次构造函数,并且在次构造函数中传入了几个参数
所以这里实例化class时只需要传入name就可以了
val player = Player("ShanBen")
初始化:
初始化变量、属性或类实例,就是给它赋初始值。
初始化块:
初始化块用于设置变量或值,以及执行有效性检查
比如检查传给构造函数的值是否有效等
初始化代码块会在构造类实例时执行
定义初始化块使用init{}
init{
require(healthPoints > 0, { "healthPoints must be greater than zero." }) // 初始化块检查属性时,必须在被检查属性定义后
require(name.isNotBlank(), { "Player must have a name." })
}
属性初始化:
属性必须在构造类实例时完成初始化
属性初始化值可以是包括函数返回在内的任意类型匹配值
// val hometown: String = selectHometown().shuffled().first()
*如果某个属性需要复杂的初始化逻辑,应该将其考虑放到函数或者初始化块中处理
val hometown:String by lazy { selectHometown()}
private fun selectHometown():String = listOf("BeiJing","ShanXi").shuffled().first()
属性必须初始化这一要求并不适用于函数这种作用域较小的变量
比如下面这个
private fun isHero(){
val hero:Boolean
hero=true
}
hero变量在被引用前会完全赋值,所以代码能正常运行
但是kotlin对属性有严格初始化要求,因为如果属性所在的类是public,其他类都有可能使用它们
而函数的作用于仅局限于函数内部,外部代码并不会接触到它们
所以这个hero无效的
属性初始化方式有很多: 1、在主构造函数中初始化 2、在声明时初始化 3、在次构造函数中初始化 4、在初始化块中初始化 既然函数有这么多初始化方式,同一属性就有可能会在很多个地方被调用, 所以初始化顺序就非常重要 属性初始化顺序图: 1、主构造函数中声明的属性 2、类级别的属性赋值 3、init初始化块中的属性赋值和函数调用 4、次构造函数中的属性赋值和函数调用 不过,初始化块init{}和类级别的属性赋值顺序取决于定义的先后。 因此如果要在初始化块中初始化属性值。需要先定义被初始化的属性 因为java基本类型int的默认值是0,所以如果属性int类型值为0,那么编译器为了优化初始化代码,就会忽略该属性
延迟初始化:
无论何时,构建类实例时,类属性都必须完成初始化 这是kotlin非空安全系统的重要组成部分 这意味着,当类的构造函数被调用时,class中所有非空属性都能以非空值完成初始化 也就是说 当某个类一旦完成实例化,就可以直接调用实例对象中的任何属性 延迟初始化就是为了规避这条规则而产生的 因为如何调用构造函数、或者什么时候调用,有时很难掌控 比如在class中定义了一个window界面属性 而界面内容需要其它属性数据支撑,这种情况就需要延时初始化 延时加载关键字 lateinit
lateinit var alignment:String // 定义一个延时初始化属性
对于任何var属性声明,都可以加上lateinit关键字 这样编译器就会允许延后初始化属性 需要注意的是,必须在属性引用前初始化变量,否则就会抛出初始化异常 延迟初始化变量一旦完成初始化后,就跟其它变量咪什么区别了
fun determineFate(){
alignment = "Good"
}
fun proclaimFate(){
// kotlin提供了一个isInitialized检查延迟初始化变量是否完成初始化
if(::alignment.isInitialized) println(alignment)
}
不过建议少用isInitialized检查,
因为如果对于每个延迟初始化变量都检查一遍,那么这个强大的功能就显得跟可空性变量没什么区别了
惰性初始化:
惰性初始化可以看做是延迟初始化的一个补充 惰性初始化可以暂时不初始化某个变量 直到首次使用它 惰性初始化能让代码执行更有效率 例如,在某个复杂的class中,往往需要实例化多个对象,以及读取文件这样的计算密集型任务 当某个属性需要触发大量这类任务,但该属性又暂时没有class需要用到它时,惰性加载就非常有用了 惰性初始化在kotlin中式使用代理机制来实现的 代理负责约定属性该如何初始化 使用代理需要用到by关键字 kotlin中有一些预先配置好的代理可用 比如lazy
val hometown:String by lazy {
selectHometown() // 当hometown首次被引用时,by lazy才会被初始化, lambda中的所有代码都会执行一次且仅执行一次,下次引用hometown时,直接使用缓存结果
}
惰性初始化同样需要在属性引用前初始化变量,否则就会抛出初始化异常
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "kotlin.Lazy.getValue()" because "<local1>" is null
惰性初始化虽然有用,但代码实现稍微繁琐,建议在处理计算密集型任务时使用
无论如何,在定义一个初始化块或者使用属性之前,一定要确保块中的所有属性已经完成初始化赋值
否则会抛出空指针异常
理论上讲,给对象分配内存就是实例化对象,给对象赋值就是初始化对象
但通常,实例化倾向于仅仅创建一个类的实例,而初始化则是指为变量、属性或类实例变得可用的工作