Kotlin系列七----属性和对象

本文详细探讨了Kotlin中的属性声明,包括可变与只读属性、getters和setters、幕后字段和幕后属性。此外,还介绍了延迟初始化属性与变量的规则。在对象部分,讲解了单例对象的声明方式,伴生对象的概念及其与普通对象表达式在初始化时机上的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

属性声明

Kotlin的类可以有属性。 属性可以用关键字var 声明为可变的,否则使用只读关键字val

class Address {
    var name: String = ……
    var street: String = ……
    var city: String = ……
    var state: String? = ……
    var zip: String = ……
}

Getters 与 Setters

声明一个属性的完整语法是

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。

例如:

var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter

一个只读属性的语法和一个可变的属性的语法有两方面的不同:1、只读属性的用 val开始代替var 2、只读属性不允许 setter

val simple: Int? // 类型 Int、默认 getter、必须在构造函数中初始化
val inferredType = 1 
在类中设定属性后,系统会默认分配get和set方法,若用户想在get和set之前执行相应的操作,则可以在属性的下方重写方法;
var name: String = "A"
  get() = "B"
  var age: Int = 25
  get() {
       println("get() AGE  Get")
       return if (field > 20) field else 100
        }
  var sex: String = "man"
  set(value) {
       println("set() SEX  Get")
       field = value
        }
调用上面的属性:
var get = Get()
println(get.age)
println(get.name)
println(get.sex)
get.show()
get.sex = "Women"
get.show()

幕后字段

在 Kotlin 类中不能直接声明字段。然而,当一个属性需要一个幕后字段时,Kotlin 会自动提供。这个幕后字段可以使用field标识符在访问器中引用,上面的例子中var sex: String = "man"中便使用了幕后字段:

var sex: String = "man"
  set(value) {
       println("set() SEX  Get")
       field = value
        }
注意:上述的赋值field = value 实际上会吧设定的值传递给Sex, field  标识符只能用在属性的访问器内。

幕后属性

如果你的需求不符合这套“隐式的幕后字段”方案,那么总可以使用 幕后属性(backing property)

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // 类型参数已推断出
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

从各方面看,这正是与 Java 相同的方式。因为通过默认 getter 和 setter 访问私有属性会被优化,所以不会引入函数调用开销。

延迟初始化属性与变量

一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检查。我们声明一个String:

class Test{
    var string : String //报错
}
使用lateinit()修饰对象
class Test{
   lateinit var string : String// 编译通过
}

lateinit只能用于var 修饰的对象,修饰的属性可以在任何地方初始化,在使用对象之前可以调用isInitialized检查是否初始化:

fun main(args: Array<String>) {
        if (::string.isInitialized) {
        }
    }

提到lateinit 可能就会想到另一个对象的延时初始化 by lazy()

  val s : String by lazy { "A" }

Kotlin lateinit 和 by lazy 的区别:
  • lazy{} 只能用在val类型, lateinit 只能用在var类型 
  • lateinit不能用在可空的属性上和java的基本类型上 
  • lateinit可以在任何位置初始化并且可以初始化多次。而lazy在第一次被调用时就被初始化,想要被改变只能重新定义

对象

对象的创建:kotlin中对象的创建不再需new的关键词,我们创建一个上文Test的对象
 var test = Test()
匿名创建:匿名创建使用默认属性object声明,在之前的文章中已经介绍了匿名的使用,这里直接写一下;
object : View.OnClickListener{
            override fun onClick(v: View?) {
            }
        }
有时当我们只是需要以对象使用,而并不在乎其类型会超类,那我们可以直接匿名创建对象:
fun main(args: Array<String>) {
        val ob = object {
            var x = 10
            var y = 15
        }
        ob.x = 20
    }

匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问。简单的说就是你把上文中的ob创建为类属性且为公有属性时,会发现其中声明的x,y无法使用。
public fun action() = object {//公有属性方法
        val  x = 100
    }
    
    private fun privateAction() = object {//私有方法属性
        val y = 200
    }
action().x   /报错: 无法使用x
privateAction().y  // OK

对象声明

单例模式是一种非常有用的模式,而 Kotlin(继 Scala 之后)使单例声明变得很容易,并且它总是在 object 关键字后跟一个名称。 就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。

object DefaultManger{
    fun defaultPrint(string: String){
        println(string)
    }
}

对单例的使用:

 DefaultManger.defaultPrint("=======")

这些对象可以有超类型:
object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ……
    }


    override fun mouseEntered(e: MouseEvent) {
        // ……
    }
}



注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。

伴生对象

类内部的对象声明可以用 companion  关键字标记:

 companion object {
        fun create(){
            println("create")
        }
    }
该伴生对象的成员可通过只使用类名作为限定符来调用:
Test.create()

请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:

interface Factory<T> {
    fun create(): T
}


class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

对象表达式和对象声明之间的语义差异

  • 对象表达式是在使用他们的地方立即执行(及初始化)的;
  • 对象声明是在第一次被访问到时延迟初始化的;
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值