前面的文章里我们已经介绍了如何创建类和类的成员变量,方法等。那么如果想要创建类级别的属性或者方法(就是类似于静态成员变量和方法)该如何实现呢?本篇文章将会介绍通过伴生对象实现以及数据类的相关内容。
我们创建一个名为MachineOperator的类,这个类有一个类级别的属性和方法,是用伴生对象生成的。还记得我们之前说到过单例对象的创建就是使用object关键字,而伴生对象的创建也是类似,就是在object关键字之前再加上companion关键字:
class MachineOperator(val name:String) {
fun checkin() = checkedId++
fun checkout() = checkedId--
companion object{
var checkedId = 0
fun minimumBreak() = "15 minutes every 2 hours"
}
}
不过这里需要说明一下,每个类只能有一个伴生对象。伴生对象中的方法和属性也就类似于Java中的静态变量和方法或者也可以理解为Kotlin中的类单例对象。伴生对象中的属性是该类的所有实例共享的,所以说在多线程情况下可能需要额外进行同步等操作。
访问伴生对象中的属性和方法等是和Java中访问静态变量和方法的形式是一样的,我们用以下代码测试一下:
fun main() {
val machine = MachineOperator("Mater").checkin()
println(MachineOperator.checkedId)
MachineOperator("Oliver").checkin()
println(MachineOperator.checkedId)
val str1 = MachineOperator.minimumBreak()
val str2 = MachineOperator.minimumBreak()
if(str1 === str2){
println("yes")
}else{
println("no")
}
}
有时候我们会需要访问整个伴生对象,这在伴生对象实现了某个接口时会经常出现,比如下面这个伴生对象就实现了Runnable接口:
companion object:Runnable{
var checkedId = 0
fun minimumBreak() = "15 minutes every 2 hours".toString()
override fun run(){
println("run run run")
}
如果我们想要访问这整个伴生对象,默认情况下就可以使用Companion字段,在我们没有指定伴生对象的名称时,这个伴生对象的名称就默认为Companion,比如我们可以这样访问:
fun main(){
MachineOperator.Companion.run()
}
我们也可以指定伴生对象的名称,只要在object关键字后加上我们要取的名字即可:
class MachineOperator(val name:String) {
fun checkin() = checkedId++
fun checkout() = checkedId--
companion object myComp:Runnable{
var checkedId = 0
fun minimumBreak() = "15 minutes every 2 hours".toString()
override fun run(){
println("run run run")
}
}
}
这种情况下我们就给我们的伴生对象取名为myComp,现在访问这整个伴生对象就是这样访问的:
fun main(){
MachineOperator.myComp.run()
}
可能我们会认为这个伴生对象中的属性方法是静态的,其实不然,它实际上还是一个类级别的单例对象,就如同object关键字所定义的一样。如果我们把它单纯认为是静态的可能会出现一些问题。
当我们引用伴生对象的成员时,Kotlin编译器会负责将调用路由到适当的单例实例。
Kotlin中的数据类是专用类,主要用于承载数据而不是行为。主构造函数需要使用val或者var至少定义一个属性,这里不允许使用non-val或者non-var。如果需要,可以在body{}中添加其他属性或者方法。不过在块体内定义的任何属性将不会在生成的equals(),hashCode()和toString()方法中使用。
定义数据类只需要在class之前加上data修饰符即可,下面就简单地创建了一个数据类,包含Id和name两个属性:
data class personn(val id:Int,val name:String) {
}
数据类将会自动生成一系列方法,比如equals(),hashCode()和toString()方法,我们可以来测试一下toString方法自动生成的:
fun main() {
val p = personn(1,"Jake")
val p1 = personn(2,"Mike")
println(p)
println(p1)
}
输出的内容是:
除此之外,数据类还会自动生成copy方法,这个方法会创建一个新对象,并将接收端对象的所有属性复制到结果对象中。与equals(),hashCode()和toString()方法不同,copy方法会复制对象中的所有属性包括body{}块中的属性。同时我们在使用这个copy方法时还可以更改一些属性的内容:
fun main() {
val p = personn(1,"Jake")
val p1 = personn(2,"Mike")
println(p)
println(p1)
val p2 = p.copy(name="ask")
println(p2)
}
输出的结果是:
需要说明的是这个自动生成的copy方法只是进行了浅拷贝,该方法不会深度复制内部引用的对象。
最后,数据类还会自动生成componentN的一系列方法,这一系列方法是用来进行解构的,比如我们可以进行以下解构:
fun main() {
val p = personn(1,"Jake")
val p1 = personn(2,"Mike")
println(p)
println(p1)
val p2 = p.copy(name="ask")
println(p2)
val(sb,qq) = p2
println("name is $qq,id is $sb")
}
书中给出了何时应该使用数据类型的情况:
本章中我们主要介绍了伴生对象和数据类的相关内容。比较重要的就是伴生对象中的内容,主要是用来实现Java中类似于静态成员变量和方法的作用,但是伴生对象又不是简单的静态的。数据类是Kotlin中的一种特殊的模版类,可以方便地实现一些方法。