类:类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础。类是一种用户定义的引用数据类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。[摘自百度百科]
在Java中可以说我们将类用得非常熟练了,那么在Kotlin中类与Java有什么不同呢?今天我们从类的定义、初始化、继承的异同来一谈究竟。
一 类定义
1.1 创建一个类
class Player{
}
与Java一样,使用class关键字 后面跟类名即可定义一个类。
1.2 类属性
类属性在Java中通常也叫为成员变量,可以定义成员变量的访问域、get、set方法等等。在Kotlin中也比较类似,Kotlin针对你定义得每一个属性,Kotlin都会产生一个field、一个getter、一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field并保护它里面得数据,只暴露getter和setter给到使用者使用。getter方法决定你如何读取属性值,每个属性都有getter方法。setter方法决定你如何设置属性值,因为属性也分为只读属性和可变属性,只有可变属性才有setter方法。Kotlin会自动提供默认得getter和setter方法,但在需要控制如何读写属性的时候,你可以自定义这2个方法。
class Player{
var name = "jack"
get() = field.capitalize()
set(value){
field = value.trim()
}
var age = 10
get() = field.absoluteValue
set(value) {
field = value.absoluteValue
}
val nickName = "pig"
get() = field.capitalize()
//只读变量不能set
// set(value) {
//
// }
var birthday = "2000-01-01"
private set(value) {
field = value
}
}
fun playerTest1(){
val player = Player()
println("name=${player.name} age=${player.age} nickName=${player.nickName} birthday=${player.birthday}")
player.name = "jacky"
player.age = -20
//这句会报错提示val变量不能设置
// player.nickName = "dog"
//这句会报错提示setter为私有
// player.birthday = "2001-01-01"
println("name=${player.name} age=${player.age} nickName=${player.nickName} birthday=${player.birthday}")
}
//结果
name=Jack age=10 nickName=Pig birthday=2000-01-01
name=Jacky age=20 nickName=Pig birthday=2000-01-01
在getter、setter属性的时候,与Java有点不同,我们操作的是一个field的封装属性而不是直接操作属性名来操作。我们可以将getter、setter设置为私有以阻止使用者调用来处理相关属性的获取和更改。
1.3 计算属性
计算属性是通过一个覆盖的get或set运算符来定义,者时候field就不需要了
class Player1{
val rolledValue
get() = (1..6).shuffled().first()
}
fun player1Test(){
val player1 = Player1()
println(player1.rolledValue)
println(player1.rolledValue)
}
//结果
1
4
可以看到属性的值是根据计算的结果来返回的,也就是说每次调用getter得到的结果都有可能不一样。
二 初始化
2.1 主构造函数
我们在类的定义中定义一个主构造函数,使用临时变量为类的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。
class Player3(_name:String,_age:Int){
var name = _name
var age = _age
}
val player3 = Player3("Jacky",20)
println("name=${player3.name} age=${player3.age}")
//结果
name=Jacky age=20
嗯,这个跟Java蛮像的,只是主构造函数括号直接写在了类名之后,不像Java那样还需要单独去写而已。
在Kotlin中允许你不使用临时变量赋值,而是直接用一个定义童锁指定参数和类属性。通常,我们更喜欢用这种方式定义类属性,因为他会减少重复代码。
class Player4(var name:String,var age:Int){
}
val player4 = Player4("Jack",18)
println("name=${player4.name} age=${player4.age}")
//结果
name=Jack age=18
是不是感觉很干净,连赋值都免了。
2.2 次构造函数
有主则有次,与Java一样,我们也可以为类定义次构造函数
class Player4(var name:String,var age:Int){
constructor(name:String):this(name,age = 20)
}
val player4 = Player4("Lily")
println("name=${player4.name} age=${player4.age}")
//结果
name=Lily age=20
同时,这个次构造函数还可以定义初始化代码逻辑
class Player4(var name:String,var age:Int){
constructor(name:String):this(name,age = 20){
this.name = name.toUpperCase()
}
}
//结果
name=LILY age=20
有点东西,感觉真的很灵活。写法也与Java差不太多。
2.3 默认参数
class Player5(var name:String = "Jack",var age:Int = 20){
}
val player5 = Player5()
println("name=${player5.name} age=${player5.age}")
//结果
name=Jack age=20
好吧,不多啰嗦,与Java比只是初始化值写在哪的问题吧。
2.4 初始化块
初始化块可以设置变量或值,执行检查有效性。
class Player5(var name:String = "Jack",var age:Int = 20){
init {
require(age > 0){"age must more than zero"}
}
}
val player5 = Player5(age = -1)
println("name=${player5.name} age=${player5.age}")
Exception in thread "main" java.lang.IllegalArgumentException: age must more than zero
at Player5.<init>(InitTest.kt:13)
at Player5.<init>(InitTest.kt:11)
at InitTestKt.main(InitTest.kt:8)
at InitTestKt.main(InitTest.kt)
在这里我们在初始化块里对参数执行了检查,如果不符合则抛出了异常,试着体会下吧。
对类的初始化Kotlin依照如下优先级进行执行:
a、主构造函数里声明的属性
b、类级别的属性赋值
c、init初始化块里的属性赋值和函数调用
d、次构造函数里的属性赋值和函数调用
2.5 延迟初始化
使用lateinit关键字相当于做了一个约定:在使用它之前负责初始化
如果无法确认lateinit变量是否完成初始化,可以调用isInitialized进行检查
class Player6{
lateinit var nickName:String
fun ready(){
nickName = "pig"
}
fun use(){
if(::nickName.isInitialized) println(nickName)
}
}
val player6 = Player6()
player6.ready()
//如果未调用初始化就直接调用,不会有任何输出,因为属性还未初始化
player6.use()
2.6 惰性初始化
延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它才进行初始化
class Player7{
val name:String by lazy {
println("init name")
"Jack"
}
}
val player7 = Player7()
println(player7.name)
println(player7.name)
//结果
init name
Jack
Jack
注意:惰性初始化的属性一定需要是只读属性,它的初始化工作会在第一次调用它的时候发生,且只发生一次。 en… 请结合代码与结果细品吧。
三 继承
3.1 在Kotlin类默认都是封闭的,是无法被其它类所继承的。如果需要让类能够被继承,那么需要是用open关键字去修饰。
class Player8: Player7() {
}
//如果这个类没有被open关键字修饰是无法被其它类继承的
open class Player7{
val name:String by lazy {
println("init name")
"Jack"
}
}
3.2 函数重载
父类的函数如果要支持被重载,那么也需要使用open关键字修饰,子类才能覆盖它。
class Player8: Player7() {
override fun sayHello() {
println("Hello,$name")
}
}
//如果这个类没有被open关键字修饰是无法被其它类继承的
open class Player7{
val name:String by lazy {
println("init name")
"Jack"
}
//没有open关键字则子类无法重载该函数
open fun sayHello(){
println("${name}, Hello")
}
}
3.3 类型检测
Kotlin的is运算符可以用来检查某个对象类型
val player7 = Player7()
println(player7 is Player7)
println(player7 is Player8)
3.4 类型转换
as操作符可以进行类型转换